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
.常用的有如下的事务管理器
![](http://upload-images.jianshu.io/upload_images/5281821-51bcc971dd9f9374.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/561/format/webp)
isolation()
表示隔离级别
![](http://upload-images.jianshu.io/upload_images/5281821-753c25c5309dec2d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/554/format/webp)
脏读:一事务对数据进行了增删改,但未提交,另一事务可以读取到未提交的数据。如果第一个事务这时候回滚了,那么第二个事务就读到了脏数据。
不可重复读:一个事务中发生了两次读操作,第一次读操作和第二次操作之间,另外一个事务对数据进行了修改,这时候两次读取的数据是不一致的。
幻读:第一个事务对一定范围的数据进行批量修改,第二个事务在这个范围增加一条数据,这时候第一个事务就会丢失对新增数据的修改。
不可重复读的重点是修改 :同样的条件, 你读取过的数据,再次读取出来发现值不一样了幻读的重点在于新增或者删除:同样的条件, 第 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 来拦截该目标方法进行事务管理。整个事务执行的时序图如下。
![](http://upload-images.jianshu.io/upload_images/5281821-fecd22fffdc8d181.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000/format/webp)
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();
}
}
另外也可以把注解加到类上来解决。
3.4注意事项
@Transactional
注解不支持多数据源的情况- 如果存在多个数据源且未指定具体的事务管理器,那么实际上启用的事务管理器是最先在配置文件中指定的(即先加载的)
- Spring使用声明式事务处理,默认情况下,如果被注解的数据库操作方法中发生了unchecked异常,所有的数据库操作将rollback;如果发生的异常是checked异常,默认情况下数据库操作还是会提交的。
4.spring事务回滚规则
指示spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。
默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。
可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。
还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。
4. 总结
@Transactional
用起来是方便,但是我们需要明白它背后的原理,避免入坑。另外@Transactional
不建议用在处理时间过长的事务。因为,它会一直持有数据库线程池的连接,造成不能及时返回。就是尽量是的事务的处理时间短。
参考文章:
Spring @Transactional的使用及原理
深入理解 Spring 事务原理
透彻的掌握 Spring 中@transactional 的使用
在同一个类中,一个方法调用另外一个有注解(比如@Async,@Transational)的方法,注解失效的原因和解决方法
https://www.cnblogs.com/yepei/p/4716112.html
作者:maskwang520
链接:https://www.jianshu.com/p/5687e2a38fbc
來源:简书