Spring事务(Transaction)管理高级篇一栈式解决开发中遇到的事务问题

时间:2023-02-18 22:06:52

Spring是目前Java开发中最流行的框架了,它的事务管理我们在开发中常常的用到,但是很多人不理解它事务的原理,导致开发中遇到事务方面的问题往往都要用很长的时间才能解决,下面就带着大家去深入了解Spring的事务,然后文章的最后还会给出开发中常常遇到的问题以及解决方案。

如果单纯的用Spring框架进行开发(PS使用注解开发,不是XML配置的方式)。那么要使用Spring事物我们首先要加的就是Spring的这个【EnableTransactionManagement】注解(PS如果直接使用了Spingboot框架了,它已经使用自动配置相关的原理自动加了这个注解了)。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {

上面注解的主要作用就是帮我们Import了TransactionManagementConfigurationSelector这个类,它的主要功能如下:

Spring事务(Transaction)管理高级篇一栈式解决开发中遇到的事务问题

这个类目前就是帮我们在Spring中注入了ProxyTransactionManagementConfiguration,大家一看名称就知道这是一个配置类(配置类一般都是帮我们注入各种需要的Bean)如下它帮我们做的事情。

  @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
  @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {
    BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
    advisor.setTransactionAttributeSource(transactionAttributeSource());
    advisor.setAdvice(transactionInterceptor());
    return advisor;
  }

  @Bean
  @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  public TransactionAttributeSource transactionAttributeSource() {
    return new AnnotationTransactionAttributeSource();
  }

  @Bean
  @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  public TransactionInterceptor transactionInterceptor() {
    TransactionInterceptor interceptor = new TransactionInterceptor();
    interceptor.setTransactionAttributeSource(transactionAttributeSource());
    return interceptor;
  }

上面的代码可以看出它帮我们做了三件事,1:导入了一个增强器。2:导入了一个事务属性的资源。3:导入一个事务的拦截器。这三个类后面讲事务的时候都会用到。

如果方法上加了事物注解的类,Spring会创建它的一个代理类放到容器中,如果没加注解(PS本文只考虑事物注解不考虑其他的)那么Spring就会把它原始的对象放到容器中这个很重要,后面总结事物为什么会失效。(下面第一张图没加Transaction注解的对象,第二张图是加了的,然后Sping就把它转换为代理对象,具体怎么转换的是AOP相关功能,AOP很多功能点本文不做具体讲解)【上面说的一段话很是重要】。

 Spring事务(Transaction)管理高级篇一栈式解决开发中遇到的事务问题

Spring事务(Transaction)管理高级篇一栈式解决开发中遇到的事务问题

重点来了,下面是我UserService中的主要的方法,很简单如下:

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao ;
    @Autowired
    private ApplicationContext applicationContext ;
    @Override
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void addUser(User user) {
        user.setPersonName("xiaozhang");
        userDao.insert(user) ;
        user.setPersonName("xiaozhang02");
        UserService userService = applicationContext.getBean(UserService.class);
        userService.addUser02(user);
        int i= 1/0 ;  //让程序抛出异常

    }
    @Override
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
    public void addUser02(User user02) {
        userDao.insert(user02) ;
    }
}

然后就开始带着大家看相关的源码,首先打断点会进入到如下的方法(你如果想尝试就在这个类【TransactionAspectSupport】的如下方法中打一个断点)。

 Spring事务(Transaction)管理高级篇一栈式解决开发中遇到的事务问题

分析上面代码主要功能。

--1:这段代码是获取事务的属性,上面配置中配置了
final TransactionAttribute txAttr = (tas != null ? 
tas.getTransactionAttribute(method, targetClass) : null);
--2:决定使用那个事务管理器
final PlatformTransactionManager tm = 
determineTransactionManager(txAttr);
--3:找方法的切入点也就是加了事务注解的方法
final String joinpointIdentification = 
methodIdentification(method, targetClass, txAttr);

上面图中的三个方法就是注释中写的作用,后面就是事务的重点了,然后会走下面的方法。

Spring事务(Transaction)管理高级篇一栈式解决开发中遇到的事务问题

protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
  @Nullable TransactionAttribute txAttr,  final String joinpointIdentification) {
    TransactionStatus status = null;
    if (txAttr != null) {
      if (tm != null) {
        status = tm.getTransaction(txAttr); //这个重点方法
      }

tm.getTransaction(txAttr),这个方法会调用如下方法:

//AbstractPlatformTransactionManager这个类的方法
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
  //获取事务管理器,第一次获取的为空
    Object transaction = doGetTransaction();
   //看看是否存在事务,第一次不存在。第二次会进入到这个方法,后面会重点讲。
    if (isExistingTransaction(transaction)) {
      // Existing transaction found -> check propagation behavior to find out how to behave.
      return handleExistingTransaction(definition, transaction, debugEnabled);
    }
    //我们addUser的方法事务传播行为是PROPAGATION_REQUIRED,所以走这个分支
    else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
        definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
        definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
      //挂起事务,后面PROPAGATION_REQUIRES_NEW这个方法很重要
      SuspendedResourcesHolder suspendedResources = suspend(null);
      try {
        boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
       //创建一个事务状态
        DefaultTransactionStatus status = newTransactionStatus(
            definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
       //重点来了,开启事务
        doBegin(transaction, definition);
        prepareSynchronization(status, definition);
        return status;
      }

开启事务方法(doBegin)(这个方法做了如下6件很重要的事情)

protected void doBegin(Object transaction, TransactionDefinition definition) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
   //1:获取事务连接,我们写JDBC的时候也是需要自己获取事务连接的
    Connection con = null;
      if (!txObject.hasConnectionHolder() ||
          txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
        Connection newCon = obtainDataSource().getConnection();
        txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
      }
      //2:设置数据库的隔离级别
      Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
      txObject.setPreviousIsolationLevel(previousIsolationLevel);
       //3:关闭数据库的自动提交
      // Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
      // so we don't want to do it unnecessarily (for example if we've explicitly
      // configured the connection pool to set it already).
      if (con.getAutoCommit()) {
        txObject.setMustRestoreAutoCommit(true);
        con.setAutoCommit(false);
      }
      prepareTransactionalConnection(con, definition);
      // 4:激活事务
      txObject.getConnectionHolder().setTransactionActive(true);
      // 5:设置超时的时间
      int timeout = determineTimeout(definition);
      if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
        txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
      }
       //6:绑定数据源和连接持有器。
      // Bind the connection holder to the thread.
      if (txObject.isNewConnectionHolder()) {
        TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
      }
    }

上面方法完成后,开始执行下面这个方法:

 try {
        这个方法的主要意思是去执行目标方法,也就是(addUser)
        // This is an around advice: 
        //Invoke the next interceptor in the chain.
        // This will normally result in a target object being invoked.
        retVal = invocation.proceedWithInvocation();
      }

去执行目标方法(addUser)后,就又回到前面那个第一次进入Spring源码中的方法(因为从上面的代码可知我们的addUser方法又调用了其他事务方法)如下图:

 Spring事务(Transaction)管理高级篇一栈式解决开发中遇到的事务问题

注意此时的addUser()方法还没执行完,又进入同样的方法如下:

Spring事务(Transaction)管理高级篇一栈式解决开发中遇到的事务问题

上面很多重复的就不讲了,前面由于已经存在了事务所以会进入如下方法。

//前面说要重点说的
private TransactionStatus handleExistingTransaction(
      TransactionDefinition definition, Object transaction, boolean debugEnabled)
      throws TransactionException {
  //不允许有事务就抛出异常 
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
      throw new IllegalTransactionStateException(
          "Existing transaction found for transaction marked with propagation 'never'");
    }
    //重点也是我们开发中常用的事务传播行为(新建一个事务) 
    // PROPAGATION_REQUIRES_NEW
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
      //先把旧的事务挂起,并且缓存起来,
      // 怎么缓存旧的事务的自己可走进去看一下,因为前面事务还没提交,还需要用
      SuspendedResourcesHolder suspendedResources = suspend(transaction);
      try {
        boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
        DefaultTransactionStatus status = newTransactionStatus(
            definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
       //又开启新的事务了,和上面讲过的同样的套路了
        doBegin(transaction, definition);
        prepareSynchronization(status, definition);
        return status;
      }

后面就和前面一样了流程。最后数据库结果如下(PS忽略我的数据库表设计,只是为了简单,用的Mysql数据库也是为了自己电脑方便安装)。

 Spring事务(Transaction)管理高级篇一栈式解决开发中遇到的事务问题

结果addUser插入失败了,addUser02插入成功了。(这也就是事务传播属性,Propagation.REQUIRED和Propagation.REQUIRES_NEW的区别)。

好了我们进行总结在工作中事务方面的问题。

1:事务注解使用不当,造成事务不生效(PS只举列开发中常用的)。

1.1 :没有用代理对象调用,直接用原始对象(文章开始说了加了事务注解的会生成一个代理对象),如下的使用事务会失效(也是大多数开发者常犯的错误)。

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao ;
    @Autowired
    private ApplicationContext applicationContext ;
    @Override
    public void addUser(User user) {
        user.setPersonName("xiaozhang");
        userDao.insert(user) ;
        user.setPersonName("xiaozhang03");
        //用this没有AOP代理对象,造成事务失效,2个user都会插入数据库
        this.addUser02(user) ;
    }
    @Override
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void addUser02(User user02) {
        userDao.insert(user02) ;
        int i= 1/0 ;  //让程序抛出异常
    }
}

1.2 rollbackFor = Exception.class如果不加,Spring只回滚运行时候的异常和程序中的Error(这是很细节的代码) 。

Spring事务(Transaction)管理高级篇一栈式解决开发中遇到的事务问题

上面2个问题解决方案大家看完也就很明白怎样解决了。

2:事务嵌套使用不当导致事务都回滚了,如下有时候会写下面的业务。

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao ;
    @Autowired
    private ApplicationContext applicationContext ;
    @Override
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void addUser(User user) {
        user.setPersonName("xiaozhang");
        userDao.insert(user) ;
        user.setPersonName("xiaozhang02");
        UserServiceImpl userService = (UserServiceImpl)applicationContext.getBean(UserService.class);
        try {
            userService.addUser02(user);
        }catch (Exception e){
            System.out.println(e);
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void addUser02(User user02) {
        userDao.insert(user02) ;
        int i= 1/0 ;  //让程序抛出异常
    }
}

很多时候我们一看代码也没什么问题addUser()这个方法会新增数据的,因为把addUser02()方法抛出的异常捕获了。开发的时候(PS开发的时候调用的其他服务的业务方法,我为了演示简单这样写了)我遇到这方面的问题找了很久才找到原因。会抛出如下异常:

Spring事务(Transaction)管理高级篇一栈式解决开发中遇到的事务问题

然后我们拿Spring源码分析,上面写的两个事务传播属性都是propagation = Propagation.REQUIRED (PS如果存在事务就使用原来的事务,不存在就新启一个事务)。

简单的说一下为什么会出现上面的原因,addUser02()这个方法抛出异常了它会把一个全局的rollback-only变量标记为true(默认为false)。由于它和addUser()方法共用事务,所以后面这个方法事务提交的时候会检查这个变量,如果看到这个变量为true。就把它自己也回滚了。如下:

Spring事务(Transaction)管理高级篇一栈式解决开发中遇到的事务问题

下面addUser()方法要进行提交事务了如下:

Spring事务(Transaction)管理高级篇一栈式解决开发中遇到的事务问题

然后会进入AbstractPlatformTransactionManager这个类的commit方法如下图。

 Spring事务(Transaction)管理高级篇一栈式解决开发中遇到的事务问题

然后会进入processRollback这个方法,为什么会进入这个方法还是因为rollback-only这个变量上一步设置为true了。(图片中可以看到是那个类的这个方法,自己Debug打断点的时候可参考),这个就是我们前面图中抛出的异常(Transaction rolled back because it has been marked as rollback-only)。

 private void processRollback(DefaultTransactionStatus status, boolean unexpected){
  try {
    //unexpected 为true 。
      boolean unexpectedRollback = unexpected;
          // Unexpected rollback only matters here if we're asked to fail early
         //这个方法不会进入
          if (!isFailEarlyOnGlobalRollbackOnly()) { 
            unexpectedRollback = false;
          }
        }
      }
      triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
      // Raise UnexpectedRollbackException if we had a global rollback-only marker
      // unexpectedRollback 为true 就会抛出开始说的那个异常。
      if (unexpectedRollback) {
        throw new UnexpectedRollbackException(
            "Transaction rolled back because it has been marked as rollback-only");
      }
    }

遇到上面的这个问题怎么解决的?

Spring事务(Transaction)管理高级篇一栈式解决开发中遇到的事务问题

1:addUser02()这个方法新建一个事物也就是使用事物传播属性propagation = Propagation.REQUIRES_NEW(PS如果你写的业务逻辑可以这样做)。

2:不用Transaction这个事物注解,事物的提交和回滚自己控制,Sping提供的有事物手动提交和回滚的方法,自己可以查找一下。

文章中很多地方我都标注了那个类的那个方法,跟着上面一步步看完源码,事物方面开发中如果再遇到问题应该很快就能解决,事物那些传播属性也很快就能理解,开发中用嵌套事物也不必太担心有Bug了。