Spring事务和事务传播机制

1. 事务(不再介绍,详看初阶笔记)

什么是事务

为什么需要事务

事务的操作:

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

2. 事务的实现

Spring中的事务操作分为两类:

  • 编程式事务(⼿动写代码操作事务).
  • 声明式事务(利⽤注解⾃动开启和提交事务).

需求:⽤⼾注册,注册时在⽇志表中插⼊⼀条操作记录

准备: 数据库:user_info表,log_info表

实体类:

java 复制代码
@Data
public class LogInfo {
    private Integer id;
    private String op;
    private String userName;
    private Date createTime;
    private Date updateTime;
}
java 复制代码
@Data
public class UserInfo {
    private Integer id;
    private String password;
    private String userName;
    private Date createTime;
    private Date updateTime;

}

service:

java 复制代码
@Service
public class LogInfoService {

    @Autowired
    private LogInfoMapper logInfoMapper;


    @Transactional
    public Integer insertLog(String userName,String op){
        logInfoMapper.insert(userName,op);
        return 1;

    }
}
java 复制代码
@Service
public class UserInfoService {
    @Autowired
    private UserInfoMapper userInfoMapper;


    @Transactional(propagation = Propagation.NESTED)
    public Integer insertUser(String userName,String password){
        return userInfoMapper.insert(userName,password);
    };
}

mapper:

java 复制代码
@Mapper
public interface LogInfoMapper {

    @Insert("insert into log_info (user_Name,op) values (#{userName},#{op})")
    Integer insert(@Param("userName") String userName, @Param("op") String op);
}
java 复制代码
@Mapper
public interface UserInfoMapper {

    @Insert("insert into user_info (user_name,password) values(#{userName},#{password})")
    Integer insert(@Param("userName") String userName, @Param("password") String password);
}

2.1 Spring编程式事务(了解)

Spring⼿动操作事务和MySQL操作事务类似,有3个重要操作步骤:

  • 开启事务
  • 提交事务
  • 回滚事务

下面看一段代码:

java 复制代码
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {

    //JDBC事务管理器
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;

    //定义事务属性
    @Autowired
    private TransactionDefinition transactionDefinition;

    @Autowired
    private UserInfoService userInfoService;

    @RequestMapping("/registry")
    public String registry(String userName,String password){
        //开启事务
        TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
        Integer result = userInfoService.insertUser(userName,password);
        log.info("用户插入成功,result:{}",result);
        //回滚事务
        //dataSourceTransactionManager.rollback(transaction);

        //提交事务
        dataSourceTransactionManager.commit(transaction);
        return "注册成功";
    }


}

观察事务提交和回滚时日志的区别:

提交:

回滚:

虽然回滚时,程序返回"注册成功",但是数据库并没有插入数据

SpringBoot 内置了两个对象:

  • DataSourceTransactionManager:事务管理器.⽤来获取事务(开启事务),提交或回滚事务
  • TransactionDefinition:是事务的属性,在获取事务的时候需要将TransactionDefinition 传递进去从⽽获得⼀个事务TransactionStatus

2.2 Spring声明式事务@Transactional

2.2.1 操作

声明式事务的实现很简单,只需要在需要事务的⽅法上添加 @Transactional 注解就可以实现了.

⽆需⼿动开启事务和提交事务,进⼊⽅法时⾃动开启事务,⽅法执⾏完会⾃动提交事务,如果中途发⽣了没有处理的异常会⾃动回滚事务.

看下面代码实现:

数据插入成功

修改程序,使之出现异常:

数据库却没有新增数据,事务进⾏了回滚.

2.2.2 @Transactional 作⽤

@Transactional 可以⽤来修饰⽅法或类:

  • 修饰⽅法时:只有修饰public⽅法时才⽣效(修饰其他⽅法时不会报错,也不⽣效)[推荐]
  • 修饰类时:对 @Transactional 修饰的类中所有的public⽅法都⽣效.

