分析和总结spring事务REQUIRES_NEW,REQUIRED的区别

分析和总结spring事务REQUIRES_NEW,REQUIRED的区别

问题描述

我们在开发中经常用到的事务传播属性有REQUIRES_NEW和REQUIRED,但是具体它们怎么使用,有什么区别,不管官方说明还是其他地方的描述总是不能让我们直观明白。下文将通过源码说明和具体实例来总结一下它们的区别和使用场景。

REQUIRES_NEW的使用特性

1.源码说明的理解
事务传播行为的枚举Propagation定义如下:
即,创建一个新事务,如果当前事务存在,则挂起当前事务在这里插入图片描述
即创建一个新事务,如果当前事务存在,则挂起当前事务。改如何理解呢,我们用一个例子显示一下。
insertUserAndUserBank作为外层方法,实现两个业务操作,新增用户并新增用户的银行卡信息。int i=1/0模拟了外层出现了异常。

/**
	 * 新增用户和用户银行卡信息
	 * 使用注解式事务
	 */
	@Transactional(propagation=Propagation.REQUIRED,rollbackForClassName="Exception")
	public void insertUserAndUserBank() throws SQLException {
		User user=new User();
		user.setCode("user01");
		user.setName("testName");
		//insertUser方法用Transactional修饰,可以看做它是处于insertUserAndUserBank的事务中的
		userDao.insertUser(user);
		
		UserBank userBank=new UserBank();
		userBank.setBank_code("bank01");
		userBank.setUser_id(user.getCode());
		userBankDao.insertUserBank(userBank);
		//模拟出现了异常情况
		int i=1/0;
	}

使用REQUIRES_NEW修饰的insertUserBank方法,实现新增银行卡操作,它被外层insertUserAndUserBank方法调用。

/**
	 *增加一条用户银行卡信息
	 * @throws SQLException 
	 */
	@Transactional(propagation=Propagation.REQUIRES_NEW,rollbackForClassName="Exception")
	public void insertUserBank(UserBank userBank) throws SQLException {
		System.out.println("userBankDao的jdbcTemplate:"+jdbcTemplate);
		System.out.println("userBankDao的jdbcTemplate的connction为:"+jdbcTemplate.getDataSource().getConnection());
		String sql = "insert into user_bank_tb (user_id,bank_code) values (?,?)";
		jdbcTemplate.update(sql, new Object[]{userBank.getUser_id(),userBank.getBank_code()});
		
	}

执行结果我们发现,外层事务出现异常,用户表更新操作被回滚,但REQUIRES_NEW修饰的insertUserBank方法执行成功。
在这里插入图片描述在这里插入图片描述
总结,REQUIRES_NEW修饰方法的特性:外面的事务对其不影响,不管外层是否提交回滚,它里面的内容会根据自己的执行情况,该提交就提交,该回滚就回滚。
现在我们模拟REQUIRES_NEW修饰方法出现异常情况,看看它是否对外层造成影响。
这里将int i=1/0 从insertUserAndUserBank方法中移到insertUserBank中。

/**
	 * 新增用户和用户银行卡信息
	 * 使用注解式事务:默认发生运行时异常或Erro异常时才会回滚,
	 */
	@Transactional(propagation=Propagation.REQUIRED,rollbackForClassName="Exception")
	public void insertUserAndUserBank() throws SQLException {
		User user=new User();
		user.setCode("user01");
		user.setName("testName");
		userDao.insertUser(user);
		
		UserBank userBank=new UserBank();
		userBank.setBank_code("bank01");
		userBank.setUser_id(user.getCode());
		userBankDao.insertUserBank(userBank);

	}
/**
	 *增加一条用户银行卡信息
	 * @throws SQLException 
	 */
	@Transactional(propagation=Propagation.REQUIRES_NEW,rollbackForClassName="Exception")
	public void insertUserBank(UserBank userBank) throws SQLException {
		System.out.println("userBankDao的jdbcTemplate:"+jdbcTemplate);
		System.out.println("userBankDao的jdbcTemplate的connction为:"+jdbcTemplate.getDataSource().getConnection());
		String sql = "insert into user_bank_tb (user_id,bank_code) values (?,?)";
		jdbcTemplate.update(sql, new Object[]{userBank.getUser_id(),userBank.getBank_code()});
		int i=1/0;
	}

执行结果我们发现,用户表和银行卡表都未更新,REQUIRES_NEW修饰的insertUserBank方法执行异常,它也引起外层其他业务的回滚。

在这里插入图片描述
在这里插入图片描述
总结,REQUIRES_NEW修饰方法的特性:它会对外层的事务有影响,如果它里面执行时出现异常不仅自己的内容回滚,也会导致外层事务也回滚。

REQUIRED的使用特性

