2016年9月27日火曜日

Springでのトランザクション管理

Springのトランザクション管理は大きく分けて以下の2つの管理方法が存在する。

1.プログラミングによるトランザクション管理

プログラミングによってトランザクションを手動管理する方法。以下のようなイメージでトランザクション制御コードをソースコード内に記述する。
transactionManager.begin();  transactionManager.commit();  transactionManager.rollback();  
ソースコード内にトランザクション制御コードも入りこむため、見通しが悪くなったり制御を間違えるとバグの温床にもなったりするので、極力使うべきではない。

2.宣言的トランザクション管理

「あるメソッドを呼び出したときにトランザクションをかける」と宣言する方法。以下のようなイメージでメソッド(やクラスに)に@Transactionalアノテーションを付与することで実現される。
以下の例では、updateFooメソッドを呼び出したときにトランザクションをかける、ということ。
※XMLベースでも宣言可能だがここではアノテーションベースを利用する
アノテーションベースのイメージ.
public class DefaultFooService implements FooService {            public Foo getFoo(String fooName) {                  // do something          }            // these settings have precedence for this method          @Transactional          public void updateFoo(Foo foo) {                  // do something          }  }  
トランザクション制御コードがソースコード内に入りこむことがないため、この管理方法が推奨されている。
詳細は後述するが、メソッドやクラスに@Transactionalアノテーションを付与することで管理され、トランザクションの開始、コミット、ロールバックは自動で行われる。
ただし、ロールバックの注意点として、非検査例外(RuntimeException及びそのサブクラス)が発生した場合はロールバックされるが、検査例外(Exception及びそのサブクラスでRuntimeExceptionのサブクラスじゃないもの)が発生した場合はロールバックされずコミットされる。
なお、Springの宣言的トランザクションはAOPを使って実現されている。
※AOPについてはSpringでAOPを参照。
本記事では「2.宣言的トランザクション管理」について明記する。

トランザクションマネージャ

Springではいくつかのトランザクションマネージャが用意されている。
これらは利用するDBアクセスAPIによって使い分ける。
DBアクセスAPIトランザクションマネージャ
JDBCorg.springframework.jdbc.datasource.DataSourceTransactionManager
JDOorg.springframework.orm.jdo.JdoTransactionManager
JTAorg.springframework.transaction.jta.JtaTransactionManager
Hibernateorg.springframework.orm.hibernate.HibernateTransactionManager

トランザクション制御

冒頭でも述べたが、以下のようにメソッドにTransactionalアノテーションを付与する方法とクラスにTransactionalアノテーションを付与する方法がある。

メソッドにTransactionalアノテーションを付与する

メソッドにTransactionalアノテーション付与することで、メソッドが呼ばれたタイミング(正確にはメソッド開始前)にトランザクションが開始され、対象のメソッドが正常終了した場合はコミット、例外で終了した場合はロールバックされるようになる(再掲になるがロールバックの注意点として、非検査例外(RuntimeException及びそのサブクラス)が発生した場合はロールバックされるが、検査例外(Exception及びそのサブクラスでRuntimeExceptionのサブクラスじゃないもの)が発生した場合はロールバックされずコミットされる)。いづれもアノテーションを付与しておくだけで自動でやってくれる。
public class DefaultFooService implements FooService {            public Foo getFoo(String fooName) {                  // do something          }            // these settings have precedence for this method          @Transactional          public void updateFoo(Foo foo) {                  // do something          }  }  

クラスにTransactionalアノテーションを付与する

以下のようにクラスにTransactionalアノテーションを付与することで、そのクラス内の全てのメソッドにトランザクション制御をかけることができる。
@Transactional(readOnly = true)  public class DefaultFooService implements FooService {        public Foo getFoo(String fooName) {          // do something      }        // these settings have precedence for this method      @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)      public void updateFoo(Foo foo) {          // do something      }  }  
ただし、クラスに対して記述した設定はメソッドで記述された設定で上書きされることに注意する必要がある。上記の場合、updateFooメソッドは@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)が適用される(クラスに付与されている@Transactional(readOnly = true)は効かない)。
なお、Transactionalアノテーションはpublicメソッドのみに適用可能。public以外のメソッドにも付与できてコンパイルも通るが、実行時エラーにもならないので注意が必要。
you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.

Transactionalアノテーションのプロパティと設定例

Transactionalアノテーションに指定可能なプロパティ

プロパティ定義
valueString1つのアプリケーションで複数のトランザクションマネージャを利用する場合に、どのトランザクションマネージャを利用するかを指定する。
propagationEnum: Propagationどのような場合にトランザクションを開始するか等の属性を指定する。
isolationEnum: Isolationトランザクションの分離レベルを指定する。デフォルトはDEFAULTレベル。
readOnlyboolean読み取り専用かどうかを指定する。デフォルトはfalse。
timeoutint (in seconds granularity)そのトランザクションがタイムアウトする時間を指定する(秒指定)。デフォルトは利用しているトランザクション管理システムのタイムアウトを利用する。
rollbackForClass<? extends Throwable>[]検査例外が発生した際もロールバックしたい場合に指定する。
rollbackForClassNameString[]rollbackForと同じ、違いは例外クラス名また例外クラス名の配列を指定するところ。
noRollbackForClass<? extends Throwable>[]ロールバックしたくない場合に指定する。
noRollbackForClassNameString[]noRollbackForと同じ、違いは例外クラス名また例外クラス名の配列を指定するところ。

プロパティの設定例

value

// 利用したいトランザクションマネージャの名前を指定する  @Transactional("order")  

propagation

設定例.
//   常に新しいトランザクションを開始する  @Transactional(propagation=Propagation.REQUIRES_NEW)  
propagationのその他の設定値
プロパティ定義
MANDATORYトランザクションが開始されていなければ例外を発生させる。
NESTEDトランザクションが存在する場合はネストしたトランザクションを開始し、他のPROPAGATION_REQUIREDのように振る舞う。
NEVERトランザクションが開始されていれば例外を発生させる。
NOT_SUPPORTEDトランザクションが存在する場合は中断して、トランザクションをはらない(トランザクションを利用しない)
REQUIREDトランザクションが開始されていなければ新規に開始し、すでに開始されていればそのトランザクションをそのまま利用する。
REQUIRES_NEW常に新しいトランザクションを開始する。トランザクションが存在する場合は中断して新しいトランザクションを開始する。
SUPPORTSトランザクションが開始されていればそれを利用。開始されていなければトランザクションをはらない(トランザクションを利用しない)。

isolation

設定例.
// ダーティーリード・ファジーリードは発生しないが、ファントムリードは発生する可能性がある  @Transactional(isolation=Isolation.REPEATABLE_READ)  
isolationのその他の設定値
※下に行くほど分離レベルが高くなる
プロパティ定義
DEFAULT利用するデータストアのデフォルト分離レベルを利用する。
READ_UNCOMMITTEDダーティーリード・ファジーリード・ファントムリードが発生する可能性がある。
READ_COMMITTEDダーティーリードは発生しないが、ファジーリード・ファントムリードは発生する可能性がある。
REPEATABLE_READダーティーリード・ファジーリードは発生しないが、ファントムリードは発生する可能性がある。
SERIALIZABLEダーティーリード・ファジーリード・ファントムリードは発生する可能性がない。

readOnly

設定例.
// 読み取り専用トランザクション  @Transactional(readOnly = true)  

timeout

設定例.
// 10秒でタイムアウト  @Transactional(timeout = 10)  

rollbackFor

設定例.
//   全ての例外が発生した場合、ロールバックさせる  @Transactional(rollbackFor=Exception.class)  

rollbackForClassName

設定例.
//   全ての例外が発生した場合、ロールバックさせる  @Transactional(rollbackForClassName={"Exception"})  

noRollbackFor

設定例.
//   全ての例外が発生した場合、ロールバックさせないようにする  @Transactional(noRollbackFor=Exception.class)  

noRollbackForClassName

設定例.
//   全ての例外が発生した場合、ロールバックさせないようにする  @Transactional(noRollbackForClassName={"Exception"})  

参考

0 件のコメント:

コメントを投稿