1. 数据库的事务

1.1 什么是事务

    作为单个逻辑工作单位的一系列操作,要么完全成功,要么完全失败。  

1.2 事务的四大特征ACID

  • A Atomic(原子性)

    事务中包含的操作被看作一个整体的业务单元,这样业务单元要么完全成功,要么完全失败。最小的单元  
  • C Consistency(一致性)

    事务在完成时,必须使所有的数据都保持一致状态,在数据库中所有的修改都基于事务,保证了数据的完整性  
  • I Isolation(隔离性)

    下面将会重点讲解隔离  
  • D Durability(持久性)

    事务结束后,所有的数据会固化到一个地方,如保存到磁盘中,即使断电后也可以提供给相应程序访问  

1.3 事务的隔离级别

1.3.1 读未提交造成的脏读

       是最低的隔离级别,其含义是允许一个事务读取另外一个事务没有提交的数据。未提交读是一种危险  
    的隔离级别,所以一般在我们实际的开发中应用不广,但是它的优点在于并发能力高,适合那些对数据  
    一致性没有要求而追求高并发的场景,它的最大坏处是出现脏读。让我们看看可能发生的脏读场景,  
    如表所示。  
    T3时刻,因为采用未提交读,所以事务2可以读取事务l未提交的库存数据为1,这里当它扣减  
    库存后则数据为0,然后它提交了事务,库存就变为了。,而事务l在TS时刻回滚事  
    务,因为第一类丢失更新已经被克服,所以它不会将库存回滚到2,那么最后的结果就变为了  
    0,这样就出现了错误。脏读一般是比较危险的隔离级别,在我们实际应用中采用得不多。  
    为了克服脏读的问题,数据库隔离级别还提供了读已提交(readcommited)的级别,  
    下面我们时论它。  

1.3.2 读已提交

读已提交解决了读未提交造成的脏读问题,但读已提交依然造成了另外一个问题–不可重复读

    读已提交: 是指一个事务只能读取另外一个事务已经提交的数据,不能读取未提交的数据。  
    在T3时刻事务2读取库存的时候,因为事务l未提交事务,所以读出的库存为l,于是事务2  
    认为当前可扣减库存;在T4时刻,事务l己经提交事务,所以在TS时刻,它扣减库存的时候  
    就发现库存为0,于是就无法扣减库存了。这里的问题在于事务2之前认为可以扣减,而到扣    
    减那一步却发现已经不可以扣减,于是库存对于事务2而言是一个可变化的值,这样的现象  
    我们称为不可重复读,这就是读写提交的一个不足。为了克服这个不足,数据库的隔离级别  
    还提出了可重复读的隔离级别,它能够消除不可重读的问题。  

1.3.3 可重复读

    可以看到,事务2在T3时刻尝试读取库存,但是此时这个库存己经被事务1事先读取,所以这  
    个时候数据库就阻塞它的读取,直至事务1提交,事务2才能读取库存的值。此时己经是TS时  
    刻,而读取到的值为0,这时就已经无法扣减了,显然在读写提交中出现的不可重复读的场  
    景被消除了。但是这样也会引发新的问题的出现,这就是幻读。   

下面演示造成幻读的实战

1.3.4 序列化 (串行化)

    串行化(Serializable):是数据库最高的隔离级别,它会要求所有的SQL都会按照顺序执  
    行,这样就可以克服上述隔离级别出现的各种问题,所以它能够完全保证数据的一致性。

2. Spring的事务的传播行为

2.1 具体的传播行为《java基础底层》中分析了Spring事务的传播行为

2.2 这里分析一个易错点,@Transactional失效的情景

  • 首先编写一个测试service

        /**
         * @author ZhangRongJun
         * @version 1.0
         * @date 2020/9/5 10:03
         * @description:TODO
         */
        @Service
        public class UserServiceImpl implements UserService  {
    
            @Autowired
            private UserMapper userMapper = null;
    
            @Override
            @Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED,rollbackFor = RuntimeException.class)
            public int inserts(List<Users> users) {
                int count = 0;
                for (Users u:users){
                    count += insert(u);
                }
                return 0;
            }
            @Override
            @Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRES_NEW,rollbackFor = RuntimeException.class)
            public int insert(Users users) {
                return userMapper.insert(users);
            }
    
        }

根据事务的传播,REQUIRED表示必须存在一个事务中,没有则新建事务,insert方法中调用了参数为Users的方法,而这个方法设置了新的传播行为REQUIRES_NEW,表示会为每一个执行该方法时创建新的事务,运行接口测试

在上图中,调用insert(Users users)方法时,使用的还是inserts(List users)方法创建的事务,造成了insert(Users users)的@Transactional注解失效

        Spring数据库事务的约定,其实现原理是AOP,而AOP的原理是动态代理,在自调用的过程中,是类自身的调用,  
        而不是代理对象去调用,那么就不会产生AOP,这样Spring就不能把你的代码织入到约定的流程中,于是就产生  
        了现在看到的失败场景。为了克服这个问题,用一个Service去调用另一个Service,这样就是代理对象的调用,  
        Spring才会将你的代码织入事务流程。当然也可以从SpringIoC容器中获取代理对象去启用AOP,  

自调用会造成@Transactional失效


2.3 解决自调用使@Transactional失效

2.3.1 更改上面service代码

        /**
         * @author ZhangRongJun
         * @version 1.0
         * @date 2020/9/5 10:03
         * @description:TODO
         */
        @Service
        public class UserServiceImpl implements UserService,ApplicationContextAware   {

            @Autowired
            private UserMapper userMapper = null;
            ApplicationContext applicationContext;

            @Override
            @Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED,rollbackFor = RuntimeException.class)
            public int inserts(List<Users> users) {
                UserService userService = applicationContext.getBean(UserService.class);
                int count = 0;
                for (Users u:users){
                    count += userService.insert(u);
                }
                return 0;
            }
            @Override
            @Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRES_NEW,rollbackFor = RuntimeException.class)
            public int insert(Users users) {
                return userMapper.insert(users);
            }

            @Override
            public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
                this.applicationContext = applicationContext;
            }
        }  
  • 在上面代码中新增实现接口ApplicationContextAware获取Spring Ioc容器
  • 通过applicationContext获取Bean:UserService userService = applicationContext.getBean(UserService.class);

2.3.2 测试

如图执行insert(Users users)方法时,没执行一次,创建了一个事务。@Transactional注解生效