1 事务的概念

  在了解spring事务之间,先需要了解数据库的事务。这里介绍下mysql事务

  

2 spring在事务中扮演的角色

  spring它是一个管理者,通过对ORM框架和数据库连接池的管理来管理事务。

  在数据库中,一个事务的完整过程,需要创建连接,开启事务,执行一条或多条sql,提交或者回滚,关闭连接。

  如果一个方法需要spring进行事务管理。当这个方法被调用的时候,先会通过事务管理器获取一个连接,并且开启事务,通过crm框架执行sql,执行完之后,回滚或者提交,而这个连接交由事务同步管理器处理。

 

3 spring使用事务编码方式分类

  Spring 支持两种事务方式,分别是编程式事务和声明式事务。

 

3.1 编程式

  编程式事务是指将事务管理代码嵌入嵌入到业务代码中,来控制事务的提交和回滚

  就编程式事务管理而言,Spring 更推荐使用 TransactionTemplate。
  在编程式事务中,必须在每个业务操作中包含额外的事务管理代码,就导致代码看起来非常的臃肿,但对理解 Spring 的事务管理模型非常有帮助

 

3.2 声明式

  注解方式,加一个注解即可@Transactional

  声明式事务将事务管理代码从业务方法中抽离了出来,以声明式的方式来实现事务管理,对于开发者来说,声明式事务显然比编程式事务更易用、更好用。
  当然了,要想实现事务管理和业务代码的抽离,就必须得用到 Spring 当中的AOP,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。

  声明式事务虽然优于编程式事务,但也有不足,声明式事务管理的粒度是方法级别,而编程式事务是可以精确到代码块级别的

4 spring的事务管理模型

4.1 简介

  Spring 将事务管理的核心抽象为一个事务管理器(TransactionManager),它的源码只有一个简单的接口定义,属于一个标记接口

public interface TransactionManager {

}

  该接口有两个子接口,分别是编程式事务接口 ReactiveTransactionManager 和声明式事务接口 PlatformTransactionManager。我们来重点说说 PlatformTransactionManager,该接口定义了 3 个接口方法

 

4.2 声明式事务接口PlatformTransactionManager

1)PlatformTransactionManager源码

interface PlatformTransactionManager extends TransactionManager{
    
  // 根据事务定义获取事务状态 TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; // 提交事务 void commit(TransactionStatus status) throws TransactionException; // 事务回滚 void rollback(TransactionStatus status) throws TransactionException; }

  通过 PlatformTransactionManager 这个接口,Spring 为各个平台如 JDBC(DataSourceTransactionManager)、Hibernate(HibernateTransactionManager)、JPA(JpaTransactionManager)等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。

 

2)TransactionDefinition

  getTransaction方法中的参数TransactionDefinition和@Transactional 注解是对应的

  比如说 @Transactional 注解中定义的事务传播行为、隔离级别、事务超时时间、事务是否只读等属性,在 TransactionDefinition 都可以找得到

 

3)TransactionStatus

  getTransaction方法中的返回值TransactionStatus,主要用来存储当前事务的一些状态和数据,比如说事务资源(connection)、回滚状态等。

 

4)TransactionDefinition部分源码

public interface TransactionDefinition {

 // 事务的传播行为
 default int getPropagationBehavior() {
  return PROPAGATION_REQUIRED;
 }

 // 事务的隔离级别
 default int getIsolationLevel() {
  return ISOLATION_DEFAULT;
 }

  // 事务超时时间
  default int getTimeout() {
  return TIMEOUT_DEFAULT;
 }

  // 事务是否只读
  default boolean isReadOnly() {
  return false;
 }
}

 

5)@Transactional注解部分源码

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

 Propagation propagation() default Propagation.REQUIRED;
 Isolation isolation() default Isolation.DEFAULT;
  int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
  boolean readOnly() default false;

}

 

5 事务传播行为

5.1 简介

  当事务方法被另外一个事务方法调用时,必须指定事务应该如何传播,例如,方法可能继续在当前事务中执行,也可以开启一个新的事务,在自己的事务中执行。
  声明式事务的传播行为可以通过 @Transactional 注解中的 propagation 属性来定义

@Transactional(propagation = Propagation.REQUIRED)
public void savePosts(PostsParam postsParam) {
}

  TransactionDefinition 一共定义了 7 种事务传播行为,其中PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW 两种传播行为是比较常用的

 

5.2 PROPAGATION_REQUIRED(默认)

  这是 @Transactional 默认的事务传播行为,指的是如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  如果外部方法没有开启事务的话,Propagation.REQUIRED 修饰的内部方法会开启自己的事务,且开启的事务相互独立,互不干扰。
  如果外部方法开启事务并且是 Propagation.REQUIRED 的话,所有 Propagation.REQUIRED 修饰的内部方法和外部方法均属于同一事务 ,只要一个方法回滚,整个事务都需要回滚。

  也就是说如果a方法和b方法都添加了注解,在默认传播模式下,a方法内部调用b方法,会把两个方法的事务合并为一个事务。

 

5.3 PROPAGATION_REQUIRES_NEW

  创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部方法都会开启自己的事务,且开启的事务与外部的事务相互独立,互不干扰。

  当类A中的 a 方法用默认 Propagation.REQUIRED模式,类B中的 b方法加上采用 Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 b方法操作数据库,然而 a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停 a方法的事务 ,总结就是a不影响b,b影响a

 