⽅法/类被@Transactional 注解修饰时,在⽬标⽅法执⾏开始之前,会⾃动开启事务,⽅法执⾏结束之后,⾃动提交事务.

如果在⽅法执⾏过程中,出现异常,且异常未被捕获,就进⾏事务回滚操作 .

如果异常被程序捕获 ,⽅法就被认为是成功执⾏,依然会提交事务 .

下面对异常进行捕获:

运⾏程序,虽然程序出错了,但是由于异常被捕获了,所以事务依然得到了提交.

2.2.3 回滚操作

如果需要事务进⾏回滚,有以下两种⽅式:

  1. 重新抛出异常
  1. 手动回滚事务

使⽤ TransactionAspectSupport.currentTransactionStatus() 得到当前的事务,并使⽤ setRollbackOnly设置

3. @Transactional 详解

三个常见属性:

  • rollbackFor: 异常回滚属性.指定能够触发事务回滚的异常类型.可以指定多个异常类型
  • Isolation: 事务的隔离级别.默认值为 Isolation.DEFAULT
  • propagation: 事务的传播机制.默认值为 Propagation.REQUIRED

3.1 rollbackFor

@Transactional 默认只在遇到运⾏时异常和Error时才会回滚,⾮运⾏时异常不回滚.即 Exception的⼦类中,除了RuntimeException及其⼦类.

看下面代码:

运行时异常:(回滚):

非运行时异常(不回滚) :

虽然程序抛出了异常,但是事务依然进⾏了提交.


如果我们需要所有异常都回滚,需要来配置@Transactional 注解当中的rollbackFor 这个属性

通过这个属性指定出现何种异常类型时事务进⾏回滚.

结论:

  • 在Spring的事务管理中,默认只在遇到运⾏时异常RuntimeException和Error时才会回滚.
  • 如果需要回滚指定类型的异常,可以通过rollbackFor属性来指定.

3.2 事务隔离级别

3.2.1 MySQL事务隔离级别

简单回顾:

读未提交,读提交,可重复读,串行化

3.2.2 Spring 事务隔离级别

  • Isolation.DEFAULT :以连接的数据库的事务隔离级别为主.
  • Isolation.READ_UNCOMMITTED :读未提交,对应SQL标准中READ_UNCOMMITTED
  • Isolation.READ UNCOMMITTED :读已提交,对应SQL标准中 READ COMMITTED
  • Isolation.REPEATABLE_READ :可重复读,对应SQL标准中 REPEATABLE READ
  • Isolation.SERIALIZABLE 串行化,对应SQL标准中SERIALIZABLE

Spring 中事务隔离级别可以通过 @Transactional 中的 isolation 属性进⾏设置

3.3 Spring事务传播机制(了解)

3.3.1 什么是事务传播机制

多个事务⽅法存在调⽤关系时,事务是如何在这些⽅法间进⾏传播的

⽐如有两个⽅法A,B都被 @Transactional 修饰, A⽅法调⽤B⽅法 A⽅法运⾏时,会开启⼀个事务.

当A调⽤B时,B⽅法本⾝也有事务,此时B⽅法运⾏时,是加⼊A的事务,还 是创建⼀个新的事务呢? 这个就涉及到了事务的传播机制.

事务隔离级别解决的是多个事务同时调⽤⼀个数据库的问题

而事务传播机制解决的是⼀个事务在多个节点(⽅法)中传递的问题

3.3.2 事务的传播机制有哪些

