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


    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中注入了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很多功能点本文不做具体讲解)【上面说的一段话很是重要】。

     

    重点来了,下面是我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】的如下方法中打一个断点)。

     

    分析上面代码主要功能。

    复制代码
    --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);
    复制代码

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

    复制代码
    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方法又调用了其他事务方法)如下图:

     

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

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

    复制代码
    //前面说要重点说的
    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数据库也是为了自己电脑方便安装)。

     

    结果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(这是很细节的代码) 。

    上面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源码分析,上面写的两个事务传播属性都是propagation = Propagation.REQUIRED (PS如果存在事务就使用原来的事务,不存在就新启一个事务)。

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

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

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

     

    然后会进入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");
          }
        }
    复制代码

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

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

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

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

     

  • 相关阅读:
    游戏安全03:缓冲区溢出攻击简单解释
    是不是Jenkins大神,看这几个技巧就够
    JS 原生实现触底加载
    【创建VUE3新项目】从零开始如何创建一个干净的vue3项目【基于前端详细介绍】
    体现技术深度(无法速成)
    镜像方式如何部署项目
    【开发教程10】疯壳·开源蓝牙心率防水运动手环-蓝牙 BLE 收发
    鸿蒙入门12-Divider
    wazuh运营使用
    天猫用户重复购买预测(速通二)
  • 原文地址:https://www.cnblogs.com/scott1102/p/17133611.html