美文网首页Spring全家桶
Spring系列之事物是如何管理的

Spring系列之事物是如何管理的

作者: 程序员阿牛 | 来源:发表于2021-09-02 12:52 被阅读0次

    前言

    我们都知道Spring给我们提供了很多抽象,比如我们在操作数据库的过程中,它为我们提供了事物方面的抽象,让我们可以非常方便的以事物方式操作数据库。不管你用JDBC、Mybatis、Hibernate等任何一种方式操作数据库,也不管你使用DataSource还是JTA的事物,Spring事物抽象管理都能很好的把他统一在一起。接下来看一下事物的抽象核心接口

    Spring事务抽象

    PlatformTransactionManager是事物管理器接口

    //事务管理器接口有以下几个接口,获取事物信息,提交和回滚
    public interface PlatformTransactionManager {
        TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
    
        void commit(TransactionStatus var1) throws TransactionException;
    
        void rollback(TransactionStatus var1) throws TransactionException;
    }
    

    常见的事物管理器有以下几种:

    • DataSourceTransactionManager
    • HibernateTransactionManager
    • JtaTransactionManager
      这些管理器都实现了PlatformTransactionManager中的三个接口,实现逻辑略有差别,但是对用户来讲区别不大

    定义事物的一些参数:
    一些事物的参数在TransactionDefinition.java中,详情如下:

    public interface TransactionDefinition {
          int PROPAGATION_REQUIRED = 0;
        int PROPAGATION_SUPPORTS = 1;
        int PROPAGATION_MANDATORY = 2;
        int PROPAGATION_REQUIRES_NEW = 3;
        int PROPAGATION_NOT_SUPPORTED = 4;
        int PROPAGATION_NEVER = 5;
        int PROPAGATION_NESTED = 6;
            //默认隔离级别,和数据库的隔离级别一致
        int ISOLATION_DEFAULT = -1;
        int ISOLATION_READ_UNCOMMITTED = 1;
        int ISOLATION_READ_COMMITTED = 2;
        int ISOLATION_REPEATABLE_READ = 4;
        int ISOLATION_SERIALIZABLE = 8;
            //默认不超时
        int TIMEOUT_DEFAULT = -1;
    }
    

    下面两张图对这些参数进行了说明:
    7种事务传播特性:

    file
    四种事务隔离级别:
    在看事务隔离级别前需要先了解下什么是脏读、不可重复读、幻读
    脏读: 脏读就是一个事物未提交的数据,被另外一个事物读到了,显然这种情况不可接受
    不可重复读: 不可重复读是指在一个事务内,多次读同一数据,前后读取的结果不一致。
    幻读: 事务A对表中的一个数据进行了修改,这种修改涉及到表中的全部数据行。同时事务B也修改了这个表中的数据,这种修改是向表中插入一行新数据。那么就会发生操作事务A的用户发现表中还存在没有修改的数据行,就好像发生了幻觉一样
    知道了以上几个概念,我们来看看隔离级别:
    file

    这里我们可以看到,Spring并不是提供了所有的事物管理的实现,而是提供了标准的事物管理器的操作接口PlatformTransactionManager, 并且规范了其行为,具体的事物实现由各个平台自行实现。这就是Spring的事物抽象。

    Spring之编程式事物

    Spring提供了TransactionTemplate工具类可以很方便的使用编程式事务。默认情况下TransactionTemplate使用的是DataSourceTransactionManager。
    在Spring上下文中,我们不配置TransactionTemplate这个bean,也能获取到TransactionTemplate。比如下面的例子。

    @Service
    public class UserInfoService {
    
        @Resource
        private UserInfoDAO userInfoDAO;
        @Autowired
        private TransactionTemplate transactionTemplate;
        
        public void updateUser1(){
            transactionTemplate.execute(transactionStatus -> {
                userInfoDAO.updateUserName(1,"zhangsanfeng");
                transactionTemplate.execute(transactionStatus2 -> {
                    userInfoDAO.updateUserName(2,"lisi");
                    return null;
                });
                return null;
            });
        }
    }
    

    由于Spring默认的事物传播特性是PROPAGATION_REQUIRED,我们来做一下验证,看是不是这样

    file
    file
    上面两幅图可以看出,TransactionStatus中的newTransaction属性,第一个是true,第二个是false,正好符合PROPAGATION_REQUIRED所描述的情况。其他的传播特性可以自己去验证。

    声明式事物

    除了编程式事物外,Spring还为我们提供了声明式事物。使用@Transactional注解。
    @Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该注解来覆盖类级别的定义。

    虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。

    @Transactional的rollbackFor属性可以设置一个 Throwable 的数组,用来表明如果方法抛出这些异常,则进行事务回滚。默认情况下如果不配置rollbackFor属性,那么事务只会在遇到RuntimeException的时候才会回滚。
    下面的代码事物就不会生效:

        @Transactional
        public void updateUser2() throws Exception {
            int r1 = userInfoDAO.updateUserName(1,"wanger");
            int r2 = userInfoDAO.updateUserName(2,"mawu");
            if (r2==1){
                throw new Exception();
            }
        }
    

    如果我们把抛出的异常改成RuntimeException,这时候事物就会生效了。或者指定异常让事物生效,比如 @Transactional(rollbackFor = Exception.class),这样碰到所有的异常事物都会生效了。

    为什么加了@Transactional注解事物就生效了?

    这是因为Spring容器会为加了这个注解的对象生成一个代理对象,实际调用的时候,实际上是调用的代理对象。 代理对象的实现了AOP的增强,实现了事物的实现。

    file

    通过注解怎么实现指定的传播特性和隔离级别的?

    public @interface Transactional {
        @AliasFor("transactionManager")
        String value() default "";
    
        @AliasFor("value")
        String transactionManager() default "";
    
        String[] label() default {};
    
        Propagation propagation() default Propagation.REQUIRED;
    
        Isolation isolation() default Isolation.DEFAULT;
    
        int timeout() default -1;
    
        String timeoutString() default "";
    
        boolean readOnly() default false;
    
        Class<? extends Throwable>[] rollbackFor() default {};
    
        String[] rollbackForClassName() default {};
    
        Class<? extends Throwable>[] noRollbackFor() default {};
    
        String[] noRollbackForClassName() default {};
    }
    

    代码中可以看出,我们可以指定隔离级别和传播特性,在Spring为我们生成代理类的时候,会读取这些属性,体现在增强逻辑中。

    事物失效的8种情况及解决办法

    数据库引擎不支持事务

    这里以 MySQL 为例,其 MyISAM 引擎是不支持事务操作的,InnoDB 才是支持事务的引擎,一般要支持事务都会使用 InnoDB,这时候选择支持事物的数据库即可(好像是废话,哈哈哈)

    没有被 Spring 管理

    这个好像没什么可说的,脱离了Spring的管理,还谈什么Spring事物管理。

    方法不是 public 的

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

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

    相当于没开启事务管理,如果不是Springboot情况需要进行如下操作。

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

    如果是SpringBoot,在启动类上直接加上注解@EnableTransactionManagement即可。

    传播特性配错了

    传播特性配置成,Propagation.NOT_SUPPORTED或者Propagation.NOT_SUPPORTED,改成支持事物的传播特性即可。

    异常类型错误

    因为默认的异常类型是运行时异常,如果抛出了其他异常就不生效。
    解决方式:
    1、将异常改成运行时异常
    2、指定异常进行事物回滚,如:@Transactional(rollbackFor = Exception.class)

    异常被吃掉了

    如果你代码这么写,事物不生效:

        @Transactional(rollbackFor = Exception.class)
        public void updateUser2() {
           try {
            int r1 = userInfoDAO.updateUserName(1,"3");
            int r2 = userInfoDAO.updateUserName(2,"4");
            if (r2==1){
                throw new RuntimeException();
            }
            
                
            }catch (Exception e){
                
            }
        }
    

    解决办法: 必须要抛出异常,否则Spring事务管理,不会走到回滚逻辑

    类内部调用

    @Service
    public class UserInfoService {
        public void justUpdate(){
            updateUser2();
        }
        @Transactional(rollbackFor = Exception.class)
        public void updateUser2() {
    
        }
    }
    

    上述代码不生效,因为内部调用不会涉及到代理类的调用,更不会有AOP的增强,因此不会生效。
    解决办法:
    1、自注入

    @Service
    public class UserInfoService {
       @Autowired
        private UserInfoService userInfoService;
        public void justUpdate(){
            userInfoService.updateUser2();
        }
        @Transactional(rollbackFor = Exception.class)
        public void updateUser2() {
    
        }
    }
    

    2、Spring上下文

    @Service
    public class UserInfoService {
        ApplicationContext applicationContext;
        public void justUpdate(){
               UserInfoService userInfoService = (UserInfoService) applicationContext.getBean("userInfoService");
            userInfoService.updateUser2();
        }
        @Transactional(rollbackFor = Exception.class)
        public void updateUser2() {
        }
    }
    

    3、获取他的代理类,直接调用代理类

    @Service
    public class UserInfoService {
        public void justUpdate(){
               ((UserInfoService) AopContext.currentProxy()).updateUser2();
        }
        @Transactional(rollbackFor = Exception.class)
        public void updateUser2() {
        }
    }
    

    ----------------------------END---------------------------
    更多Spring相关知识,请关注我,各平台都是同一个ID

    相关文章

      网友评论

        本文标题:Spring系列之事物是如何管理的

        本文链接:https://www.haomeiwen.com/subject/aynqwltx.html