概要
トランザクションとは何か、トランザクション管理を行う目的やどのように管理していくのかについてまとめた。
トランザクションとは
トランザクションとは、「DBに対する一連の処理」を管理する仕組み。
トランザクションが管理する範囲内で、DB操作に失敗したらロールバック、一連のDB操作に成功したらコミット等を行い、DBの不整合が起きないようにする。
トランザクション管理の例
例えば、以下のような一連のDB更新操作がまとまった「送金処理」がある。
・Bさんの口座残高を増やす
片方のテーブル更新処理が失敗した場合、不整合が起きないようにDB更新前の状態に戻す(ロールバック)。
すべてのテーブル更新処理が成功した場合、DBの状態を更新する(コミット)。
このように、トランザクション管理内での一連のDB操作を管理することで、DBの整合性を保つ役割がある。
トランザクションの目的
トランザクションを行う目的は、ACID特性を守ることにある。
上記を守ることで、DBの整合性を保つ。
ACID特性
トランザクションが満たすべき4つの基本的な特性のイニシャルをとっている。
特性 | 説明 |
---|---|
Atomicity(原子性) | トランザクション内のすべての操作が完全に実行されるか、または一つも実行されないことを保証する。 |
Consistency(一貫性) | トランザクションの実行前後で、データベースが一貫した状態を維持することを保証する。 |
Isolation(隔離性) | 同時に実行される複数のトランザクションが、互いに影響を与えないようにすることを保証する。 |
Durability(持続性) | 一度コミットされたトランザクションの結果は、システム障害が発生しても失われることがないことを保証する。 |
上記のなかで、実装時に注意するのが「Atomicity」と「Isolation」になる。
トランザクションの境界
トランザクション管理を行うには、トランザクションの開始と終了位置(トランザクションの境界線)を定義する必要がある。
この範囲内で行う一連のDB操作が、トランザクションが保証すべきACID特性に従って処理される。
境界線 = メソッド
基本的にトランザクションの開始と終了の境界線は、サービス層のメソッドになる。
プレゼンテーション層からサービス層のメソッドを呼び出したところからトランザクションが開始され、そのメソッドが終了したらトランザクションが終了することになる。
トランザクション管理
Springでトランザクション管理の役割を担うのは、トランザクションマネージャというオブジェクトになる。
このオブジェクトによって、異なるDBやDBアクセスフレームワーク(JDBC, Hibernate, MyBatisなど)に対しても、統一された方法でトランザクション制御を実装できる。
PlatformTransactionManager
トランザクションマネージャの役割を担うのが、PlatformTransactionManagerインターフェースになる。
使用するDBアクセスフレームワークによって、PlatformTransactionManagerの実装クラスを使い分けてトランザクション管理を行う。
実装クラス | 説明 |
---|---|
DataSourceTransactionManager | JDBCやMyBatisなどのDataSourceに対してトランザクション制御を行う。 |
HibernateTransactionManager | HibernateのSessionに対してトランザクション制御を行う。 |
JpaTransactionManager | JPAのEntityManagerに対してトランザクション制御を行う。 |
JtaTransactionManager | JTAを介して分散トランザクション制御を行う。 |
トランザクション定義情報
アプリケーション要件に応じてトランザクションマネージャをカスタマイズできる。
カスタマイズできる定義項目は以下となる。
・独立性レベル(隔離レベルともいう)
・タイムアウト(秒)
・読み取り専用
・ロールバック対象例外
・コミット対象例外
伝搬属性
トランザクションの伝搬方法を設定する。
具体的には、トランザクションが既に開始している他のトランザクションにどのように関連するかを定義できる。
例えば、新しいトランザクションを開始する、既存のトランザクションに参加する、既存のトランザクションが存在する場合はそれを使用するが存在しない場合は何もしない、などの挙動を指定する。
伝搬属性の設定値
以下のサービスメソッドAに対して、伝搬属性を設定したときの挙動を紹介する。
サービスメソッドAは「コントローラーAから直接呼ばれる」ケースと「コントローラーB>サービスメソッドBから呼ばれる」ケースがある。
サービスメソッドAを直接呼び出した場合、またはサービスメソッドBから間接的に呼び出した場合の整理が以下となる。
サービスメソッドAの伝搬属性の設定値 | サービスメソッドAを直接呼び出した場合 | サービスメソッドAを間接的に呼び出した場合 |
---|---|---|
Propagation.REQUIRED(伝搬属性のデフォルト値) | トランザクション開始 | メソッドBのトランザクションに参加 |
Propagation.REQUIRES_NEW | トランザクション開始 | 新規トランザクション開始(既存トランザクションに参加しない) |
Propagation.SUPPORTS | トランザクションなしで実行 | メソッドBのトランザクションに参加(既存トランザクションが存在すれば参加) |
Propagation.MANDATORY | 例外をスロー | メソッドBのトランザクションに参加(既存トランザクションに参加前提) |
Propagation.NESTED | トランザクション開始 | メソッドBのトランザクション内で独立したトランザクション開始 |
Propagation.NEVER | トランザクションなしで実行 | 例外をスロー(既存トランザクションが存在すれば例外を投げる) |
Propagation.NOT_SUPPORTED | トランザクションなしで実行 | トランザクションなしで実行(既存のトランザクションが存在すれば一時停止する) |
独立性レベル
トランザクションの独立性レベルを設定する。
具体的には、複数のトランザクションが同時に実行される場合に、一つのトランザクションが他のトランザクションの操作結果をどの程度「見る」ことができるかを設定する。
独立性レベルを設定することで、トランザクション間で発生する可能性のある問題(ダーティリード、ノンリピータブルリード、ファントムリード)を制御する。
独立性レベルが低いと発生する問題
ダーティリード、ノンリピータブルリード、ファントムリードについて紹介する。
問題 | 説明 |
---|---|
ダーティリード (Dirty Read) | あるトランザクションが、他のトランザクションによって変更されたがまだコミットされていないデータを読み取ることができる現象。 もし読み取った後で他のトランザクションがロールバックした場合、無効なデータを基に処理を進めてしまうことになる。 |
ノンリピータブルリード (Non-Repeatable Read) | あるトランザクションが同じデータを2回読み取る間に、他のトランザクションによってそのデータが変更されてコミットされることで、2回目の読み取り結果が異なる現象。 |
ファントムリード (Phantom Read) | あるトランザクションが同じクエリを2回実行する間に、他のトランザクションがそのクエリの結果に影響を与える新しいデータを挿入または削除してコミットすることで、2回目のクエリ結果が異なるケース。 最初のクエリと2回目のクエリの間で「幽霊」のように新しい行が現れたり消えたりする現象。 |
独立性レベルの設定値
独立性レベルの設定値について紹介する。
独立性レベル | 説明 |
---|---|
Isolation.READ_UNCOMMITTED | 他のトランザクションがコミット前に行った変更を読み取ることができる。 他のトランザクションの待ち時間が少なくなり、性能が上がる。 |
Isolation.READ_COMMITTED | コミットされたデータのみを読み取ることができる。 |
Isolation.REPEATABLE_READ | あるトランザクションが一度読み取ったデータを、トランザクションが終了するまで何度でも同じ値で読み取れることができる。 読み取っている間は他のトランザクションの変更が反映されない。 |
Isolation.SERIALIZABLE | 最も厳しい隔離レベルで、トランザクションが完全に順序付けられ、他のトランザクションの影響を受けずに実行される。 性能への影響が大きく、デッドロックの可能性が高まる。 |
Isolation.DEFAULT | DBが提供するデフォルトの独立性レベルを使用する。 |
独立性レベルと関連する問題
独立性レベルの設定値と発生しうる問題の関係性は以下となる。
独立性レベル | Dirty Read | Non-Repeatable Read | Phantom Read |
---|---|---|---|
Isolation.READ_UNCOMMITTED | 発生する可能性あり | 発生する可能性あり | 発生する可能性あり |
Isolation.READ_COMMITTED | なし | 発生する可能性あり | 発生する可能性あり |
Isolation.REPEATABLE_READ | なし | なし | 発生する可能性あり |
Isolation.SERIALIZABLE | なし | なし | なし |
その他設定値
その他のトランザクション設定値の概要は以下となる。
定義情報 | 説明 |
---|---|
タイムアウト(秒) | トランザクションが完了するまでの最大時間を指定する。 この時間を超えると、トランザクションは自動的にロールバックされる。 |
読み取り専用 | トランザクションがデータの読み取りのみを行い、データの更新を行わないことを示す。 |
ロールバック対象例外 | トランザクションをロールバックするべき例外を定義する。 デフォルトでは非検査例外(RuntimeExceptionとサブクラス)がスローされた場合にのみロールバックされる。 |
コミット対象例外 | トランザクションをコミットするべき例外を定義する。 デフォルトでは検査例外がスローされた際にはコミットされる。 Spring JDBCなどのフレームワークでは、検査例外も一律DataAccessException(RuntimeExceptionのサブクラス)に変換されるため、検査例外は意図してスローしないと発生しない。 |
実装の種類
トランザクション機能を使用するには、「宣言的トランザクション」と「明示的トランザクション」の2種類の方法がある。
基本的には「明示的トランザクション」の場合、トランザクション処理を実装するため推奨されない。
「宣言的トランザクション」はアノテーションを使用する方法とAOPを活用する方法がある。
具体的な実装方法については、別途紹介する。