zoukankan      html  css  js  c++  java
  • spring事务失效的12种场景

                                                                      一 事务不生效

    1.访问权限问题

    java的访问权限主要有四种:private<default<protected<public。

    把有某些事务方法,定义了错误的访问权限,就会导致事务功能出问题:

     @Service
    public class UserService {
        
        @Transactional
        private void add(UserModel userModel) {
             saveData(userModel);
             updateData(userModel);
        }
    }

    spring要求被代理方法必须是public

    AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法中有个判断,如果目标方法不是public,则TransactionAttribute返回null,即不支持事务。

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

        // The method may be on an interface, but we need attributes from the target class.
        // If the target class is null, the method will be unchanged.
        Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);

        // First try is the method in the target class.
        TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
        if (txAttr != null) {
          return txAttr;
        }

        // Second try is the transaction attribute on the target class.
        txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
        if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
          return txAttr;
        }

        if (specificMethod != method) {
          // Fallback is to look at the original method.
          txAttr = findTransactionAttribute(method);
          if (txAttr != null) {
            return txAttr;
          }
          // Last fallback is the class of the original method.
          txAttr = findTransactionAttribute(method.getDeclaringClass());
          if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
            return txAttr;
          }
        }
        return null;
      }

    2.方法用final修饰:

    某个方法不想被子类重新,这时可以将该方法定义成final的,但如果将事务方法定义成final的事物会失效。

    @Service
    public class UserService {

        @Transactional
        public final void add(UserModel userModel){
            saveData(userModel);
            updateData(userModel);
        }
    }

    spring事务底层使用了aop,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。

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

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

     

    3.方法内部调用:

    @Service
    public class UserService {

        @Autowired
        private UserMapper userMapper;

        @Transactional
        public void add(UserModel userModel) {
            userMapper.insertUser(userModel);
            updateStatus(userModel);
        }

        @Transactional
        public void updateStatus(UserModel userModel) {
            doSameThing();
        }
    }

    在事务方法add中,直接调用事务方法updateStatus。从前面介绍的内容可以知道,updateStatus方法拥有事务的能力是因为spring aop生成代理了对象,但是这种方法直接调用了this对象的方法,所以updateStatus方法不会生成事务。

    解决办法:

    1)新加一个Service方法

    这个方法非常简单,只需要新加一个Service方法,把@Transactional注解加到新Service方法上,把需要事务执行的代码移到新方法中

    @Servcie
    public class ServiceA {
       @Autowired
       prvate ServiceB serviceB;

       public void save(User user) {
             queryData1();
             queryData2();
             serviceB.doSave(user);
       }
     }

     @Servcie
     public class ServiceB {

        @Transactional(rollbackFor=Exception.class)
        public void doSave(User user) {
           addData1();
           updateData2();
        }

     }

    2)在该Service类中注入自己

    Servcie
    public class ServiceA {
       @Autowired
       prvate ServiceA serviceA;

       public void save(User user) {
             queryData1();
             queryData2();
             serviceA.doSave(user);
       }

       @Transactional(rollbackFor=Exception.class)
       public void doSave(User user) {
           addData1();
           updateData2();
        }
     }

    3)通过AopContent类

    在该Service类中使用AopContext.currentProxy()获取代理对象

    @Servcie
    public class ServiceA {

       public void save(User user) {
             queryData1();
             queryData2();
             ((ServiceA)AopContext.currentProxy()).doSave(user);
       }

       @Transactional(rollbackFor=Exception.class)
       public void doSave(User user) {
           addData1();
           updateData2();
        }
     }

    4.未被spring管理

    使用spring事务的前提是:对象要被spring管理,需要创建bean实例。

    通过@Controller、@Service、@Component、@Repository等注解,可以自动实现bean实例化和依赖注入的功能。

    public class UserService {

        @Transactional
        public void add(UserModel userModel) {
             saveData(userModel);
             updateData(userModel);
        }    
    }

    5.多线程调用

    @Slf4j
    @Service
    public class UserService {

        @Autowired
        private UserMapper userMapper;
        @Autowired
        private RoleService roleService;

        @Transactional
        public void add(UserModel userModel) throws Exception {
            userMapper.insertUser(userModel);
            new Thread(() -> {
                roleService.doOtherThing();
            }).start();
        }
    }

    @Service
    public class RoleService {

        @Transactional
        public void doOtherThing() {
            System.out.println("保存role表数据");
        }
    }

    事务方法add中,调用了事务方法doOtherThing,但是事务方法doOtherThing是在另外一个线程中调用的。

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

    spring的事务是通过数据库连接来实现的。当前线程中保存了一个map,key是数据源,value是数据库连接。

    ====》private static final ThreadLocal<Map<Object, Object>> resources =

      new NamedThreadLocal<>("Transactional resources");

    说明:同一个事务,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接肯定是不一样的,所以是不同的事务。

     

    6.表不支持事务

    (mysql5之前,默认的数据库引擎是MYISAM。

    它的好处就不用多说了:索引文件和数据文件是分开存储的,对于查多写少的单表操作,性能比innodb更好。

    创建表的时候,只需要把ENGINE参数设置成MyISAM即可:

    CREATE TABLE `category` (
      `id` bigint NOT NULL AUTO_INCREMENT,
      `one_category` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,
      `two_category` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,
      `three_category` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,
      `four_category` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin


    myisam好用,但有个很致命的问题是:不支持事务; myisam还不支持行锁和外键

    7.未开启事务

    (1)使用的是springboot项目,那么你很幸运。因为springboot通过DataSourceTransactionManagerAutoConfiguration类,已经默默的帮你开启了事务。

    (2)使用的还是传统的spring项目,则需要在applicationContext.xml文件中,手动配置事务相关参数。如果忘了配置,事务肯定是不会生效的。<!-- 配置事务管理器 --> 
    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager"> 
        <property name="dataSource" ref="dataSource"></property> 
    </bean> 
    <tx:advice id="advice" transaction-manager="transactionManager"> 
        <tx:attributes> 
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes> 
    </tx:advice> 
    <!-- 用切点把事务切进去 --> 
    <aop:config> 
        <aop:pointcut expression="execution(* com.susan.*.*(..))" id="pointcut"/> 
        <aop:advisor advice-ref="advice" pointcut-ref="pointcut"/> 
    </aop:config> 

                                                                  二 事务不回滚

    1.错误的传播特性

    在使用@Transactional注解时,是可以指定propagation参数的。

    该参数的作用是指定事务的传播特性,spring目前支持7种传播特性:

    • REQUIRED 如果当前上下文中存在事务,那么加入该事务,如果不存在事务,创建一个事务,这是默认的传播属性值。
    • SUPPORTS 如果当前上下文存在事务,则支持事务加入事务,如果不存在事务,则使用非事务的方式执行。
    • MANDATORY 如果当前上下文中存在事务,否则抛出异常。
    • REQUIRES_NEW 每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。
    • NOT_SUPPORTED 如果当前上下文中存在事务,则挂起当前事务,然后新的方法在没有事务的环境中执行。
    • NEVER 如果当前上下文中存在事务,则抛出异常,否则在无事务环境上执行代码。
    • NESTED 如果当前上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。

    手动设置propagation参数的时候,把传播特性设置错了

    @Service
    public class UserService {

        @Transactional(propagation = Propagation.NEVER)
        public void add(UserModel userModel) {
            saveData(userModel);
            updateData(userModel);
        }
    }

    目前只有这三种传播特性才会创建新事务:REQUIRED,REQUIRES_NEW,NESTED。

    2.自己吞了异常

    开发者在代码中手动try...catch了异常

    @Slf4j
    @Service
    public class UserService {
        
        @Transactional
        public void add(UserModel userModel) {
            try {
                saveData(userModel);
                updateData(userModel);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
    }

    开发者自己捕获了异常,又没有手动抛出,换句话说就是把异常吞掉了。

    如果想要spring事务能够正常回滚,必须抛出它能够处理的异常。如果没有抛异常,则spring认为程序是正常的。

     

    3.手动抛了别的异常

    开发者没有手动捕获异常,但如果抛的异常不正确,spring事务也不会回滚。

    @Slf4j
    @Service
    public class UserService {
        
        @Transactional
        public void add(UserModel userModel) throws Exception {
            try {
                 saveData(userModel);
                 updateData(userModel);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
                throw new Exception(e);
            }
        }
    }

    开发人员自己捕获了异常,又手动抛出了异常:Exception,事务同样不会回滚。

    因为spring事务,默认情况下只会回滚RuntimeException(运行时异常)和Error(错误),对于普通的Exception(非运行时异常),它不会回滚。

     

    4.自定义了回滚异常

    在使用@Transactional注解声明事务时,有时我们想自定义回滚的异常,spring也是支持的。可以通过设置rollbackFor参数,来完成这个功能

    @Slf4j
    @Service
    public class UserService {
        
        @Transactional(rollbackFor = BusinessException.class)
        public void add(UserModel userModel) throws Exception {
           saveData(userModel);
           updateData(userModel);
        }
    }

    在执行上面这段代码,保存和更新数据时,程序报错了,抛了SqlException、DuplicateKeyException等异常。而BusinessException是我们自定义的异常,报错的异常不属于BusinessException,所以事务也不会回滚。

    5.嵌套事务回滚多了

    public class UserService {

        @Autowired
        private UserMapper userMapper;

        @Autowired
        private RoleService roleService;

        @Transactional
        public void add(UserModel userModel) throws Exception {
            userMapper.insertUser(userModel);
            roleService.doOtherThing();
        }
    }

    @Service
    public class RoleService {

        @Transactional(propagation = Propagation.NESTED)
        public void doOtherThing() {
            System.out.println("保存role表数据");
        }
    }

    使用了嵌套的内部事务,原本是希望调用roleService.doOtherThing方法时,如果出现了异常,只回滚doOtherThing方法里的内容,不回滚 userMapper.insertUser里的内容,即回滚保存点。。但事实是,insertUser也回滚了。

    因为doOtherThing方法出现了异常,没有手动捕获,会继续往上抛,到外层add方法的代理方法中捕获了异常。所以,这种情况是直接回滚了整个事务,不只回滚单个保存点。

    解决办法:

    @Slf4j
    @Service
    public class UserService {

        @Autowired
        private UserMapper userMapper;

        @Autowired
        private RoleService roleService;

        @Transactional
        public void add(UserModel userModel) throws Exception {

            userMapper.insertUser(userModel);
            try {
                roleService.doOtherThing();
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
    }

    ==将内部嵌套事务放在try/catch中,并且不继续往上抛异常。这样就能保证,如果内部嵌套事务中出现异常,只回滚内部事务,而不影响外部事务。

     

     

  • 相关阅读:
    【编程题目】左旋转字符串 ☆
    360測试开发笔试题(2016内推)
    start_kernel——boot_cpu_init及PER_CPU
    UVa 10673
    【面试】-Java基础知识
    Navgationcontroller 的pop
    别拿接口不当开发
    C++基础学习教程(三)
    安卓版微信自带浏览器和IE6浏览器ajax请求abort错误处理
    【动态树问题】LCT学习笔记
  • 原文地址:https://www.cnblogs.com/KL2016/p/15272369.html
Copyright © 2011-2022 走看看