spring-transaction-propagation

Spring 事务:是spring 通过 aop实现的一套事务管理功能,可以定义隔离级别,传播行为,回滚规则,事务超时,是否只读。

1、隔离级别 对应mysql 几乎都是定义为 可重复读

2、传播特性则不同,不同的业务场景,使用的特性非常不同

 

spring 事务的创建的时间点:通过aop 在调用需要开启事务的方法前开启,方法结束后提交事务(准确的说还要看事务的传播行为),或者抛出异常回滚事务

 

事务传播行为(Transaction Propagation Behavior):定义当前方法的事务和调用方事务的关系,比如:可以加入到调用方的事务,也可以重新开启事务,亦或开始事务保存点等。

 

 Spring 提供的几种事务传播行为:

1.PROPAGATION_REQUIRED(加入已有事务)

    尝试加入已经存在的事务中,如果没有则开启一个新的事务。

注意:如果这个事务回滚,因为和调用方使用的是一个事务,所以调用方的事务同时回滚,这一点也是和nested事务的区别之处 (这里都是说内层的事务传播特性,调用方已经开启了事务)

2.RROPAGATION_REQUIRES_NEW(独立事务)

    挂起当前存在的事务,并开启一个全新的事务,新事务与已存在的事务之间彼此没有关系。

  注意:如果做到没有任何关系 ?重新启动一个connection连接,和当前连接没有关系,自然事务也完全隔离了

3.PROPAGATION_NESTED(嵌套事务)

    在当前事务上开启一个子事务(Savepoint),如果递交主事务。那么连同子事务一同递交。如果递交子事务则保存点之前的所有事务都会被递交。

注意:嵌套事务本身不会开启新的事务,即用的是已经存在的事务,nested事务抛出异常,调用方捕获异常,则调用方的事务不会回滚(savepoint特性);当然如果继续抛出或者调用方没有捕获异常亦或调用方有异常,都回滚(这里都是说内层的事务传播特性,调用方已经开启了事务)

 

更专业的描述为:嵌套事务:就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。

 

4.PROPAGATION_SUPPORTS(跟随环境)

    是指 Spring 容器中如果当前没有事务存在,就以非事务方式执行;如果有,就使用当前事务。

5.PROPAGATION_NOT_SUPPORTED(非事务方式)

    是指如果存在事务则将这个事务挂起,并使用新的数据库连接。新的数据库连接不使用事务。

6.PROPAGATION_NEVER(排除事务)

    当存在事务时抛出异常,否则就已非事务方式运行。

7.PROPAGATION_MANDATORY(需要事务)

    如果不存在事务就抛出异常,否则就已事务方式运行。

 

其中第4-7的事务行为比较简单,不再详述;最应该引起重视的是:RROPAGATION_REQUIRES_NEW ,当前方法启动的事务和调用方的事务没有关系,他和调用方的数据库连接都不是一个

 

 如果有时间,最好运行下下面的用例,仔细体会下不同事务的传播属性的实际情况:

测试用例:

/**
 * @author xinchun.wang
 */
@Service
public class UserDaoImpl  {

	@Resource
	private JdbcTemplate jdbcTemplate;
	
	
	
	public void testWithOutTransaction() {
		String info = "testWithOutTransaction_"+System.currentTimeMillis() + UUID.randomUUID().toString();
		jdbcTemplate.execute("INSERT INTO user_log(info, `time`) VALUES ( '"+info+"','"+info+"')");
	}
	
	/**
	 *  挂起当前存在的事务,并开启一个全新的事务,新事务与已存在的事务之间彼此没有关系。
	 */
	@Transactional(value = "transactionManager",propagation=Propagation.REQUIRES_NEW)
	public void testTransactionNew() {
		String info = "Afater_insertLoginLog_"+System.currentTimeMillis() + UUID.randomUUID().toString();
		jdbcTemplate.execute("INSERT INTO user_log(info, `time`) VALUES ( '"+info+"','"+info+"')");
		Connection connection = ((ConnectionHolder)(TransactionSynchronizationManager.getResourceMap().values().iterator().next())).getConnection();
		System.out.println("$$$$$$$"+connection.hashCode());
	}

	
	
	/**
	 * 尝试加入已经存在的事务中,如果没有则开启一个新的事务。
	 */
	@Transactional(value = "transactionManager",propagation=Propagation.REQUIRED)
	public void testTransactionRequired() {
		String info = "Before_tesTransactionRequired_"+System.currentTimeMillis() + UUID.randomUUID().toString();
		jdbcTemplate.execute("INSERT INTO user_log(info, `time`) VALUES ( '"+info+"','"+info+"')");
		
		if(true){
			throw new RuntimeException("Error");
		}
		
		info = "After_tesTransactionRequired_"+System.currentTimeMillis() + UUID.randomUUID().toString();
		jdbcTemplate.execute("INSERT INTO user_log(info, `time`) VALUES ( '"+info+"','"+info+"')");
		
	}
	
	
	
	/**
	 *   在当前事务上开启一个子事务(Savepoint),如果递交主事务。那么连同子事务一同递交。如果递交子事务则保存点之前的所有事务都会被递交。
	 */
	@Transactional(value = "transactionManager",propagation=Propagation.NESTED)
	public void testNestTransactionWithException() {
		String info = "testNestTransactionWithException_"+System.currentTimeMillis() + UUID.randomUUID().toString();
		jdbcTemplate.execute("INSERT INTO user_log(info, `time`) VALUES ( '"+info+"','"+info+"')");
		if(true){
			throw new RuntimeException("Error");
		}
	}

	
	/**
	 *   在当前事务上开启一个子事务(Savepoint),如果递交主事务。那么连同子事务一同递交。如果递交子事务则保存点之前的所有事务都会被递交。
	 */
	@Transactional(value = "transactionManager",propagation=Propagation.NESTED)
	public void testNestTransaction() {
		String info = "testNestTransaction_"+System.currentTimeMillis() + UUID.randomUUID().toString();
		jdbcTemplate.execute("INSERT INTO user_log(info, `time`) VALUES ( '"+info+"','"+info+"')");
		UserServiceImpl.printData("&&&&&&&&&&");
	}
	
	
}

 

