1. Spring事务的基本原理

事务管理是应用系统开发中必不可少的一部分。Spring 为事务管理提供了丰富的功能支持。Spring 事务管理分为编码式和声明式的两种方式。编程式事务指的是通过编码方式实现事务;声明式事务基于 AOP,将具体业务逻辑与事务处理解耦。声明式事务管理使业务代码逻辑不受污染, 因此在实际使用中声明式事务用的比较多。声明式事务有两种方式,一种是在配置文件(xml)中做相关的事务规则声明,另一种是基于@Transactional 注解的方式。注释配置是目前流行的使用方式,因此本文将着重介绍基于@Transactional 注解的事务管理。
使用@Transactional的相比传统的我们需要手动开启事务,然后提交事务来说。它提供如下方便

  • 根据你的配置,设置是否自动开启事务
  • 自动提交事务或者遇到异常自动回滚

声明式事务(@Transactional)基本原理如下:

  1. 配置文件开启注解驱动,在相关的类和方法上通过注解@Transactional标识。
  2. spring 在启动的时候会去解析生成相关的bean,这时候会查看拥有相关注解的类和方法,并且为这些类和方法生成代理,并根据@Transaction的相关参数进行相关配置注入,这样就在代理中为我们把相关的事务处理掉了(开启正常提交事务,异常回滚事务)。
  3. 真正的数据库层的事务提交和回滚是通过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()表示事务的传播属性
    事务的传播是指事务的嵌套的时候,它们的事务属性。常见的传播属性有如下几个
  1. 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()都会在事务中。

  2. 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() 已经提交的事务是不会回滚的。

  3. 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在目标方法执行前后进行拦截之前,DynamicAdvisedInterceptorCglibAopProxy的内部类)的的 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();
}
}

另外也可以把注解加到类上来解决。

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
來源:简书

transaction注解分析的更多相关文章

  1. @Tranactional 注解分析

    Spring可以通过注解@Transactional来为业务逻辑层的方法(调用DAO完成持久化动作)添加事务能力,如下是@Transactional注解的定义 @Tranactional注解分析 作用 ...

  2. DispatcherServlet源码注解分析

    DispatcherServlet的介绍与工作流程 DispatcherServlet是SpringMVC的前端分发控制器,用于处理客户端请求,然后交给对应的handler进行处理,返回对应的模型和视 ...

  3. SpringBoot系列三:SpringBoot基本概念(统一父 pom 管理、SpringBoot 代码测试、启动注解分析、配置访问路径、使用内置对象、项目打包发布)

    声明:本文来源于MLDN培训视频的课堂笔记,写在这里只是为了方便查阅. 1.了解SpringBoot的基本概念 2.具体内容 在之前所建立的 SpringBoot 项目只是根据官方文档实现的一个基础程 ...

  4. Spring @Transaction 注解是如何执行事务的?

    前言 相信小伙伴一定用过 @Transaction 注解,那 @Transaction 背后的秘密又知道多少呢? Spring 是如何开启事务的?又是如何进行提交事务和关闭事务的呢? 画图猜测 在开始 ...

  5. Spring Boot(四)@EnableXXX注解分析

    在学习使用springboot过程中,我们经常碰到以@Enable开头的注解,其实早在Spring3中就已经出现了类似注解,比如@EnableTransactionManagement.@ Enabl ...

  6. @SpringBootApplication注解分析

    首先我们分析的就是入口类Application的启动注解@SpringBootApplication,进入源码: @Target(ElementType.TYPE) @Retention(Retent ...

  7. SpringMVC中 Controller的 @ResponseBody注解分析

    需求分析:需要 利用    out 对象返回给财付通是否接收成功 .那么将需要如下代码: /** * 返回处理结果给财付通服务器. * @param msg: Success or fail. * @ ...

  8. SpringBoot注解分析解释

    使用注解的优势: 1.采用纯java代码,不在需要配置繁杂的xml文件 2.在配置中也可享受面向对象带来的好处 3.类型安全对重构可以提供良好的支持 4.减少复杂配置文件的同时亦能享受到springI ...

  9. Spring事务注解分析

    1.使用spring事务注解 2.手写事务注解 1).sql执行器 2).事务注解定义 3).AOP实现事务具体实现(同一个线程中使用同一个连接) 4).应用使用注解前 5).应用使用注解后

随机推荐

  1. Easy-to-Learn English Travel Phrases and Vocabulary!

    Easy-to-Learn English Travel Phrases and Vocabulary! Share Tweet Share Tagged With: Real Life Englis ...

  2. MTIM(中间人攻击)

    所谓的MITM攻击就是通过拦截正常的网络通信数据,并进行数据篡改和嗅探,而通信的双方却毫不知情. 信息篡改 当主机A.和主机B通信时,都由主机C来为其“转发”,如图一,而A.B之间并没有真正意思上的直 ...

  3. GDI+ 实现透明水印和文字

    最近给<JPEG浏览缩放器>增加了水印功能,在设计的过程中,参考了网上的文章,但是发现文章使用的GDI+ API封装包不是我现在使用的那一套,目前DELPHI使用的GDI+ API封装包有 ...

  4. 20.struts2的数据填充和类型转换.md

    目录 1. struts2的自动填充 2. struts2的对象填充 3. struts2的类型转换器 3.1 类继承关系 3.2 局部转换器 3.3 全局转换器 3.4 注意 1. struts2的 ...

  5. Hibernate 再接触 组件映射

    将另外一个类嵌入到另外一个类 从而合并生成一张表 Husband.java package com.bjsxt.hibernate; import javax.persistence.Embedded ...

  6. C#导出Excel,某单元格内容长度超过255 的解决方法

    public static void ToExcel(DataTable dtSource, string strPath, string strSheetName) { System.Data.Ol ...

  7. 吴裕雄 python 数据处理(3)

    import time a = time.time()print(a)b = time.localtime()print(b)c = time.strftime("%Y-%m-%d %X&q ...

  8. linux配置虚拟主机

    linux 下怎么配置虚拟主机 linux 下怎么配置虚拟主机,在网上找到N个资料都是高手们随便说几句,都没怎么说清楚.问题:  我把域名(bs.jxiop.com)指向了 68.10.140.10 ...

  9. Shader基础(固定管线着色器)

    在Shader的编码中,要养成不加空格的习惯,否则会有时候出现一些错误 固定管线着色器: 优点:实现简单 缺点:处理的效果比较差 //设置Shader的路径 Shader "MyFixedS ...

  10. 【selenium+python】关于使用selenium时的几个问题1

    问题:selenium.common.exceptions.WebDriverException: Message: 'chromedriver' executable needs to be in ...