Spring事务和事务传播机制

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

什么是事务

为什么需要事务

事务的操作:

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

 

2. 事务的实现

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

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

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

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

实体类:

@Data
public class LogInfo {
    private Integer id;
    private String op;
    private String userName;
    private Date createTime;
    private Date updateTime;
}
@Data
public class UserInfo {
    private Integer id;
    private String password;
    private String userName;
    private Date createTime;
    private Date updateTime;

}

service:

@Service
public class LogInfoService {

    @Autowired
    private LogInfoMapper logInfoMapper;


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

    }
}
@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:

@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);
}
@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个重要操作步骤:

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

下面看一段代码:

@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. 重新抛出异常

 

2. 手动回滚事务

使⽤ 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(加⼊事务)

 

@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可以实现局部回滚

 

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值