1.源码说明的理解
事务传播行为的枚举Propagation定义如下:
在这里插入图片描述
即支持当前事务,如果不存在,则创建一个新事务。该如何理解呢,我们在用一个类似的例子演示一下。
insertUserAndUserBank作为外层方法,和上述一样。int i=1/0模拟了外层出现了异常。我们把insertUserBank改为REQUIRED修饰

/**
	 *增加一条用户银行卡信息
	 * @throws SQLException 
	 */
	@Transactional(propagation=Propagation.REQUIRED,rollbackForClassName="Exception")
	public void insertUserBank(UserBank userBank) throws SQLException {
		System.out.println("userBankDao的jdbcTemplate:"+jdbcTemplate);
		System.out.println("userBankDao的jdbcTemplate的connction为:"+jdbcTemplate.getDataSource().getConnection());
		String sql = "insert into user_bank_tb (user_id,bank_code) values (?,?)";
		jdbcTemplate.update(sql, new Object[]{userBank.getUser_id(),userBank.getBank_code()});
		
	}

执行结果我们发现,用户表和银行卡表都未更新,外层其他业务执行异常,REQUIRED修饰的insertUserBank方法也被回滚。
在这里插入图片描述
总结,REQUIRED修饰方法的特性:外面的事务对其有影响,外层事务出现异常,它里面的内容也会回滚。
现在我们模拟REQUIRED修饰方法出现异常情况,看看它是否对外层造成影响。
这里将int i=1/0 从insertUserAndUserBank方法中移到insertUserBank中。

/**
	 *增加一条用户银行卡信息
	 * @throws SQLException 
	 */
	@Transactional(propagation=Propagation.REQUIRED,rollbackForClassName="Exception")
	public void insertUserBank(UserBank userBank) throws SQLException {
		System.out.println("userBankDao的jdbcTemplate:"+jdbcTemplate);
		System.out.println("userBankDao的jdbcTemplate的connction为:"+jdbcTemplate.getDataSource().getConnection());
		String sql = "insert into user_bank_tb (user_id,bank_code) values (?,?)";
		jdbcTemplate.update(sql, new Object[]{userBank.getUser_id(),userBank.getBank_code()});
		int i=1/0 ;
	}

执行结构我们发现,用户表和银行卡表都未更新,REQUIRED修饰方法insertUserBank方法执行异常,它也引起外层其他业务的回滚。
在这里插入图片描述

问题总结

REQUIRES_NEW的事务,不受外层调用者影响,但会影响外层的事务。
REQUIRED的事务,即受外层调用者影响,也会影响外层的事务。

实际业务如何使用: 在同一个方法中,因为大多数情况是一系列业务要保证要么都成功要么都失败的,所以各个业务方法使用默认的REQUIRED方式即可。
如果中间有一个特殊的业务方法,和其他业务不关联,我们可以给它的方法设置REQUIRES_NEW,这样就能保证其他业务有异常时,它也不会被回滚。