|---------------------------|-----------------------------------------|--------------------------------------|
| 机制 | 意义 | 举例 |
| Propagation.REQUIRED(默认) | 如果当前存在事务,则加⼊该事务.如果当前没有事务,则创建⼀个新的事务 | 需要房⼦.如果你有房,我们就⼀起住,如果你没房,我们就⼀起买房 |
| Propagation.SUPPORTS | :如果当前存在事务,则加⼊该事务.如果当前没有事务,则以⾮事务的 ⽅式继续运⾏ | 可以有房⼦.如果你有房,那就⼀起住.如果没房,那就租房 |
| Propagation.MANDATORY | 强制性.如果当前存在事务,则加⼊该事务.如果当前没有事务,则 抛出异常. | 必须有房⼦.要求必须有房,如果没房就不结婚 |
| Propagation.REQUIRES_NEW | 创建⼀个新的事务.如果当前存在事务,则把当前事务挂起 | 必须买新房.不管你有没有房,必须要两个⼈⼀起买房.即使有房也不住 |
| Propagation.NEVER | 以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常 | 不能有房⼦ |
| Propagation.NOT_SUPPORTED | 以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起 | 不需要房.不管你有没有房,我都不住,必须租房 |
| Propagation.NESTED | 如果如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏. | 如果你没房,就⼀起买房.如果你有房,我们就以房⼦为根据地,做点下 ⽣意. |

3.3.3 Spring 事务传播机制使用和各种场景演示

这里演示三个:

  • REQUIRED(默认值)
  • REQUIRES_NEW
  • NESTED
3.3.3.1 REQUIRED(加⼊事务)
java 复制代码
@RestController
@RequestMapping("/proga")
@Slf4j
public class ProController {

    @Autowired
    private UserInfoService userInfoService;

    @Autowired
    private LogInfoService logInfoService;

    @Transactional
    @RequestMapping("/p1")
    public String registry(String userName,String password){

        Integer result = userInfoService.insertUser(userName,password);


        Integer result2 = logInfoService.insertLog(userName,"用户自行注册");
        return "注册成功";
    }
}

对应的UserService和LogService都添加上@Transactional(propagation = Propagation. REQUIRED)

看第一种情况:

第二种:没有错误

总结:

事务全部成功时,都成功

一个失败,都失败

流程描述:

  • p1⽅法开始事务
  • ⽤⼾注册,插⼊⼀条数据(执⾏成功)(和p1使⽤同⼀个事务)
  • 记录操作⽇志,插⼊⼀条数据(出现异常,执⾏失败)(和p1使⽤同⼀个事务)
  • 因为步骤3出现异常,事务回滚.步骤2和3使⽤同⼀个事务,所以步骤2的数据也回滚了
3.3.3.2 REQUIRES_NEW(新建事务)
3.3.3.2 REQUIRES_NESTED(嵌套事务)

总结:

事务全部成功时,都成功

一个失败,都失败


流程:

  • Controller 中p1⽅法开始事务 UserService
  • ⽤⼾注册,插⼊⼀条数据(嵌套p1事务)
  • LogService 记录操作⽇志,插⼊⼀条数据(出现异常,执⾏失败)(嵌套p1事务,回滚当前事务,数据添加失败)
  • 由于是嵌套事务, LogService 出现异常之后,往上找调⽤它的⽅法和事务,所以⽤⼾注册也失败了.
  • 最终结果是两个数据都没有添加
    p1事务可以认为是⽗事务,嵌套事务是⼦事务.⽗事务出现异常,⼦事务也会回滚,⼦事务出现异常,如 果不进⾏处理,也会导致⽗事务回滚.
3.3.3.4 REQUIRED与NESTED区别

NESTED可以实现局部回滚

重新运⾏程序,发现⽤⼾表数据添加成功,⽇志表添加失败.

相关推荐
tatasix22 分钟前
MySQL UPDATE语句执行链路解析
数据库·mysql
一元咖啡30 分钟前
SpringCloud Gateway转发请求到同一个服务的不同端口
spring·spring cloud·gateway
南城花随雪。35 分钟前
硬盘(HDD)与固态硬盘(SSD)详细解读
数据库
儿时可乖了36 分钟前
使用 Java 操作 SQLite 数据库
java·数据库·sqlite
ruleslol38 分钟前
java基础概念37:正则表达式2-爬虫
java
懒是一种态度38 分钟前
Golang 调用 mongodb 的函数
数据库·mongodb·golang
天海华兮40 分钟前
mysql 去重 补全 取出重复 变量 函数 和存储过程
数据库·mysql
xmh-sxh-13141 小时前
jdk各个版本介绍
java