/**
 * @author xinchun.wang
 */
@Service
public class UserServiceImpl implements IUserService {

	@Resource
	private UserDaoImpl userDaoImpl;
	
	/**
	 *  挂起当前存在的事务,并开启一个全新的事务,新事务与已存在的事务之间彼此没有关系。
	 */
	@Override
	@Transactional(value = "transactionManager",propagation=Propagation.REQUIRED)
	public void testTransactionNew() {
		userDaoImpl.testWithOutTransaction();
		Connection connection = ((ConnectionHolder)(TransactionSynchronizationManager.getResourceMap().values().iterator().next())).getConnection();
		System.out.println("#######"+connection.hashCode());
		userDaoImpl.testTransactionNew();
		userDaoImpl.testTransactionRequired();
	}

	

	/**
	 * 嵌套事务失败,外部事务捕获异常,可以正常提交
	 * start transaction
	 *  insert data1
	 *  save savepoint1
	 *  insert data2
	 *  roback savepoint1
	 *  commit
	 */
	@Override
	@Transactional(value = "transactionManager",propagation=Propagation.REQUIRED)
	public void testNestTransactionWithException() {
		userDaoImpl.testWithOutTransaction();
		try{
			userDaoImpl.testNestTransactionWithException();
		}catch(Exception e){
			e.printStackTrace();
		}
	}

	
	/**
	 * 
	 * 调用者出现unchecked异常,却能触发所调用的nested事务的回滚.(外部事务失败,都取消)
	 * start transaction
	 *  insert data1
	 *  save savepoint1
	 *  insert data2
	 *  rollback
	 */
	@Override
	@Transactional(value = "transactionManager",propagation=Propagation.REQUIRED)
	public void testNestTransaction() {
		UserServiceImpl.printData("########");
		userDaoImpl.testWithOutTransaction();
		userDaoImpl.testNestTransaction();
		
		if(true){
			throw new RuntimeException("Error");
		}
	}
	
	
	@Override
	@Transactional(value = "transactionManager",propagation=Propagation.REQUIRED)
	public void testTransactionRequared() {
		System.out.println(TransactionSynchronizationManager.getResourceMap().values());
		Connection connection = ((ConnectionHolder)(TransactionSynchronizationManager.getResourceMap().values().iterator().next())).getConnection();
		System.out.println("$$$$$$$"+connection.hashCode());
		userDaoImpl.testWithOutTransaction();
		try{
			userDaoImpl.testTransactionRequired();
		}catch(Exception e){
			
		}
	}
	
	
	/** 通过这个,可以确定当前连接的hashcode*/
	public static void printData(String key){
		System.out.println(TransactionSynchronizationManager.getResourceMap().values());
		Connection connection = ((ConnectionHolder)(TransactionSynchronizationManager.getResourceMap().values().iterator().next())).getConnection();
		
		System.out.println(key+connection.hashCode());
	}
}

 

 

最后:在事务修饰的方法内部,直接调用事务修饰的方法,如果没有通过aop代理来调用,不会有事务,也不会是一个数据库连接。

### Spring Boot集成`dynamic-datasource-spring-boot-starter`的事务配置 当使用 `dynamic-datasource-spring-boot-starter` 进行多数据源切换时,为了确保跨多个数据库操作的一致性和可靠性,正确设置事务管理至关重要。默认情况下,Spring 的声明式事务管理器 (`@Transactional`) 只能作用于单一的数据源上。 对于多数据源场景下的事务处理,可以采用以下几种方式来实现[^1]: #### 使用AOP切面编程控制事务传播行为 通过自定义 AOP 切入点,在方法执行前后动态调整当前线程绑定的数据源上下文,并根据业务逻辑需求指定不同的事务隔离级别和传播特性。这种方式灵活性较高,适用于复杂业务流程中的细粒度控制。 ```java import org.aspectj.lang.annotation.Aspect; import com.baomidou.dynamic.datasource.annotation.DS; @Aspect public class DataSourceTransactionAspect { @Around("@annotation(DS)") public Object around(ProceedingJoinPoint point) throws Throwable { try { // 设置目标DS前缀... String result = (String)point.proceed(); // 清理DS后置处理... return result; } catch (Throwable ex){ throw ex; } } } ``` #### 统一配置全局事务管理策略 如果应用程序内所有的服务调用都遵循相同的事务规则,则可以在应用启动类或配置文件中统一设定默认的事务属性,简化开发工作量的同时也提高了系统的可维护性。 ```yaml spring: transaction: default-timeout: 30s isolation-level-for-create: DEFAULT propagation-behavior-name: REQUIRED ``` #### 常见问题及解决方案 - **无法回滚分布式事务** 当涉及两个及以上不同数据库之间的交互时,可能会遇到提交失败但未能成功回滚的情况。此时建议引入支持XA协议的JTA(Java Transaction API),借助 Atomikos 或 Bitronix 等开源组件完成强一致性保障[^2]。 - **读写分离模式下主从库同步延迟引发脏读现象** 对于高并发访问的应用来说,通常会部署一套主备架构以提高可用性和性能表现;然而这也会带来一定的挑战——即由于网络传输等因素造成副本间存在短暂的时间差。针对此类情况,一方面要优化SQL语句减少不必要的锁竞争,另一方面则可通过增加版本号字段辅助判断记录状态变化从而规避潜在风险。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值