5.4 PROPAGATION_NESTED

  如果当前存在事务,就在当前事务内执行;否则,就执行与 PROPAGATION_REQUIRED 类似的操作。

 

  PROPAGATION_NESTED 开始一个 "嵌套的" 事务, 它是已经存在事务的一个真正的子事务. 嵌套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交

 

 

 

5.5 PROPAGATION_SUPPORTS

  如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

 

5.6 PROPAGATION_NOT_SUPPORTED

  以非事务方式运行,如果当前存在事务,则把当前事务挂起。

 

5.7 PROPAGATION_MANDATORY

  如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

 

5.8 PROPAGATION_NEVER

  以非事务方式运行,如果当前存在事务,则抛出异常

 

6 spring事务失效的创建原因

6.1 数据库引擎不支持事务

  从 MySQL 5.5.5 开始的默认存储引擎是:InnoDB,之前默认的都是:MyISAM,所以这点要值得注意,底层引擎不支持事务再怎么搞都是白搭。

  

6.2 没有被 Spring 管理

// @Service
public class OrderServiceImpl implements OrderService {

    @Transactional
    public void updateOrder(Order order) {
        // update order
    }
    
}

  如果此时把 @Service 注解注释掉,这个类就不会被加载成一个 Bean,那这个类就不会被 Spring 管理了,事务自然就失效了

 

6.3方法不是 public 的

  @Transactional 只能用于 public 的方法上,否则事务会失效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。

 

6.4 自身调用问题

1)示例1

@Service
public class OrderServiceImpl implements OrderService {

    public void update(Order order) {
        updateOrder(order);
    }
    
    @Transactional
    public void updateOrder(Order order) {
        // update order
    }
    
}

  update方法上面没有加 @Transactional 注解,调用有 @Transactional 注解的 updateOrder 方法,updateOrder 方法上的事务不管用

 

2)示例2

@Service
public class OrderServiceImpl implements OrderService {

    @Transactional
    public void update(Order order) {
        updateOrder(order);
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void updateOrder(Order order) {
        // update order
    }
    
}

  这次在 update 方法上加了 @Transactional,updateOrder 加了 REQUIRES_NEW 新开启一个事务,那么新开的事务不管用

  因为它们发生了自身调用,实际上通过的是this去调用的该类自己的方法,而没有经过 Spring 的代理类,而spring是通过代理来实现事务的。

  这个的解决方案之一就是在的类中注入自己,用注入的对象再调用另外一个方法,这个不太优雅,另外一个可行的方案可以参考《Spring 如何在一个事务中开启另一个事务?》

6.5 数据源没有配置事务管理器

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

  如上面所示,当前数据源若没有配置事务管理器,那也是白搭

 

6.6 不支持事务

@Service
public class OrderServiceImpl implements OrderService {

    @Transactional
    public void update(Order order) {
        updateOrder(order);
    }
    
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void updateOrder(Order order) {
        // update order
    }
    
}

  Propagation.NOT_SUPPORTED: 表示不以事务运行,当前若存在事务则挂起,详细的可以参考《事务隔离级别和传播机制》这篇文章。

  都主动不支持以事务方式运行了,那事务生效也是白搭

 

6.7 异常被吃了try catch

// @Service
public class OrderServiceImpl implements OrderService {

    @Transactional
    public void updateOrder(Order order) {
        try {
            // update order
        } catch {
            
        }
    }
    
}

 

6.8 异常类型不支持

 // @Service
    public class OrderServiceImpl implements OrderService {
    
        @Transactional
        public void updateOrder(Order order) {
            try {
                // update order
            } catch {
                throw new Exception("更新错误");
            }
        }
        
    }

  这样事务也是不生效的,因为默认回滚的异常必须是是RuntimeException

  如果你想触发其他异常的回滚,需要在注解上配置一下。这个配置仅限于 Throwable 异常类及其子类

@Transactional(rollbackFor = Exception.class)

 

6.9 多线程

  两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果想doOtherThing方法中抛了异常,add方法也回滚是不可能的

 

6.10方法用final修饰

  如果某个方法不想被子类重写,这时我们可以将该方法定义成final。普通方法这样定义是没问题的,但如果将事务方法定义成final

  但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法,而无法添加事务功能。

 

6.11 方法用static修饰

  如果某个方法是static的,同样无法通过动态代理,变成事务方法

 

 7 spring事务和多线程

  ThreadLocal是用来处理多线程并发问题的一种解决方案。ThreadLocal是的作用是提供线程的局部变量,在多线程并发环境下,提供了与其他线程隔离的局部变量。通常这样的设计的情况是因为这个局部变量是不适合放在全局变量进行同步处理的。

  比如在事务管理中,在service类中的涉及到事务的方法,每个事务的上下文都应该是独立拥有数据库的connection连接的,否则在数据提交回滚过程中就会产生冲突。

  spring中使用ThreadLocal来设计TransactionSynchronizationManager类,实现了事务管理与数据访问服务的解耦,同时也保证了多线程环境下connection的线程安全问题

  DataSourceTransactionManager的实现中,doBegin()方法开启事务,我们看下它是怎么处理connection资源的。

  首先从数据库连接池中获得一个connection,并构造一个connection包装类,使用这个包装类开启事务,最后通过TransactionSynchronizationManager将connection与ThreadLocal绑定,事务提交或者回滚后,解除绑定。

  TransactionSynchronizationManager中bindResource()的实现

   Resource就是ThreadLocal,而这里的Map就是ThreadLocalMap的value,与当前线程关联的ThreadLocal的值