spring的事务传播属性REQUIRED_NESTED的原理介绍
传统事务中回滚点的使用
package com.morris.spring.demo.jdbc;import java.sql.*;/** * 传统JDBC中回滚点的使用 */public class TraditionSavePointDemo { public static void main(String[] args) throws SQLException { String url = "jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&allowMultiQueries=true&characterEncoding=UTF-8&useFastDateParsing=false&zeroDateTimeBehavior=convertToNull"; String username = "user"; String password = "user"; Connection connection = DriverManager.getConnection(url, username, password); connection.setAutoCommit(false); // 不自动提交 Savepoint one = connection.setSavepoint("one"); Savepoint two = null; try { Statement statement = connection.createStatement(); statement.execute("insert into t_good(good_name, price) values('iphone14', 9999)"); statement.close(); two = connection.setSavepoint("two"); } catch (Exception e) { e.printStackTrace(); connection.rollback(one); // 回滚事务 } try { Statement statement = connection.createStatement(); statement.execute("insert into t_good(good_name, price) values('iphone15', 9999)"); statement.close(); boolean flag = true; if(flag) { throw new RuntimeException("xxxx"); } } catch (Exception e) { e.printStackTrace(); connection.rollback(two); // 回滚事务 } connection.commit(); }}
在一个事务中可以指定回滚事务到某一个阶段,实现精确控制事务。
事务的传播属性NESTED
在spring中,要想使用事务中的回滚点,可以使用传播属性NESTED。
com.morris.spring.service.TransactionService#addGoodAndArea
@Transactional(propagation = Propagation.REQUIRED)public void addGoodAndArea() { System.out.println("------addGoodAndArea-------"); goodService.addGood(); areaService.addArea(0);}
com.morris.spring.service.AreaServiceImpl#addArea
@Transactional(propagation = Propagation.NESTED)@Overridepublic boolean addArea(int i) { int y = 1000000 / i; Area area = new Area(); area.setAreaCode(y); area.setAreaName("shenzhen"); return areaDao.insert(area);}
com.morris.spring.service.GoodServiceImpl#addGood
@Transactional(propagation = Propagation.NESTED)@Overridepublic boolean addGood() { Good good = new Good(); good.setGoodName("iphone"); good.setPrice(BigDecimal.valueOf(99999)); return goodDao.insert(good);}
运行结果如下:
DEBUG DataSourceTransactionManager:384 - Creating new transaction with name [com.morris.spring.service.TransactionService.addGoodAndArea]: PROPAGATION_REQUIRED,ISOLATION_DEFAULTDEBUG DriverManagerDataSource:144 - Creating new JDBC DriverManager Connection to [jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&allowMultiQueries=true&characterEncoding=UTF-8&useFastDateParsing=false&zeroDateTimeBehavior=convertToNull]DEBUG DataSourceTransactionManager:267 - Acquired Connection [com.mysql.cj.jdbc.ConnectionImpl@8ef162] for JDBC transactionDEBUG DataSourceTransactionManager:285 - Switching JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@8ef162] to manual commit------addGoodAndArea-------DEBUG DataSourceTransactionManager:477 - Creating nested transaction with name [com.morris.spring.service.GoodServiceImpl.addGood]DEBUG JdbcTemplate:860 - Executing prepared SQL updateDEBUG JdbcTemplate:609 - Executing prepared SQL statement [insert into t_good(good_name, price) values(?,?)]DEBUG DataSourceTransactionManager:767 - Releasing transaction savepointDEBUG DataSourceTransactionManager:477 - Creating nested transaction with name [com.morris.spring.service.AreaServiceImpl.addArea]DEBUG DataSourceTransactionManager:870 - Rolling back transaction to savepointDEBUG DataSourceTransactionManager:877 - Initiating transaction rollbackDEBUG DataSourceTransactionManager:347 - Rolling back JDBC transaction on Connection [com.mysql.cj.jdbc.ConnectionImpl@8ef162]DEBUG DataSourceTransactionManager:392 - Releasing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@8ef162] after transactionjava.lang.ArithmeticException: / by zero... ...
发现整个事务都已经回滚了,按照回滚点的逻辑,addArea()方法抛出异常,不是应该只回滚到addArea()前吗,也就是addGood()应该被提交,这是为什么呢?
如果我们将addArea()方法try catch起来,就能得到我们想要的结果,addGood()被提交,而addArea()回滚,这又是为什么呢?我们带着这几个问题来分析源码。
addAreaAndGood()开启事务
addAreaAndGood()开启事务,最外层方法使用传播属性PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED效果都一样,都是开启一个新的事务。
org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED || def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW || def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { // 第一次进来 SuspendedResourcesHolder suspendedResources = suspend(null); if (debugEnabled) { logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def); } try { // 开启新事务 return startTransaction(def, transaction, debugEnabled, suspendedResources); } catch (RuntimeException | Error ex) { resume(null, suspendedResources); throw ex; }}
addGood()获得事务并创建回滚点
addGood()从ThreadLocal中获得addAreaAndGood()创建的事务,然后发现自己的传播属性为PROPAGATION_NESTED,就创建了一个回滚点。
org.springframework.transaction.support.AbstractPlatformTransactionManager#handleExistingTransaction
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { if (!isNestedTransactionAllowed()) { throw new NestedTransactionNotSupportedException( "Transaction manager does not allow nested transactions by default - " + "specify 'nestedTransactionAllowed' property with value 'true'"); } if (debugEnabled) { logger.debug("Creating nested transaction with name [" + definition.getName() + "]"); } if (useSavepointForNestedTransaction()) { // Create savepoint within existing Spring-managed transaction, // through the SavepointManager API implemented by TransactionStatus. // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization. DefaultTransactionStatus status = prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null); // 创建回滚点 status.createAndHoldSavepoint(); return status; } else { // Nested transaction through nested begin and commit/rollback calls. // Usually only for JTA: Spring synchronization might get activated here // in case of a pre-existing JTA transaction. return startTransaction(definition, transaction, debugEnabled, null); }}
addGood()提交事务时释放回滚点
addGood()并不会真正的提交事务,因为事务并不是addGood()创建的,只是在提交时会将之前创建的回滚点释放。
org.springframework.transaction.support.AbstractPlatformTransactionManager#processCommit
if (status.hasSavepoint()) { // NESTED的提交 if (status.isDebug()) { logger.debug("Releasing transaction savepoint"); } unexpectedRollback = status.isGlobalRollbackOnly(); // 只是释放回滚点 status.releaseHeldSavepoint();}
addArea()获得事务并创建回滚点
流程与addGood()一致。
addArea()回滚事务释放回滚点
addArea()发生异常,会执行回滚事务的逻辑,并没有真正的回滚事务,因为事务并不是addArea()创建的,,只是将之前创建的回滚点释放。 org.springframework.transaction.support.AbstractPlatformTransactionManager#processRollback
if (status.hasSavepoint()) { // 用于NESTED传播机制,发生异常 // 回滚至回滚点 if (status.isDebug()) { logger.debug("Rolling back transaction to savepoint"); } status.rollbackToHeldSavepoint();}
addAreaAndGood()回滚这个事务
addArea()发生异常后继续往外抛,addAreaAndGood()也会捕获到异常,然后执行回滚逻辑,这样整个事务都回滚了。 org.springframework.transaction.support.AbstractPlatformTransactionManager#processRollback
else if (status.isNewTransaction()) { // 只有最外层的事务newTransaction=true if (status.isDebug()) { logger.debug("Initiating transaction rollback"); } // 事务的回滚 /** * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#doRollback(org.springframework.transaction.support.DefaultTransactionStatus) */ doRollback(status);}
为什么将addArea()方法try catch起来,整个事务就不会回滚了呢?
因为将addArea()方法try catch起来后,addAreaAndGood()就会执行提交事务的逻辑,这样addGood()就被提交了。