spring事务注解@Transactional,你学“废”了吗?

257 阅读5分钟

事务管理一般有编程式和声明式两种,编程式是直接在代码中进行编写事务处理过程,而声明式则是通过注解方式或者是在xml文件中进行配置,相对编程式很方便。

而注解方式通过@Transactional 是常见的。我们可以使用@EnableTransactionManagement 注解来启用事务管理功能,该注解可以加在启动类上或者单独加个配置类来处理。

1、Transactional 注解的属性

  • name 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。
  • propagation 事务的传播行为,默认值为 REQUIRED。
  • isolation 事务的隔离度,默认值采用 DEFAULT。
  • timeout 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
  • read-only 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
  • rollback-for 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。
  • no-rollback- for 抛出 no-rollback-for 指定的异常类型,不回滚事务。

  propagation 属性(事务传播性)

  • REQUIRED 支持当前已经存在的事务,如果还没有事务,就创建一个新事务。
  • MANDATORY 支持当前已经存在的事务,如果还没有事务,就抛出一个异常。
  • NESTED 在当前事务中创建一个嵌套事务,如果还没有事务,那么就简单地创建一个新事务。
  • REQUIRES_NEW 挂起当前事务,创建一个新事务,如果还没有事务,就简单地创建一个新事务。
  • NEVER 强制要求不在事务中运行,如果当前存在一个事务,则抛出异常。
  • NOT_SUPPORTED 强制不在事务中运行,如果当前存在一个事务,则挂起该事务。
  • SUPPORTS 支持当前事务,如果没有事务那么就不在事务中运行。

2、Transactional应用

@Transactional 可以加在方法上,表示对当前方法配置事务也可以添加到类级别上。

也可以添加到类级别上。当把@Transactional 注解放在类级别时,表示所有该类的公共方法都配置相同的事务属性信息。

当类级别配置了@Transactional,方法级别也配置了@Transactional,应用程序会以方法级别的事务属性信息来管理事务,即方法级别的事务属性信息会覆盖类级别的相关配置信息。

3、Transactional工作原理

声明式事务管理包含三个组成部分:

  • 事务的切面
  • 事务管理器
  • EntityManager Proxy本身

事务****的切面

事务的切面是一个“around(环绕)”切面,在注解的业务方法前后都可以被调用。实现切面的具体类是TransactionInterceptor。事务的切面有两个主要职责:

  • 在’before’时,切面提供一个调用点,来决定被调用业务方法应该在正在进行事务的范围内运行,还是开始一个新的独立事务。
  • 在’after’时,切面需要确定事务被提交,回滚或者继续运行。

在’before’时,事务切面自身不包含任何决策逻辑,是否开始新事务的决策委派给事务管理器完成。

事务管理器

事务管理器需要解决下面两个问题:

  • 新的Entity Manager是否应该被创建?
  • 是否应该开始新的事务?

这些需要事务切面’before’逻辑被调用时决定。事务管理器的决策基于以下两点:

  • 事务是否正在进行
  • 事务方法的propagation属性(比如REQUIRES_NEW总要开始新事务)

如果事务管理器确定要创建新事务,那么将:

  • 创建一个新的entity manager
  • entity manager绑定到当前线程
  • 从数据库连接池中获取连接
  • 将连接绑定到当前线程

使用ThreadLocal变量将entity manager和数据库连接都绑定到当前线程。事务运行时他们存储在线程中,当它们不再被使用时,事务管理器决定是否将他们清除。程序的任何部分如果需要当前的entity manager和数据库连接都可以从线程中获取。

EntityManager proxy

当业务方法调用类似**entityManager.persist()**方法时,这不是由entity manager直接调用的,而是业务方法调用代理,因为事物管理器将entity manage绑定到了线程上,代理从线程获取当前的entity manager。

4、附注

  4.1 @Transactional 注解应用到 public 方法,才能进行事务管理。因为aop会进行拦截是否是public方法:

//AbstractFallbackTransactionAttributeSource类      protected TransactionAttribute computeTransactionAttribute(Method method,        Class<?> targetClass) {            // Don't allow no-public methods as required.            if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {            return null;       }  }

  4.2 propagation 属性

  下面三种 propagation 可以不启动事务。错误的配置这三种 propagation,事务可能不会发生回滚。

  • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

  4.3 rollbackFor 属性  

默认情况下,如果在事务中抛出了未检查异常(继承自 RuntimeException 的异常)或者 Error,则 Spring 将回滚事务;除此之外,Spring 不会回滚事务。  可以通过rollbackFor来制定在事物中抛出的其他类型的异常来支持事务回滚,例:

@Transactional(propagation= Propagation.REQUIRED, rollbackFor= MyException.class)

  若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。

  4.4 在默认的代理模式下,只有目标方法由外部调用,才能被 Spring 的事务拦截器拦截。在同一个类中的两个方法直接调用,是不会被 Spring 的事务拦截器拦截

  像如下这种在同一个类中的两个方法上加上事务控制,其中method上的事务是不能生效的,一种方法就是把它写到另一个列中,然后再当前类中调用

@Transactional(propagation = Propagation.REQUIRED)@Overridepublic void save() {    method();    。。。业务处理    if (true) {        throw new RuntimeException("save 抛异常了");    }}@Transactional(propagation = Propagation.REQUIRES_NEW)public void method() {    。。。业务处理}