Springboot——事物管理
作者:mmseoamin日期:2023-12-25

文章目录

  • 事务管理
  • 一、 Spring事务管理
    • 1.1 事务回顾
    • 1.2 案例: 解散部门(未开启事务)
    • 1.3 事务管理注解@Transactional
    • 1.4 事务管理日志开关
    • 1.5 rollbackFor 异常回滚属性
    • 1.6 propagation 事务传播行为
    • 1.7 解散部门并记录操作日志
      • 1.7.1 创建数据库表
      • 1.7.2 代码实现
      • 二、Spring基于AOP的声明式事务控制
        • 2.1 Spring事务概述
        • 2.2 搭建测试环境
        • 2.3xml方式声明事务
          • 2.3.1 快速入门
          • 2.3.2 详解
          • 2.3.3 原理剖析(浅浅看一下 )
          • 2.4 注解方式声明事务
          • 2.5 切点表达式配置方法与实物属性配置方法区别

            事务管理

            一、 Spring事务管理

            1.1 事务回顾

            事务: 是一组操作的集合,是一个不可分割的工作单位,这些操作要么同时成功,要么同时失败

            事务的操作

            • 开启事务(一组操作开始前,开启事务):start transaction / begin
            • 提交事务:(这组操作全部成功后,提交事务),commit
            • 回滚事务:(中间任何一个操作出现异常,回滚事务),rollback

              1.2 案例: 解散部门(未开启事务)

              需求:解散部门(删除部门),同时删除该部门下的员工

              我们之前业务逻辑仅仅删除了部门,并没有删除该部门下的员工,此时造成数据的不完整、不一致。

              @Delete("delete from dept where id= #{id}")
              void deleteById(Integer id);
              

              下面进行完善

              SQL

              DeptMapper

              @Delete("delete from dept where id= #{id}")
              void deleteById(Integer id);
              

              EmpMapper

              /**
               * 根据部门ID删除该部门下的员工数据
               * @param id 部门id
               */
              @Delete("delete from emp where dept_id  =#{id}")
              void deleteByDeptId(Integer id);
              

              业务代码

              @Override
              public void deleteById(Integer id) {
                  //根据id删除部门数据
                  deptMapper.deleteById(id);
                  //根据部门id删除员工数据
                  empMapper.deleteByDeptId(id);
              }
              

              假如在业务代码执行过程中出现异常了,会发生什么情况?

              ​ 可能会发生部门删除了但是部门里面员工没有被删除。此时造成数据的不一致。

              ​ 为了保证数据一致性,我们要保证删除部门和删除员工同时成功或者同时失败,也就是说这两步操作都在同一个事务当中

              1.3 事务管理注解@Transactional

              位置: 业务(Service)层方法上、类上、接口上

              作用: 将当前方法交给Spring进行事务管理,方法执行前开启事务;成功执行完毕,提交事务;出现异常,回滚事务

              我们一般添加在业务层执行多次数据访问操作的方法上

              @Transactional
              @Override
              public void deleteById(Integer id) {
                  //根据id删除部门数据
                  deptMapper.deleteById(id);
                  //根据部门id删除员工数据
                  empMapper.deleteByDeptId(id);
              }
              

              1.4 事务管理日志开关

              logging:
                level:
                  org.springframework.jdbc.support JdbcTransactionManager: debug
              

              1.5 rollbackFor 异常回滚属性

              • 默认情况下,只有出现RuntimeException才会回滚异常。

                比如说我们手动throw了一个Exception,并不会出现回滚的情况,而是直接将事务提交了

                @Transactional
                @Override
                public void deleteById(Integer id) throws Exception {
                    //根据id删除部门数据
                    deptMapper.deleteById(id);
                    if (true) {
                        throw new Exception("出错了");
                    }
                    //根据部门id删除员工数据
                    empMapper.deleteByDeptId(id);
                }
                
                • rollbackFor 属性用于控制出现哪一种异常类型的时候,进行回滚事务

                  这样配置后,所有的异常都会进行事务的回滚

                  @Transactional(rollbackFor = Exception.class)
                  @Override
                  public void deleteById(Integer id) throws Exception {
                      //根据id删除部门数据
                      deptMapper.deleteById(id);
                      if (true) {
                          throw new Exception("出错了");
                      }
                      //根据部门id删除员工数据
                      empMapper.deleteByDeptId(id);
                  }
                  

                  1.6 propagation 事务传播行为

                  事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。

                  属性值含义
                  REQUIRED[默认值] 需要事务,有则加入(b事务加入到a事务),无则创建新事务
                  **QEQUIRES_NEW **需要创建新事务,无论有无,总是创建新事务(b创立一个新事务),如果创建新事务,当前事务进行挂起,等新事务完成后再进行当前事务
                  SUPPORTS支持事务,有则加入,无则在无事务状态
                  NOT_SUPPORTED不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务(a事务先挂起先执行b事务,b事务完成后再执行a事务)
                  MANDATORY必须有事务,否则抛异常
                  NEVER必须没事务,否则抛异常

                  Springboot——事物管理,image-20230517142250530,第1张

                  1.7 解散部门并记录操作日志

                  需求:解散部门时,无论成功还是失败,都要记录操作日志

                  步骤:

                  ① 解散部门: 删除部门、删除部门下的员工

                  ② 记录日志到数据库表中

                  1.7.1 创建数据库表

                  create table dept_log(
                     	id int auto_increment comment '主键ID' primary key,
                      create_time datetime null comment '操作时间',
                      description varchar(300) null comment '操作描述'
                  )comment '部门操作日志表';
                  

                  1.7.2 代码实现

                  日志信息实体类

                  @Data
                  @NoArgsConstructor
                  @AllArgsConstructor
                  public class DeptLog {
                      private Integer id;
                      private LocalDateTime createTime;
                      private String description;
                  }
                  

                  日志插入SQL

                  @Mapper
                  public interface DeptLogMapper {
                      @Insert("insert into dept_log(create_time,description) values(#{createTime},#{description})")
                      void insert(DeptLog log);
                  }
                  

                  日志插入业务代码

                  @Service
                  public class DeptLogServiceImpl implements DeptLogService {
                      @Autowired
                      private DeptLogMapper deptLogMapper;
                      @Transactional //事务传播行为:有事务就加入、没有事务就新建事务
                      @Override
                      public void insert(DeptLog deptLog) {
                          deptLogMapper.insert(deptLog);
                      }
                  }
                  

                  删除部门业务代码

                      @Transactional(rollbackFor = Exception.class)
                      @Override
                      public void deleteById(Integer id) throws Exception {
                          //根据id删除部门数据
                          deptMapper.deleteById(id);
                          //根据部门id删除员工数据
                          empMapper.deleteByDeptId(id);
                  //      TODO 记录操作日志
                          DeptLog deptLog = new DeptLog();
                          deptLog.setCreateTime(LocalDateTime.now());
                          deptLog.setDescription("执行了解散部门的操作,此时解散的是"+id+"号部门");
                          //调用其他业务类中的方法
                          deptLogService.insert(deptLog);
                      }
                  

                  此时方法调用有两个 @Transactional注解

                  • 一个在deleteById方法,删除部门与对应用户
                  • 一个在insert方法,并且这个方法在deleteById方法中被调用

                    此时涉及事务传播行为

                    进行测试,发现数据库中并不存在日志信息,是什么原因?

                    两个方法都有 @Transactional注解,采用的是默认事务传播行为,需要事务,有则加入(b事务加入到a事务),无则创建新事务。

                    ​ 很显然insert事务会加入到deleteById事务,但是在deleteById业务执行时发生异常,进行回滚,那同属于一个事务的insert也会回滚,导致数据库中没有记录。

                    所以现在我们需要修改一下事务传播行为

                    propagation = Propagation.REQUIRES_NEW,表示无论deleteById中是否有事务,insert方法中都会新开启一个新的事物

                    @Transactional(propagation = Propagation.REQUIRES_NEW)
                    @Override
                    public void insert(DeptLog deptLog) {
                        deptLogMapper.insert(deptLog);
                    }
                    

                    当在deleteById事务中开启了insert事务,此时deleteById事务会被挂起,进行insert事务,当insert事务进行完成后继续运行deleteById事务

                    propagation = Propagation.REQUIRES_NEW,表示无论deleteById中是否有事务,insert方法中都会新开启一个新的事物

                    @Transactional(propagation = Propagation.REQUIRES_NEW)
                    @Override
                    public void insert(DeptLog deptLog) {
                        deptLogMapper.insert(deptLog);
                    }
                    

                    当在deleteById事务中开启了insert事务,此时deleteById事务会被挂起,进行insert事务,当insert事务进行完成后继续运行deleteById事务

                    二、Spring基于AOP的声明式事务控制

                    2.1 Spring事务概述

                    Spring的事务分为:编程式事务控制 和 声明式事务控制

                    • 编程式事务控制

                      Spring提供了事务控制的类和方法,使用编码的方式对业务代码进行事务控制,事务控制代码和业务操作代码耦合到了一起,开发中不使用

                      • 声明式事务控制

                        Spring将事务控制的代码封装,对外提供了Xml和注解配置方式,通过配置的方式完成事务的控制,可以达到事务控制与业务操作代码解耦合,开发中推荐使用

                        Spring事务编程相关的类主要有如下三个

                        • 平台事务管理器 PlatformTransactionManager

                          是一个接口标准,实现类都具备事务提交、回滚和获得事务对象的功能,不同持久层框架可能会有不同实现方案

                          • **事务定义 TransactionDefinition **

                            封装事务的隔离级别、传播行为、过期时间等属性信息

                            • 事务状态 TransactionStatus

                              存储当前事务的状态信息,如果事务是否提交、是否回滚、是否有回滚点等

                              2.2 搭建测试环境

                              ​ 搭建一个转账的环境,dao层一个转出钱的方法,一个转入钱的方法,service层一个转账业务方法,内部分别调用dao层转出钱和转入钱的方法,准备工作如下:

                              Maven坐标

                                      
                                      
                                          mysql
                                          mysql-connector-java
                                          8.0.19
                                      
                                      
                                          org.springframework
                                          spring-jdbc
                                          6.0.6
                                      
                                      
                                      
                                          org.mybatis
                                          mybatis
                                          3.4.6
                                      
                                          org.mybatis
                                          mybatis-spring
                                          3.0.2
                                      
                                      
                                      
                                          com.alibaba
                                          druid
                                          1.1.23
                                      
                                      
                                          org.springframework
                                          spring-context
                                          5.3.7
                                      
                              

                              jdbc连接信息

                              jdbc.driver = com.mysql.cj.jdbc.Driver
                              jdbc.url=jdbc:mysql://localhost:3306/itcast
                              jdbc.username=root
                              jdbc.password=root
                              
                              • 数据库准备一个账户表account;

                                Springboot——事物管理,image-20230609175807358,第2张

                              • dao层准备一个AccountMapper,包括incrMoney和decrMoney两个方法;

                                public interface AccountMapper {
                                //   加钱
                                    @Update("update account set money=money+#{money} where id=#{id}")
                                    public void incrMoney(@Param("id") String account,@Param("money") Integer money);
                                //   减钱
                                    @Update("update account set money=money-#{money} where id=#{id}")
                                    public void decrMoney(@Param("id") String account,@Param("money") Integer money);
                                }
                                
                                • service层准备一个transferMoney方法,分别调用incrMoney和decrMoney方法;
                                  public interface AccountService {
                                      void transferMoney(String outAccount,String inAccount , Integer money);
                                  }
                                  

                                  实现类

                                  @Service("accountService")
                                  public class AccountServiceImpl implements AccountService {
                                      @Autowired
                                      private AccountMapper accountMapper;
                                      @Override
                                      public void transferMoney(String outAccount, String inAccount, Integer money) {
                                          accountMapper.decrMoney(outAccount,money);
                                          accountMapper.incrMoney(inAccount,money);
                                      }
                                  }
                                  
                                  • 在applicationContext文件中进行Bean的管理配置;

                                    xml配置文件

                                    
                                    
                                    
                                    
                                    
                                    
                                        
                                        
                                        
                                        
                                        
                                    
                                    
                                    
                                        
                                    
                                    
                                    
                                        
                                    
                                    
                                    • 测试正常转账与异常转账。
                                      public class AccountTest {
                                          public static void main(String[] args) {
                                              ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
                                              AccountService accountService = applicationContext.getBean(AccountService.class);
                                              accountService.transferMoney("1","2",500);
                                          }
                                      }
                                      

                                      2.3xml方式声明事务

                                      2.3.1 快速入门

                                      我们要把AccountServiceImpl类中的转账方法设置在一个事务之中

                                      下面的代码是两个事务

                                      @Override
                                      public void transferMoney(String outAccount, String inAccount, Integer money) {
                                          accountMapper.decrMoney(outAccount,money);
                                          accountMapper.incrMoney(inAccount,money);
                                      }
                                      

                                      以使用AOP对Service的方法进行事务的增强

                                      • 目标类: 自定义的AccountServiceImpl,内部的方法是切点
                                      • 通知类:Spring提供的,通知方法已经定义好,只需要配置即可

                                        分析

                                        • 通知类是Spring提供的,需要导入Spring事务的相关的坐标
                                          
                                              org.springframework
                                              spring-jdbc
                                              5.2.13.RELEASE
                                          
                                          

                                          其实这个坐标我们之前已经导入过了,这个坐标下面有事务相关的坐标

                                          Springboot——事物管理,image-20230610124509971,第3张

                                          • 配置目标类AccountServiceImpl
                                          • 使用advisor标签配置切面

                                            如果有所忘记的话,可以看一下这篇文章 SpringBoot——IOC与AOP

                                            xml配置文件新配置信息

                                            
                                            
                                                
                                                
                                            
                                            
                                            
                                                
                                                    
                                                    
                                                
                                            
                                            
                                            
                                                
                                                
                                                
                                                
                                            
                                            

                                            故意在impl层出现异常

                                            @Service("accountService")
                                            public class AccountServiceImpl implements AccountService {
                                                @Autowired
                                                private AccountMapper accountMapper;
                                                @Override
                                                public void transferMoney(String outAccount, String inAccount, Integer money) {
                                                    accountMapper.decrMoney(outAccount,money);
                                                    int c = 3/0;
                                                    accountMapper.incrMoney(inAccount,money);
                                                }
                                            }
                                            

                                            进行测试

                                            出现了异常,但是两个人的钱并没有扣,很完美 就是我们想要的结果

                                            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
                                            AccountService accountService = applicationContext.getBean(AccountService.class);
                                            accountService.transferMoney("1","2",500);
                                            

                                            2.3.2 详解

                                            • 平台事务管理器

                                              我们再看一下xml中配置的平台事务管理器

                                              
                                                  
                                                  
                                              
                                              

                                              其中DataSourceTransactionManager是一个类,实现了很多接口,其中一个接口的父接口是PlatformTransactionManager接口(一个平台事务管理器),接口中有提交和回滚的方法

                                              PlatformTransactionManager接口的实现类是选哪一个,取决于当前DAO层使用的框架是什么样的

                                              比如如果使用的是最简单的JDBC、Mybatis,那实现类就是DataSourceTransactionManager

                                              如果DAO层使用的是Hibernate,那对应的平台管理器就是HibernateTransactionManager

                                              Springboot——事物管理,image-20230610131659253,第4张

                                              • Spring提供好的Advice 通知/增强

                                                刚刚我们在配置类中是这么配置的

                                                
                                                    
                                                        
                                                        
                                                    
                                                
                                                

                                                底层用到的事务的操作都在transactionManager中封装这

                                                主要看一下事务的属性

                                                忘记的话可以看一下下面这个文章MySQL基础 — 多表查询以及事务管理

                                                
                                                
                                                
                                                

                                                下面这个操作是配置不同方法的不同属性,name代表方法名称,*代表通配符,下面这个配置的意思就是所有的方法都用当前这一套事务的配置,如果没配置事务的属性,就是默认值

                                                
                                                

                                                也就是说可以配置好几套。比如下面,没有配置的参数就按照默认

                                                
                                                
                                                

                                                但是方法很多的时候一个一个添加又不太现实,所以我们的命名一定要规范,这样就能使用通配符进行匹配

                                                比如下面是匹配add开头的方法

                                                
                                                

                                                2.3.3 原理剖析(浅浅看一下 )

                                                主要看一下下面这个配置的原理

                                                    
                                                        
                                                            
                                                            
                                                        
                                                    
                                                
                                                    
                                                    
                                                    
                                                    
                                                
                                                

                                                首先找到对应的处理器

                                                Springboot——事物管理,image-20230610141624327,第5张

                                                找到对应的标签点进去

                                                Springboot——事物管理,image-20230610141752059,第6张

                                                发现TxAdviceBeanDefinitionParser类中没有parse方法,但是有一个doparse方法,代表着调用他爹的

                                                Springboot——事物管理,image-20230610142237853,第7张

                                                然后看一下AbstractSingleBeanDefinitionParser类,也没有parse方法,

                                                那就再看一下AbstractSingleBeanDefinitionParser类的爹AbstractBeanDefinitionParser类中有没有,发现是有的

                                                并且调用了一个parseInternal方法

                                                Springboot——事物管理,image-20230610142720804,第8张

                                                点过去发现parseInternal方法是抽象的,说明具体的实现在他儿子那里

                                                Springboot——事物管理,image-20230610142807004,第9张

                                                AbstractBeanDefinitionParser类的儿子是AbstractSingleBeanDefinitionParser类,里面确实有parseInternal方法的具体实现

                                                Springboot——事物管理,image-20230610142955925,第10张

                                                这个地方感觉太难了,看不下去了,就到这里吧

                                                2.4 注解方式声明事务

                                                标题一就是注解方式声明事务

                                                就是用注解代替通知的配置、织入的配置,也就是代替下面两部分

                                                
                                                    
                                                        
                                                        
                                                    
                                                
                                                
                                                
                                                    
                                                    
                                                    
                                                    
                                                
                                                

                                                注解方式,依然可以使用xml中对应的参数

                                                @Transactional可以使用在类上,也可以使用在方法上

                                                @Transactional(isolation = Isolation.REPEATABLE_READ,propagation = 
                                                Propagation.REQUIRED,readOnly = false,timeout = 5)
                                                

                                                仍然需要xml配置,开启事务开关

                                                
                                                   
                                                
                                                
                                                
                                                

                                                2.5 切点表达式配置方法与实物属性配置方法区别

                                                下图中我们已经选出合适的切点了

                                                Springboot——事物管理,image-20230610151246654,第11张

                                                为什么还要在事务管理这个地方再配置一下对应哪个方法?

                                                Springboot——事物管理,image-20230610151319336,第12张

                                                因为我们在切点中筛选出来的方法很多,所实现的业务也不一样

                                                那如果不在事务管理再分或者说筛选的话,那所有的方法事务配置的属性都一个样子了,这可能不会符合现实

                                                切点表达式,是过滤哪些方法可以进行事务增强

                                                事务属性信息的name,是指定哪个方法要进行哪些事务属性的配置

                                                Springboot——事物管理,image-20230610151233531,第13张