• Spring的事务有坑请注意


    错误的访问权限
    @Service
    public class UserService {
    ​
        @Autowired
        private UserMapper userMapper;
        
        @Transactional
        private void add(UserModel userModel) {
            userMapper.insertUser(userModel);
        }
    }

    我们可以看到add方法的访问权限被定义成了private,这样会导致事务失效,spring要求被代理方法必须是public的。

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

    方法被定义成final的
    @Service
    public class UserService {
    ​
        @Autowired
        private UserMapper userMapper;
    ​
        @Transactional
        public final void add(UserModel userModel) {
            userMapper.insertUser(userModel);
        }
    }

    我们可以看到add方法被定义成了final的,这样会导致spring aop生成的代理对象不能复写该方法,而让事务失效。

    方法内部调用
    @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方法不会生成事务。

    当前实体没有被spring管理
    //@Service
    public class UserService {
    ​
        @Autowired
        private UserMapper userMapper;
    ​
        @Transactional
        public void add(UserModel userModel) {
            userMapper.insertUser(userModel);
        }    
    }

    我们可以看到UserService类没有定义@Service注解,即没有交给spring管理bean实例,所以它的add方法也不会生成事务。

    错误的spring事务传播特性
    @Service
    public class UserService {
    ​
        @Autowired
        private UserMapper userMapper;
    ​
        @Transactional(propagation = Propagation.NEVER)
        public void add(UserModel userModel) {
            userMapper.insertUser(userModel);
        }
    ​
    }

    我们可以看到add方法的事务传播特性定义成了Propagation.NEVER,这种类型的传播特性不支持事务,如果有事务则会抛异常。只有这三种传播特性才会创建新事务:PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED。

    数据库不支持事务

    msql8以前的版本数据库引擎是支持myslam和innodb的。我以前也用过,对应查多写少的单表操作,可能会把表的数据库引擎定义成myslam,这样可以提升查询效率。但是,要千万记得一件事情,myslam只支持表锁,并且不支持事务。所以,对这类表的写入操作事务会失效。

    自己吞掉了异常
    @Service
    public class UserService {
    ​
        @Autowired
        private UserMapper userMapper;
        
        @Transactional
        public void add(UserModel userModel) {
            try {
                userMapper.insertUser(userModel);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
    }

    这种情况下事务不会回滚,因为开发者自己捕获了异常,又没有抛出。事务的AOP无法捕获异常,导致即使出现了异常,事务也不会回滚。

    抛出的异常不正确
    @Service
    public class UserService {
    ​
        @Autowired
        private UserMapper userMapper;
        
        @Transactional
        public void add(UserModel userModel) throws Exception {
            try {
                userMapper.insertUser(userModel);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
                throw new Exception(e);
            }
        }
    ​
    }

    这种情况下,开发人员自己捕获了异常,又抛出了异常:Exception,事务也不会回滚。因为spring事务,默认情况下只会回滚RuntimeException(运行时异常)和Error(错误),不会回滚Exception。

    多线程调用
    @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事务源码的朋友,可能会知道spring的事务是通过数据库连接来实现的。当前线程中保存了一个map,key是数据源,value是数据库连接。

    private static final ThreadLocal<Map<Object, Object>> resources =new NamedThreadLocal<>("Transactional resources");

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

    嵌套事务多回滚了
    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方法的代理方法中捕获了异常。所以,这种情况是直接回滚了整个事务,不只回滚单个保存点。

    怎么样才能只回滚保存点呢?

    @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中,并且不继续往抛异常。

    时刻与技术进步,每天一点滴,日久一大步!!! 本博客只为记录,用于学习,如有冒犯,请私信于我。
  • 相关阅读:
    hdu1828线段树(两次扫描+离散化)
    hdu1542线段树(扫描线+离散化)
    二分思想
    hdu2871线段树区间更新
    git初体验
    python笔记-模块
    python笔记-动态类型
    AWS上创建EKS(K8S)集群
    Akamai CDN刷新(通过Akamai cli 自动刷新)
    创建Akamai cdn api授权
  • 原文地址:https://www.cnblogs.com/myitnews/p/14014860.html
走看看 - 开发者的网上家园