Spring Bootでトランザクション(@Transactional)の伝搬属性(propagation)を試す
Spring Bootでトランザクション(@Transactional)の伝搬属性(propagation)について確認したメモです。
Spring Bootでトランザクションの伝搬属性について試してみました。
伝搬属性は種類が多いのと、すでにトランザクションが存在している場合と存在していない場合で挙動が異なるので
かなりややこしいです。
伝搬属性
伝搬属性はトランザクションの伝搬レベルを設定する属性です。
すでにトランザクションが存在している場合にどのように伝搬するのかを設定します。
Springのトランザクションでは下記の伝搬レベルが定義されています。
- REQUIRED
- REQUIRES_NEW
- SUPPORTS
- NOT_SUPPORTED
- MANDATORY
- NESTED
- NEVER
Maven
下記のdependencyを追加しました。
DBはH2を使います。JDBCで接続します。
pom.xml
<dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency>
JDBCのログを出力するために、log4jdbc-spring-boot-starterを追加しました。
これでデフォルトでspyログが出力されます。
pom.xml
<dependency> <groupId>com.integralblue</groupId> <artifactId>log4jdbc-spring-boot-starter</artifactId> <version>1.0.1</version> </dependency>
テスト用クラス
DBを更新するメソッドを持ったServiceAとServiceBを作成しました。
ServiceAには各伝搬レベルで更新するメソッドがあります。
ServiceA.java
// default @Transactional(propagation = Propagation.REQUIRED) public void required() { jdbc.update("UPDATE person SET age=? WHERE name = 'Andy'", 21); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void requiresNew() { jdbc.update("UPDATE person SET age=? WHERE name = 'Andy'", 21); } @Transactional(propagation = Propagation.SUPPORTS) public void supports() { jdbc.update("UPDATE person SET age=? WHERE name = 'Andy'", 21); } @Transactional(propagation = Propagation.NOT_SUPPORTED) public void notSupported() { jdbc.update("UPDATE person SET age=? WHERE name = 'Andy'", 21); } @Transactional(propagation = Propagation.MANDATORY) public void mandatory() { jdbc.update("UPDATE person SET age=? WHERE name = 'Andy'", 21); } @Transactional(propagation = Propagation.NESTED) public void nested() { jdbc.update("UPDATE person SET age=? WHERE name = 'Andy'", 21); } @Transactional(propagation = Propagation.NEVER) public void never() { jdbc.update("UPDATE person SET age=? WHERE name = 'Andy'", 21); }
ServiceBでは、トランザクションを開始してDBを更新した後、
ServiceAの各伝搬レベルのメソッドを呼んでいます。
ServiceB.java
// default @Transactional(propagation = Propagation.REQUIRED) public void required() { jdbc.update("UPDATE person SET age=? WHERE name = 'Bobby'", 20); myServiceA.required(); } @Transactional(propagation = Propagation.REQUIRED) public void requiresNew() { jdbc.update("UPDATE person SET age=? WHERE name = 'Bobby'", 20); myServiceA.requiresNew(); } @Transactional(propagation = Propagation.REQUIRED) public void supports() { jdbc.update("UPDATE person SET age=? WHERE name = 'Bobby'", 20); myServiceA.supports(); } @Transactional(propagation = Propagation.REQUIRED) public void notSupported() { jdbc.update("UPDATE person SET age=? WHERE name = 'Bobby'", 20); myServiceA.notSupported(); } @Transactional(propagation = Propagation.REQUIRED) public void mandatory() { jdbc.update("UPDATE person SET age=? WHERE name = 'Bobby'", 20); myServiceA.mandatory(); } @Transactional(propagation = Propagation.REQUIRED) public void nested() { jdbc.update("UPDATE person SET age=? WHERE name = 'Bobby'", 20); myServiceA.nested(); } @Transactional(propagation = Propagation.REQUIRED) public void never() { jdbc.update("UPDATE person SET age=? WHERE name = 'Bobby'", 20); myServiceA.never(); }
それぞれServiceAとServiceBでどのようにトランザクションが伝搬されるか確認します。
伝搬レベル
REQUIRED (default)
REQUIREDはトランザクションが存在しない場合、新規にトランザクションを開始し、
すでに存在する場合はそのトランザクションを利用します。
REQUIREDがdefaultの伝搬レベルになっています。
ServiceA直接
ログの下記の間がトランザクションになっています。
4. Connection.setAutoCommit(false) returned : : 4. Connection.commit() returned
新規にトランザクションが開始されていることが分かります。
jdbc.connection : 4. Connection opened jdbc.audit : 4. Connection.new Connection returned jdbc.audit : null. DataSource.getConnection() returned jdbc.audit : 4. Connection.getAutoCommit() returned true // トランザクション1 開始 jdbc.audit : 4. Connection.setAutoCommit(false) returned jdbc.audit : 4. PreparedStatement.new PreparedStatement returned jdbc.audit : 4. Connection.prepareStatement(UPDATE person SET age=? WHERE name = 'Andy') returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@36183389 jdbc.audit : 4. PreparedStatement.setObject(1, 21) returned jdbc.sqlonly : UPDATE person SET age=21 WHERE name = 'Andy' jdbc.sqltiming : UPDATE person SET age=21 WHERE name = 'Andy' {executed in 1 msec} jdbc.audit : 4. PreparedStatement.executeUpdate() returned 1 jdbc.audit : 4. PreparedStatement.close() returned jdbc.audit : 4. Connection.commit() returned // トランザクション1 終了 jdbc.audit : 4. Connection.setAutoCommit(true) returned jdbc.audit : 4. Connection.isReadOnly() returned false jdbc.connection : 4. Connection closed jdbc.audit : 4. Connection.close() returned
ServiceB⇒ServiceA (ServiceB経由でServiceA呼び出し)
ServiceBでトランザクションが新規作成され、
その後ServiceAの処理では新規作成されず既存のトランザクションを利用しています。
jdbc.connection : 4. Connection opened jdbc.audit : 4. Connection.new Connection returned jdbc.audit : null. DataSource.getConnection() returned jdbc.audit : 4. Connection.getAutoCommit() returned true // トランザクション1 開始 jdbc.audit : 4. Connection.setAutoCommit(false) returned jdbc.audit : 4. PreparedStatement.new PreparedStatement returned jdbc.audit : 4. Connection.prepareStatement(UPDATE person SET age=? WHERE name = 'Bobby') returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@21270ae7 jdbc.audit : 4. PreparedStatement.setObject(1, 20) returned jdbc.sqlonly : UPDATE person SET age=20 WHERE name = 'Bobby' jdbc.sqltiming : UPDATE person SET age=20 WHERE name = 'Bobby' {executed in 2 msec} jdbc.audit : 4. PreparedStatement.executeUpdate() returned 1 jdbc.audit : 4. PreparedStatement.close() returned jdbc.audit : 4. PreparedStatement.new PreparedStatement returned jdbc.audit : 4. Connection.prepareStatement(UPDATE person SET age=? WHERE name = 'Andy') returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@5bb27803 jdbc.audit : 4. PreparedStatement.setObject(1, 21) returned jdbc.sqlonly : UPDATE person SET age=21 WHERE name = 'Andy' jdbc.sqltiming : UPDATE person SET age=21 WHERE name = 'Andy' {executed in 0 msec} jdbc.audit : 4. PreparedStatement.executeUpdate() returned 0 jdbc.audit : 4. PreparedStatement.close() returned jdbc.audit : 4. Connection.commit() returned // トランザクション1 終了 jdbc.audit : 4. Connection.setAutoCommit(true) returned jdbc.audit : 4. Connection.isReadOnly() returned false jdbc.connection : 4. Connection closed jdbc.audit : 4. Connection.close() returned
REQUIRES_NEW
REQUIRES_NEWはトランザクションが存在しない場合、新規にトランザクションを開始し、
すでにトランザクションが存在する場合でも新規にトランザクションを開始します。
ServiceA直接
新規にトランザクションが開始されていることが分かります。
jdbc.connection : 4. Connection opened jdbc.audit : 4. Connection.new Connection returned jdbc.audit : null. DataSource.getConnection() returned jdbc.audit : 4. Connection.getAutoCommit() returned true // トランザクション1 開始 jdbc.audit : 4. Connection.setAutoCommit(false) returned jdbc.audit : 4. PreparedStatement.new PreparedStatement returned jdbc.audit : 4. Connection.prepareStatement(UPDATE person SET age=? WHERE name = 'Andy') returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@3222821c jdbc.audit : 4. PreparedStatement.setObject(1, 21) returned jdbc.sqlonly : UPDATE person SET age=21 WHERE name = 'Andy' jdbc.sqltiming : UPDATE person SET age=21 WHERE name = 'Andy' {executed in 1 msec} jdbc.audit : 4. PreparedStatement.executeUpdate() returned 1 jdbc.audit : 4. PreparedStatement.close() returned jdbc.audit : 4. Connection.commit() returned // トランザクション1 終了 jdbc.audit : 4. Connection.setAutoCommit(true) returned jdbc.audit : 4. Connection.isReadOnly() returned false jdbc.connection : 4. Connection closed jdbc.audit : 4. Connection.close() returned
ServiceB⇒ServiceA (ServiceB経由でServiceA呼び出し)
ServiceBでトランザクションが新規作成され、
その後ServiceAの処理では新規にトランザクションが作成されているのが分かります。
jdbc.connection : 4. Connection opened jdbc.audit : 4. Connection.new Connection returned jdbc.audit : null. DataSource.getConnection() returned jdbc.audit : 4. Connection.getAutoCommit() returned true // トランザクション1 開始 jdbc.audit : 4. Connection.setAutoCommit(false) returned jdbc.audit : 4. PreparedStatement.new PreparedStatement returned jdbc.audit : 4. Connection.prepareStatement(UPDATE person SET age=? WHERE name = 'Bobby') returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@11e23f42 jdbc.audit : 4. PreparedStatement.setObject(1, 20) returned jdbc.sqlonly : UPDATE person SET age=20 WHERE name = 'Bobby' jdbc.sqltiming : UPDATE person SET age=20 WHERE name = 'Bobby' {executed in 1 msec} jdbc.audit : 4. PreparedStatement.executeUpdate() returned 1 jdbc.audit : 4. PreparedStatement.close() returned jdbc.connection : 5. Connection opened jdbc.audit : 5. Connection.new Connection returned jdbc.audit : null. DataSource.getConnection() returned jdbc.audit : 5. Connection.getAutoCommit() returned true // トランザクション2 開始 jdbc.audit : 5. Connection.setAutoCommit(false) returned jdbc.audit : 5. PreparedStatement.new PreparedStatement returned jdbc.audit : 5. Connection.prepareStatement(UPDATE person SET age=? WHERE name = 'Andy') returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@5298d8ca jdbc.audit : 5. PreparedStatement.setObject(1, 21) returned jdbc.sqlonly : UPDATE person SET age=21 WHERE name = 'Andy' jdbc.sqltiming : UPDATE person SET age=21 WHERE name = 'Andy' {executed in 0 msec} jdbc.audit : 5. PreparedStatement.executeUpdate() returned 0 jdbc.audit : 5. PreparedStatement.close() returned jdbc.audit : 5. Connection.commit() returned // トランザクション2 終了 jdbc.audit : 5. Connection.setAutoCommit(true) returned jdbc.audit : 5. Connection.isReadOnly() returned false jdbc.connection : 5. Connection closed jdbc.audit : 5. Connection.close() returned jdbc.audit : 4. Connection.commit() returned // トランザクション1 終了 jdbc.audit : 4. Connection.setAutoCommit(true) returned jdbc.audit : 4. Connection.isReadOnly() returned false jdbc.connection : 4. Connection closed jdbc.audit : 4. Connection.close() returned
SUPPORTS
SUPPORTSはトランザクションが存在しない場合、トランザクションを使用せず、
すでに存在する場合はそのトランザクションを利用します。
ServiceA直接
下記のようなログがなく、トランザクションが開始されていません。
4. Connection.setAutoCommit(false) returned : : 4. Connection.commit() returned
トランザクションを使用せずに直接更新しています。
jdbc.connection : 4. Connection opened jdbc.audit : 4. Connection.new Connection returned jdbc.audit : null. DataSource.getConnection() returned jdbc.audit : 4. PreparedStatement.new PreparedStatement returned // トランザクションなしで更新 jdbc.audit : 4. Connection.prepareStatement(UPDATE person SET age=? WHERE name = 'Andy') returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@6d15f646 jdbc.audit : 4. PreparedStatement.setObject(1, 21) returned jdbc.sqlonly : UPDATE person SET age=21 WHERE name = 'Andy' jdbc.sqltiming : UPDATE person SET age=21 WHERE name = 'Andy' {executed in 1 msec} jdbc.audit : 4. PreparedStatement.executeUpdate() returned 1 jdbc.audit : 4. PreparedStatement.close() returned jdbc.connection : 4. Connection closed jdbc.audit : 4. Connection.close() returned
ServiceB⇒ServiceA (ServiceB経由でServiceA呼び出し)
ServiceBでトランザクションが新規作成されて、
その後ServiceAの処理では新規作成されず既存のトランザクションを利用しています。
jdbc.connection : 4. Connection opened jdbc.audit : 4. Connection.new Connection returned jdbc.audit : null. DataSource.getConnection() returned jdbc.audit : 4. Connection.getAutoCommit() returned true // トランザクション1 開始 jdbc.audit : 4. Connection.setAutoCommit(false) returned jdbc.audit : 4. PreparedStatement.new PreparedStatement returned jdbc.audit : 4. Connection.prepareStatement(UPDATE person SET age=? WHERE name = 'Bobby') returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@4a31b11d jdbc.audit : 4. PreparedStatement.setObject(1, 20) returned jdbc.sqlonly : UPDATE person SET age=20 WHERE name = 'Bobby' jdbc.sqltiming : UPDATE person SET age=20 WHERE name = 'Bobby' {executed in 1 msec} jdbc.audit : 4. PreparedStatement.executeUpdate() returned 1 jdbc.audit : 4. PreparedStatement.close() returned jdbc.audit : 4. PreparedStatement.new PreparedStatement returned jdbc.audit : 4. Connection.prepareStatement(UPDATE person SET age=? WHERE name = 'Andy') returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@57af01c1 jdbc.audit : 4. PreparedStatement.setObject(1, 21) returned jdbc.sqlonly : UPDATE person SET age=21 WHERE name = 'Andy' jdbc.sqltiming : UPDATE person SET age=21 WHERE name = 'Andy' {executed in 1 msec} jdbc.audit : 4. PreparedStatement.executeUpdate() returned 0 jdbc.audit : 4. PreparedStatement.close() returned jdbc.audit : 4. Connection.commit() returned // トランザクション1 終了 jdbc.audit : 4. Connection.setAutoCommit(true) returned jdbc.audit : 4. Connection.isReadOnly() returned false jdbc.connection : 4. Connection closed jdbc.audit : 4. Connection.close() returned
NOT_SUPPORTED
NOT_SUPPORTEDはトランザクションを使用しません。
すでにトランザクションが存在する場合はそのトランザクションを一時停止し、トランザクションを使用せずに処理を実行後、
停止していたトランザクションを再開します。
ServiceA直接
トランザクションを使用せずに直接更新しています。
jdbc.connection : 4. Connection opened jdbc.audit : 4. Connection.new Connection returned jdbc.audit : null. DataSource.getConnection() returned jdbc.audit : 4. PreparedStatement.new PreparedStatement returned // トランザクションなしで更新 jdbc.audit : 4. Connection.prepareStatement(UPDATE person SET age=? WHERE name = 'Andy') returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@4f28c6d9 jdbc.audit : 4. PreparedStatement.setObject(1, 21) returned jdbc.sqlonly : UPDATE person SET age=21 WHERE name = 'Andy' jdbc.sqltiming : UPDATE person SET age=21 WHERE name = 'Andy' {executed in 1 msec} jdbc.audit : 4. PreparedStatement.executeUpdate() returned 1 jdbc.audit : 4. PreparedStatement.close() returned jdbc.connection : 4. Connection closed jdbc.audit : 4. Connection.close() returned
ServiceB⇒ServiceA (ServiceB経由でServiceA呼び出し)
ServiceBでトランザクションが新規作成されて、
ServiceAでは新規にコネクションを作成してトランザクションを使用せずにDBを更新し、
その後、ServiceBのトランザクションが再開して終了しています。
jdbc.connection : 4. Connection opened jdbc.audit : 4. Connection.new Connection returned jdbc.audit : null. DataSource.getConnection() returned jdbc.audit : 4. Connection.getAutoCommit() returned true // トランザクション1 開始 jdbc.audit : 4. Connection.setAutoCommit(false) returned jdbc.audit : 4. PreparedStatement.new PreparedStatement returned jdbc.audit : 4. Connection.prepareStatement(UPDATE person SET age=? WHERE name = 'Bobby') returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@3c6f913 jdbc.audit : 4. PreparedStatement.setObject(1, 20) returned jdbc.sqlonly : UPDATE person SET age=20 WHERE name = 'Bobby' jdbc.sqltiming : UPDATE person SET age=20 WHERE name = 'Bobby' {executed in 2 msec} jdbc.audit : 4. PreparedStatement.executeUpdate() returned 1 jdbc.audit : 4. PreparedStatement.close() returned jdbc.connection : 5. Connection opened jdbc.audit : 5. Connection.new Connection returned jdbc.audit : null. DataSource.getConnection() returned jdbc.audit : 5. PreparedStatement.new PreparedStatement returned // 新しいコネクションでトランザクションなしで更新 jdbc.audit : 5. Connection.prepareStatement(UPDATE person SET age=? WHERE name = 'Andy') returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@fa4d0f5 jdbc.audit : 5. PreparedStatement.setObject(1, 21) returned jdbc.sqlonly : UPDATE person SET age=21 WHERE name = 'Andy' jdbc.sqltiming : UPDATE person SET age=21 WHERE name = 'Andy' {executed in 1 msec} jdbc.audit : 5. PreparedStatement.executeUpdate() returned 0 jdbc.audit : 5. PreparedStatement.close() returned jdbc.connection : 5. Connection closed jdbc.audit : 5. Connection.close() returned jdbc.audit : 4. Connection.commit() returned // トランザクション1 終了 jdbc.audit : 4. Connection.setAutoCommit(true) returned jdbc.audit : 4. Connection.isReadOnly() returned false jdbc.connection : 4. Connection closed jdbc.audit : 4. Connection.close() returned
MANDATORY
MANDATORYはすでにトランザクションが存在することを前提にします。
トランザクションが存在しない場合、例外が発生します。
すでに存在する場合はそのトランザクションを利用します。
ServiceA直接
トランザクションが存在しないため例外が発生しています。
2017-08-19 21:08:20.649 ERROR 73901 --- o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'] with root cause org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory' : : :
ServiceB⇒ServiceA (ServiceB経由でServiceA呼び出し)
ServiceBでトランザクションが新規作成され、
その後ServiceAの処理ではそのトランザクションを利用しています。
jdbc.connection : 4. Connection opened jdbc.audit : 4. Connection.new Connection returned jdbc.audit : null. DataSource.getConnection() returned jdbc.audit : 4. Connection.getAutoCommit() returned true // トランザクション1 開始 jdbc.audit : 4. Connection.setAutoCommit(false) returned jdbc.audit : 4. PreparedStatement.new PreparedStatement returned jdbc.audit : 4. Connection.prepareStatement(UPDATE person SET age=? WHERE name = 'Bobby') returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@2bc05c21 jdbc.audit : 4. PreparedStatement.setObject(1, 20) returned jdbc.sqlonly : UPDATE person SET age=20 WHERE name = 'Bobby' jdbc.sqltiming : UPDATE person SET age=20 WHERE name = 'Bobby' {executed in 2 msec} jdbc.audit : 4. PreparedStatement.executeUpdate() returned 1 jdbc.audit : 4. PreparedStatement.close() returned jdbc.audit : 4. PreparedStatement.new PreparedStatement returned jdbc.audit : 4. Connection.prepareStatement(UPDATE person SET age=? WHERE name = 'Andy') returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@610ccf8 jdbc.audit : 4. PreparedStatement.setObject(1, 21) returned jdbc.sqlonly : UPDATE person SET age=21 WHERE name = 'Andy' jdbc.sqltiming : UPDATE person SET age=21 WHERE name = 'Andy' {executed in 0 msec} jdbc.audit : 4. PreparedStatement.executeUpdate() returned 0 jdbc.audit : 4. PreparedStatement.close() returned jdbc.audit : 4. Connection.commit() returned // トランザクション1 終了 jdbc.audit : 4. Connection.setAutoCommit(true) returned jdbc.audit : 4. Connection.isReadOnly() returned false jdbc.connection : 4. Connection closed jdbc.audit : 4. Connection.close() returned
NESTED
NESTEDはネストしたトランザクションを作成します。
トランザクションが存在しない場合、新規にトランザクションを開始し、
すでに存在する場合はそのトランザクションを利用しますが、その部分だけネストしたトランザクションのように処理されます。
ServiceA直接
新規にトランザクションが開始されていることが分かります。
jdbc.connection : 4. Connection opened jdbc.audit : 4. Connection.new Connection returned jdbc.audit : null. DataSource.getConnection() returned jdbc.audit : 4. Connection.getAutoCommit() returned true // トランザクション1 開始 jdbc.audit : 4. Connection.setAutoCommit(false) returned jdbc.audit : 4. PreparedStatement.new PreparedStatement returned jdbc.audit : 4. Connection.prepareStatement(UPDATE person SET age=? WHERE name = 'Andy') returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@1bc2f58 jdbc.audit : 4. PreparedStatement.setObject(1, 21) returned jdbc.sqlonly : UPDATE person SET age=21 WHERE name = 'Andy' jdbc.sqltiming : UPDATE person SET age=21 WHERE name = 'Andy' {executed in 2 msec} jdbc.audit : 4. PreparedStatement.executeUpdate() returned 1 jdbc.audit : 4. PreparedStatement.close() returned jdbc.audit : 4. Connection.commit() returned // トランザクション1 終了 jdbc.audit : 4. Connection.setAutoCommit(true) returned jdbc.audit : 4. Connection.isReadOnly() returned false jdbc.connection : 4. Connection closed jdbc.audit : 4. Connection.close() returned
ServiceB⇒ServiceA (ServiceB経由でServiceA呼び出し)
ログを見てみると、ネストした部分にはSAVEPOINTが設定され、ServiceBの処理が正常に終わるとSAVEPOINTが解除されています。
SAVEPOINTを設定することでネストしたトランザクションでも内側だけロールバックすることが出来ます。
(ただし部分的なトランザクションはDBの実装に依存するので、H2の場合はSAVEPOINTを利用しているようです)
下記の様にネストした部分にはSAVEPOINTが設定されています。
4. Connection.setSavepoint(SAVEPOINT_1) returned sp0: id=0 name=SAVEPOINT_1 : : 4. Connection.releaseSavepoint(sp0: id=0 name=SAVEPOINT_1) returned
jdbc.connection : 4. Connection opened jdbc.audit : 4. Connection.new Connection returned jdbc.audit : null. DataSource.getConnection() returned jdbc.audit : 4. Connection.getAutoCommit() returned true // トランザクション1 開始 jdbc.audit : 4. Connection.setAutoCommit(false) returned jdbc.audit : 4. PreparedStatement.new PreparedStatement returned jdbc.audit : 4. Connection.prepareStatement(UPDATE person SET age=? WHERE name = 'Bobby') returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@29b25138 jdbc.audit : 4. PreparedStatement.setObject(1, 20) returned jdbc.sqlonly : UPDATE person SET age=20 WHERE name = 'Bobby' jdbc.sqltiming : UPDATE person SET age=20 WHERE name = 'Bobby' {executed in 2 msec} jdbc.audit : 4. PreparedStatement.executeUpdate() returned 1 jdbc.audit : 4. PreparedStatement.close() returned jdbc.audit : 4. Connection.getMetaData() returned dbMeta4: conn9: url=jdbc:h2:mem:testdb user=SA // SAVEPOINTを設定 jdbc.audit : 4. Connection.setSavepoint(SAVEPOINT_1) returned sp0: id=0 name=SAVEPOINT_1 jdbc.audit : 4. PreparedStatement.new PreparedStatement returned jdbc.audit : 4. Connection.prepareStatement(UPDATE person SET age=? WHERE name = 'Andy') returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@4f28c6d9 jdbc.audit : 4. PreparedStatement.setObject(1, 21) returned jdbc.sqlonly : UPDATE person SET age=21 WHERE name = 'Andy' jdbc.sqltiming : UPDATE person SET age=21 WHERE name = 'Andy' {executed in 0 msec} jdbc.audit : 4. PreparedStatement.executeUpdate() returned 0 jdbc.audit : 4. PreparedStatement.close() returned // SAVEPOINTを解除 jdbc.audit : 4. Connection.releaseSavepoint(sp0: id=0 name=SAVEPOINT_1) returned jdbc.audit : 4. Connection.commit() returned // トランザクション1 終了 jdbc.audit : 4. Connection.setAutoCommit(true) returned jdbc.audit : 4. Connection.isReadOnly() returned false jdbc.connection : 4. Connection closed jdbc.audit : 4. Connection.close() returned
NEVER
NEVERはトランザクションを使用しません。
すでにトランザクションが存在する場合は例外が発生します。
ServiceA直接
トランザクションを使用せずに直接更新しています。
jdbc.connection : 4. Connection opened jdbc.audit : 4. Connection.new Connection returned jdbc.audit : null. DataSource.getConnection() returned jdbc.audit : 4. PreparedStatement.new PreparedStatement returned // トランザクションを使用せずに更新 jdbc.audit : 4. Connection.prepareStatement(UPDATE person SET age=? WHERE name = 'Andy') returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@25294da3 jdbc.audit : 4. PreparedStatement.setObject(1, 21) returned jdbc.sqlonly : UPDATE person SET age=21 WHERE name = 'Andy' jdbc.sqltiming : UPDATE person SET age=21 WHERE name = 'Andy' {executed in 1 msec} jdbc.audit : 4. PreparedStatement.executeUpdate() returned 1 jdbc.audit : 4. PreparedStatement.close() returned jdbc.connection : 4. Connection closed jdbc.audit : 4. Connection.close() returned
ServiceB⇒ServiceA (ServiceB経由でServiceA呼び出し)
すでにトランザクションが存在するため例外が発生しています。
jdbc.connection : 4. Connection opened jdbc.audit : 4. Connection.new Connection returned jdbc.audit : null. DataSource.getConnection() returned jdbc.audit : 4. Connection.getAutoCommit() returned true // トランザクション1 開始 jdbc.audit : 4. Connection.setAutoCommit(false) returned jdbc.audit : 4. PreparedStatement.new PreparedStatement returned jdbc.audit : 4. Connection.prepareStatement(UPDATE person SET age=? WHERE name = 'Bobby') returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@50e1fd41 jdbc.audit : 4. PreparedStatement.setObject(1, 20) returned jdbc.sqlonly : UPDATE person SET age=20 WHERE name = 'Bobby' jdbc.sqltiming : UPDATE person SET age=20 WHERE name = 'Bobby' {executed in 1 msec} jdbc.audit : 4. PreparedStatement.executeUpdate() returned 1 jdbc.audit : 4. PreparedStatement.close() returned jdbc.audit : 4. Connection.rollback() returned jdbc.audit : 4. Connection.setAutoCommit(true) returned jdbc.audit : 4. Connection.isReadOnly() returned false jdbc.connection : 4. Connection closed jdbc.audit : 4. Connection.close() returned // すでにトランザクションが存在しているため例外発生 2017-08-19 21:13:38.859 --- o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'] with root cause org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never' : : :
終わり。
ソースは下記にあげときました。