<think>嗯,用户现在遇到了Spring事务嵌套的问题,具体是内层用了REQUIRES_NEW之后,外层事务切换不回来。之前我给出的解决方案提到了使用保存点、连接池配置传播机制,但用户现在的情况可能更复杂。需要结合他们提供的引用内容来分析。 首先,根据引用[1][3],当内层事务使用REQUIRES_NEW时,确实会创建独立的事务,这时候外层事务会被挂起。理论上,内层事务提交或回滚后,外层事务应该恢复。但用户遇到的问题是外层事务无法恢复,可能是事务管理器在处理事务恢复时出了问题。 用户提到的“切换不回来”可能是指外层事务在继续执行时,使用的连接不正确,或者事务上下文没有正确恢复。这可能涉及到事务同步管理的问题。比如,Spring的DataSourceTransactionManager在处理嵌套事务时,需要正确管理挂起恢复的事务资源。 需要检查事务管理器的配置是否正确,特别是多数据源的情况下。如果使用了多个数据源,但没有正确配置事务管理器,可能会导致事务上下文无法恢复。例如,每个数据源应该有对应的事务管理器,并且通过@Transactional注解指定正确的事务管理器。 另外,引用[2]提到外层事务捕获了内层异常,导致异常没有继续向上传播,从而可能影响事务的回滚。用户的问题可能是在内层事务回滚后,外层事务没有正确处理异常,导致无法恢复。这时候需要确保外层事务没有捕获内层异常,或者正确处理了异常以触发回滚。 可能还需要考虑数据库本身对嵌套事务的支持情况。虽然REQUIRES_NEW通过保存点实现,但某些数据库可能在处理保存点时存在问题,导致事务无法正确恢复。比如,MySQL的InnoDB支持保存点,但某些版本可能有bug。 另外,事务隔离级别也可能影响事务的恢复。如果外层事务内层事务的隔离级别不同,可能会导致锁的问题,进而影响事务的恢复。需要检查隔离级别是否一致,或者是否有必要调整。 此外,用户可能在代码中混合使用了编程式事务声明式事务,导致事务管理混乱。比如,在外层使用@Transactional,而在内层手动处理事务,这样可能导致Spring无法正确管理事务的边界。 还需要检查日志,确认事务的挂起恢复是否按预期执行。启用Spring的调试日志,查看事务的创建、挂起、提交回滚过程,可以帮助定位问题所在。 总结下来,可能的解决方案包括:检查事务管理器配置,确保多数据源正确处理;确认异常处理是否正确,避免外层事务吞没异常;验证数据库对保存点的支持;调整隔离级别;以及检查代码中是否存在混合事务管理的情况。此外,详细的事务日志分析也是必要的。</think>#### 解决方案:Spring事务嵌套中REQUIRES_NEW导致外层事务失效的深度修复 --- ### 核心问题定位 当内层事务使用`Propagation.REQUIRES_NEW`时,Spring会通过**物理事务分离**机制创建新数据库连接[^1],但外层事务恢复时可能出现: 1. 事务同步器未正确重建(`TransactionSynchronizationManager`绑定状态丢失) 2. 连接持有线程上下文切换异常 3. 嵌套事务边界处理逻辑缺陷 --- ### 分层解决方案 #### 一、事务管理器强化配置(关键修复) 在**多数据源环境**中必须显式指定事务管理器: ```java @Bean(name = "txManager") public PlatformTransactionManager transactionManager(DataSource dataSource) { DataSourceTransactionManager txManager = new DataSourceTransactionManager(); txManager.setDataSource(dataSource); txManager.setNestedTransactionAllowed(true); // 必须开启嵌套支持 txManager.setValidateExistingTransaction(true); return txManager; } // 使用处明确指定 @Transactional(value = "txManager", propagation = Propagation.REQUIRED) public void outerMethod() { // 主事务操作 innerService.innerMethod(); } ``` #### 二、事务恢复保护模式 通过`TransactionTemplate`手动控制恢复过程: ```java public void outerMethod() { TransactionTemplate template = new TransactionTemplate(transactionManager); template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); template.execute(status -> { // 主事务操作 try { innerService.innerMethod(); } catch (Exception e) { // 记录日志但保持主事务活动 status.setRollbackOnly(); // 选择性回滚 } return null; }); } ``` #### 三、连接持有验证机制 在`AbstractPlatformTransactionManager`层面增加验证: ```java public class EnhancedDataSourceTransactionManager extends DataSourceTransactionManager { @Override protected void doResume(@Nullable Object transaction, SuspendedResourcesHolder resourcesHolder) { super.doResume(transaction, resourcesHolder); // 验证连接是否有效 if (!DataSourceUtils.isConnectionTransactional(resourcesHolder.getConnection(), getDataSource())) { throw new TransactionSystemException("Connection recovery failed"); } } } ``` #### 四、嵌套事务异常处理规范 采用**异常标记模式**保证事务状态同步: ```java @Transactional(propagation = Propagation.REQUIRES_NEW) public void innerMethod() { try { // 业务操作 } catch (Exception e) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); throw new MarkedRollbackException(e); // 自定义标记异常 } } @Transactional public void outerMethod() { try { innerService.innerMethod(); } catch (MarkedRollbackException ex) { // 获取原始异常但不影响主事务 logger.error("子事务失败", ex.getCause()); } } ``` #### 五、事务监听强化 通过`TransactionSynchronization`保证资源同步: ```java TransactionSynchronizationManager.registerSynchronization( new TransactionSynchronization() { @Override public void suspend() { // 记录当前事务状态快照 } @Override public void resume() { // 验证连接与快照是否一致 } } ); ``` --- ### 调试关键点 1. 开启事务调试日志: ```properties logging.level.org.springframework.transaction.interceptor=TRACE logging.level.org.springframework.jdbc.datasource=DEBUG ``` 2. 连接有效性验证(MySQL示例): ```sql SHOW STATUS LIKE 'Threads_connected'; SHOW PROCESSLIST; ``` 3. 事务状态追踪API: ```java TransactionSynchronizationManager.getCurrentTransactionName(); TransactionSynchronizationManager.isActualTransactionActive(); ``` --- ### 不同场景下的表现对比 | 传播行为 | 外层回滚 | 内层回滚 | 连接数 | 事务恢复 | |-------------------|----------|----------|--------|----------| | REQUIRED | 全部回滚 | 全部回滚 | 1 | 自动 | | REQUIRES_NEW | 不影响 | 自身回滚 | 2 | 需验证 | | NESTED | 全部回滚 | 部分回滚 | 1 | 自动 | --- ### 典型错误场景修复 **案例:支付主事务+日志子事务** ```java @Transactional public void paymentProcess() { try { paymentService.execute(); // 主事务 logService.record(); // REQUIRES_NEW } catch (LogFailureException e) { // 日志服务异常不中断支付 } } // 修复方案:强制刷新事务管理器 @Transactional(propagation = Propagation.REQUIRES_NEW) public void recordLog() { TransactionInterceptor.currentTransactionStatus().flush(); } ``` ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值