1. Spring 事务的基本原理
事务管理是应用系统开发中必不可少的一部分. Spring 为事务管理提供了丰富的功能支持. Spring 事务管理分为编码式和声明式的两种方式. 编程式事务指的是通过编码方式实现事务; 声明式事务基于 AOP, 将具体业务逻辑与事务处理解耦. 声明式事务管理使业务代码逻辑不受污染, 因此在实际使用中声明式事务用的比较多. 声明式事务有两种方式, 一种是在配置文件 (xml) 中做相关的事务规则声明, 另一种是基于 @Transactional 注解的方式. 注释配置是目前流行的使用方式, 因此本文将着重介绍基于 @Transactional 注解的事务管理.
使用 @Transactional 的相比传统的我们需要手动开启事务, 然后提交事务来说. 它提供如下方便
根据你的配置, 设置是否自动开启事务
自动提交事务或者遇到异常自动回滚
声明式事务 (@Transactional) 基本原理如下:
配置文件开启注解驱动, 在相关的类和方法上通过注解 @Transactional 标识.
spring 在启动的时候会去解析生成相关的 bean, 这时候会查看拥有相关注解的类和方法, 并且为这些类和方法生成代理, 并根据 @Transaction 的相关参数进行相关配置注入, 这样就在代理中为我们把相关的事务处理掉了(开启正常提交事务, 异常回滚事务).
真正的数据库层的事务提交和回滚是通过 binlog 或者 redo log 实现的.
2. @Transactional 基本配置解析
- @Transactional
- public void saveUser(){
- User user = new User();
- user.setAge(22);
- user.setName("maskwang");
- logger.info("save the user{}",user);
- userRepository.save(user);
- }
如上面这个例子一样, 很轻松的就能应用事务. 只需要在方法上加入 @Transactional 注解. 当 @Transactional 加在方法上, 表示对该方法应用事务. 当加在类上, 表示对该类里面所有的方法都应用相同配置的事务. 接下来对 @Transactional 的参数解析.
- public @interface Transactional {
- @AliasFor("transactionManager")
- String value() default "";
- @AliasFor("value")
- String transactionManager() default "";
- Propagation propagation() default Propagation.REQUIRED;
- Isolation isolation() default Isolation.DEFAULT;
- int timeout() default -1;
- boolean readOnly() default false;
- Class<? extends Throwable>[] rollbackFor() default {};
- String[] rollbackForClassName() default {};
- Class<? extends Throwable>[] noRollbackFor() default {};
- String[] noRollbackForClassName() default {};
- }
- transactionManager()
表示应用那个应用那个 TransactionManager. 常用的有如下的事务管理器
image.png
isolation()表示隔离级别
image.png
脏读: 一事务对数据进行了增删改, 但未提交, 另一事务可以读取到未提交的数据. 如果第一个事务这时候回滚了, 那么第二个事务就读到了脏数据.
不可重复读: 一个事务中发生了两次读操作, 第一次读操作和第二次操作之间, 另外一个事务对数据进行了修改, 这时候两次读取的数据是不一致的.
幻读: 第一个事务对一定范围的数据进行批量修改, 第二个事务在这个范围增加一条数据, 这时候第一个事务就会丢失对新增数据的修改.
不可重复读的重点是修改 : 同样的条件, 你读取过的数据, 再次读取出来发现值不一样了幻读的重点在于新增或者删除: 同样的条件, 第 1 次和第 2 次读出来的记录数不一样
propagation()表示事务的传播属性
事务的传播是指事务的嵌套的时候, 它们的事务属性. 常见的传播属性有如下几个
- PROPAGATION_REQUIRED
- (spring 默认)
假设外层事务 Service A 的 Method A() 调用 内层 Service B 的 Method B(). 如果 ServiceB.methodB() 的事务级别定义为 PROPAGATION_REQUIRED, 那么执行 ServiceA.methodA() 的时候 spring 已经起了事务, 这时调用 ServiceB.methodB(),ServiceB.methodB() 看到自己已经运行在 ServiceA.methodA() 的事务内部, 就不再起新的事务. 假如 ServiceB.methodB() 运行的时候发现自己没有在事务中, 他就会为自己分配一个事务. 不管如何, ServiceB.methodB()都会在事务中.
PROPAGATION_REQUIRES_NEW
比如我们设计 ServiceA.methodA() 的事务级别为 PROPAGATION_REQUIRED,ServiceB.methodB() 的事务级别为 PROPAGATION_REQUIRES_NEW. 那么当执行到 ServiceB.methodB() 的时候, ServiceA.methodA() 所在的事务就会挂起, ServiceB.methodB() 会起一个新的事务, 等待 ServiceB.methodB() 的事务完成以后, 它才继续执行. 它与 1 中的区别在于 ServiceB.methodB() 新起了一个事务. 如过 ServiceA.methodA() 发生异常, ServiceB.methodB() 已经提交的事务是不会回滚的.
PROPAGATION_SUPPORTS
假设 ServiceB.methodB() 的事务级别为 PROPAGATION_SUPPORTS, 那么当执行到 ServiceB.methodB()时, 如果发现 ServiceA.methodA()已经开启了一个事务, 则加入当前的事务, 如果发现 ServiceA.methodA()没有开启事务, 则自己也不开启事务. 这种时候, 内部方法的事务性完全依赖于最外层的事务.
剩下几种就不多介绍, 可以参考这篇文章. https://www.jianshu.com/p/249f2cd42692
readOnly() 事务超时设置. 超过这个时间, 发生回滚
readOnly() 只读事务
从这一点设置的时间点开始 (时间点 a) 到这个事务结束的过程中, 其他事务所提交的数据, 该事务将看不见!(查询中不会出现别人在时间点 a 之后提交的数据).
注意是一次执行多次查询来统计某些信息, 这时为了保证数据整体的一致性, 要用只读事务
rollbackFor()导致事务回滚的异常类数组.
rollbackForClassName()
导致事务回滚的异常类名字数组
noRollbackFor 不会导致事务回滚的异常类数组
noRollbackForClassName
不会导致事务回滚的异常类名字数组
3. @Transactional 使用应该注意的地方
3.1 默认情况下, 如果在事务中抛出了未检查异常 (继承自 RuntimeException 的异常) 或者 Error, 则 Spring 将回滚事务; 除此之外, Spring 不会回滚事务. 你如果想要在特定的异常回滚可以考虑 rollbackFor()等属性
3.2 @Transactional 只能应用到 public 方法才有效.
这是因为在使用 Spring AOP 代理时, Spring 会调用
TransactionInterceptor
在目标方法执行前后进行拦截之前,
DynamicAdvisedInterceptor
(CglibAopProxy 的内部类)的的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用
AbstractFallbackTransactionAttributeSource
(Spring 通过这个类获取 @Transactional 注解的事务属性配置属性信息)的
computeTransactionAttribute
方法.
- @Nullable
- protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
- // 这里判断是否是 public 方法
- if(this.allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
- return null;
- }
- // 省略其他代码
若不是 public, 就不会获取 @Transactional 的属性配置信息, 最终会造成不会用 TransactionInterceptor 来拦截该目标方法进行事务管理. 整个事务执行的时序图如下.
image.png
3.3 Spring 的 AOP 的自调用问题
在 Spring 的 AOP 代理下, 只有目标方法由外部调用, 目标方法才由 Spring 生成的代理对象来管理, 这会造成自调用问题. 若同一类中的其他没有 @Transactional 注解的方法内部调用有 @Transactional 注解的方法, 有 @Transactional 注解的方法的事务被忽略, 不会发生回滚. 这个问题是由于 Spring AOP 代理造成的 (如下面代码所示). 之所以没有应用事务, 是因为在内部调用, 而代理后的类(把目标类作为成员变量静态代理) 只是调用成员变量中的对应方法, 自然也就没有 aop 中的 advice, 造成只能调用父类的方法. 另外一个问题是只能应用在 public 方法上. 为解决这两个问题, 使用 AspectJ 取代 Spring AOP 代理.
- @Transactional
- public void saveUser(){
- User user = new User();
- user.setAge(22);
- user.setName("mask");
- logger.info("save the user{}",user);
- userRepository.save(user);
- // throw new RuntimeException("exception");
- }
- public void saveUserBack(){
- saveUser(); // 自调用发生
- }
3.4 自注入解决办法
- @Service
- public class UserService {
- Logger logger = LoggerFactory.getLogger(UserService.class);
- @Autowired
- UserRepository userRepository;
- @Autowired
- UserService userService; // 自注入来解决
- @Transactional
- public void saveUser(){
- User user = new User();
- user.setAge(22);
- user.setName("mask");
- logger.info("save the user{}",user);
- userRepository.save(user);
- // throw new RuntimeException("exception");
- }
- public void saveUserBack(){
- saveUser();
- }
- }
另外也可以把注解加到类上来解决.
4. 总结
@Transactional 用起来是方便, 但是我们需要明白它背后的原理, 避免入坑. 另外 @Transactional 不建议用在处理时间过长的事务. 因为, 它会一直持有数据库线程池的连接, 造成不能及时返回. 就是尽量是的事务的处理时间短.
参考文章:
Spring @Transactional 的使用及原理 https://blog.csdn.net/aichuanwendang/article/details/53306351
深入理解 Spring 事务原理
透彻的掌握 Spring 中 @transactional 的使用 https://www.ibm.com/developerworks/cn/java/j-master-spring-transactional-use/index.html
在同一个类中, 一个方法调用另外一个有注解 (比如 @Async,@Transational) 的方法, 注解失效的原因和解决方法 https://blog.csdn.net/clementad/article/details/47339519
来源: http://www.jianshu.com/p/5687e2a38fbc