【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制

目录

需要知道:

1、什么是事务?

2、事务的主要操作3个

一、Spring中事务的实现方式

1、编程式事务(手动写代码操作事务)(了解)

2、声明式事务(用注解自动开启和提交事务)(重点)

(1)@Transactional注解的使用

(2)@Transactional 参数说明

(3)异常被捕获不会回滚的演示

(4)要求对于所有的异常都要求回滚的办法

二、事务的隔离级别(5种)

三、事务的传播机制

1、事务的传播机制是什么?

2、Spring中事务传播机制的分类(7种)

3、 Spring 事务传播机制使用和部分场景演示 


需要知道:

1、什么是事务?

将一组操作封装成⼀个执行(封装到一起),要么全部成功,要么全部失败。

比如转账分为两个操作:

(1):A账户+100元;

(2):B账户-100元;
如果没有事务,AB两个账户是分离的,当B账户给A转账成功,B账户少了100,但是A账户却没有反应;如果使用事务,那么AB两个账户的钱数是联动的,B账户给A转账少100元与A账户增加100元这个操作,是一起成功或者失败的。

2、事务的主要操作3个

-- 开启事务
start transaction;
-- 业务执⾏
-- 提交事务
commit;
-- 回滚事务
rollback;


一、Spring中事务的实现方式

1、编程式事务(手动写代码操作事务)(了解)

@Slf4j
@RestController
@RequestMapping("/trans")
public class TransactionController {
    @Autowired
    private UserService userService;
    //1、拿到事务管理器
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;
    //2、定义事务属性
    @Autowired
    private TransactionDefinition transactionDefinition;

    @RequestMapping("/addUser")
    public Integer addUser(String username,String password){
        //3、开启事务
        TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
        //4、插入数据
        User user = new User(username,password);
        Integer result = userService.insert(user);
        log.info("影响的行数是:"+result);
        //5、提交事务
        dataSourceTransactionManager.commit(transaction);
        //6、回滚事务
//        dataSourceTransactionManager.rollback(transaction);
        return result;
    }

}

测试结果:(1)提交事务:url端传参,事务提交成功,数据库成功插入一条记录;

【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制_第1张图片

【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制_第2张图片

 (2)当回滚事务的时候,url端传参,数据库并没有插入数据。【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制_第3张图片

【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制_第4张图片

2、声明式事务(用注解自动开启和提交事务)(重点)

(1)@Transactional注解的使用

    /**
     * 自动提交事务
     */
@Slf4j
@RestController
@RequestMapping("/trans")
public class TransactionController {
    @Autowired
    private UserService userService;

    //声明式事务的实现很简单,只需要在需要的⽅法上添加 @Transactional 注解就可以实现了,⽆需⼿动
    //开启事务和提交事务,进⼊⽅法时⾃动开启事务,⽅法执⾏完会⾃动提交事务;如果中途发⽣了没有处
    //理的异常会⾃动回滚事务
    @RequestMapping("/addUser2")
    @Transactional
    public Integer addUser2(String username,String password){
        //插入数据
        User user = new User(username,password);
        Integer result = userService.insert(user);
        //模拟异常
        int a = 10/0;
        return result;
    }
}

使用:

(1)声明式事务只要在需要的方法上添加@Transactional 注解就可以实现了;

(2)进入方法时自动开启事务,方法执行成功会自动提交事务;

(3)如果中途发生了没有处理的异常会自动回滚事务。默认在运行时异常和Error的时候才会回滚,也就是exception的子类中的RuntimeException会回滚。非运行时异常不回滚。

(4)如果异常被捕获,也不会进行处理;(后面演示)

(5)@Transactional 可以用来修饰方法或类:
        修饰方法时:需要注意只能应用到 public 方法上,否则不生效。(推荐这种用法)。
        修饰类时:表明该注解对该类中所有的 public 方法都生效。

(1)情况1:正常情况:使用注解@Transactional会自动开启和提交事务。url端传参,数据库添加数据成功。

【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制_第5张图片

(2)当有异常情况的时候,使用@Transactional会自动回滚事务,数据库也不会添加数据。

【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制_第6张图片

【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制_第7张图片

【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制_第8张图片

(3)当不使用注解@Transactional,但是有异常存在,数据依然会执行成功。

【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制_第9张图片

【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制_第10张图片

(2)@Transactional 参数说明

【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制_第11张图片

(3)异常被捕获不会回滚的演示

加一个被捕获的算数异常,观察结果:

/**
 * @Transactional 在异常被捕获的时候,不会进行回滚。
 */
@RestController
@RequestMapping("/trans")
public class TransactionController{
    @Resource
    private UserService userService;
    @RequestMapping("/addUser")
    @Transactional
    public Object addUser(String username,String password) {
        //插入数据
        User user = new User(username,password);
        Integer result = userService.insert(user);
        try {
            // 执⾏了异常代码(0不能做除数)
            int i = 10 / 0;
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        return result;
    }
}

(4)要求对于所有的异常都要求回滚的办法

方式1:使用rollbackfor参数,指定对于所有异常都回滚。

@Transactional(rollbackFor = Exception.class)

方式2:手动回滚事务,在方法中使用TransactionAspectSupport.currentTransactionStatus() 可以得到当前的事务,然后设置回滚方法setRollbackOnly 就可以实现回滚了。

 // ⼿动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

二、事务的隔离级别(5种)

1、Spring比MySQL的隔离级别多了一个default。

2、MySQL的默认隔离级别:可重复读;Oracle是读已提交。

3、Spring 中事务隔离级别只需要设置 @Transactional ⾥的 isolation 属性即可:

@Transactional(isolation = Isolation.SERIALIZABLE)

4、事务隔离级别解决的是多个事务同时调用一个数据库的问题。

【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制_第12张图片​​​​​​​

5、Spring中事务隔离级别的分类

(1)Isolation.DEFAULT:以连接的数据库的事务隔离级别为主。
(2)Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读。
(3)Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读。
(4) Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级别)。
(5) Isolation.SERIALIZABLE:串行化,可以解决所有并发问题,但性能太低。


举个栗子

(1)A和B正在写作业,B不想写,就照着A的作业抄,抄完之后B就开开心心的出去玩了,此时A检查发现了一个错误,就对错误进行了修正,那么B此时读到的就是错误的数据,这个现象就是“读未提交”,存在脏读问题;

(2)读已提交:A在写作业的过程中,给A上把锁,B不能抄,要等到A写完提交之后才能抄。那么当早上10.00A开始写作业,这个过程中B只能等着,10.30的时候A把作业写完了,B开始抄,等到10.40 A 又开始检查作业了,发现有问题,又开始修正,于是B抄作业,抄着抄着就突然发现数据变了。这就是出现了“幻读”现象。

脏读与不可重复读的区别:脏读是某一个事务读取了另一个事务未提交的脏数据,但是不可重复读是读取了前一事务提交的数据。

(3)可重复读:A在写作业的时候,B不能抄;B在抄作业的时候,A也不能修改。相当于A写作业的时候加了一个排它锁,B抄作业的时候加了一个共享锁,那么AB互不干扰。

(4)串行化:虽然B在抄作业的时候,A不能进行修改,但是A想着,闲着也是闲着,你抄语文作业,那么我就去修改数学作业,虽然没有直接修改B抄的作业,但是也影响到了B的最终的抄作业的效果,这就是出现了幻读现象。要用串行化来解决:也就是B抄语文作业,A所有的作业都不能修改,抄和写严格执行,不能相互响应。

三、事务的传播机制

1、事务的传播机制是什么?

Spring 事务传播机制定义了多个包含了事务的方法,相互调用时,事务是如何在这些方法间进行传递的。事务传播机制是保证⼀个事务在多个调用方法间的可控性的(稳定性的)。

设置事务的传播机制:

@Transactional(propagation = Propagation.REQUIRED

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

【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制_第13张图片

 2、Spring中事务传播机制的分类(7种)

Spring中默认的传播机制是Propagation.REQUIRED。

​​​​​​​【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制_第14张图片

3、 Spring 事务传播机制使用和部分场景演示 

controller中代码


@RestController
@RequestMapping("/trans1")
public class UserLogController {
    @Autowired
    private UserService userService;
    @Autowired
    private UserLogService userLogService;
    //事务定义
    @Transactional
    @RequestMapping("/addUser1")
    public boolean addUser1(String username,String password){
        //插入用户表
        User user = new User(username,password);
        userService.insert(user);
        //插入用户日志表
        UserLog userLog = new UserLog(username);
        userLogService.insertLog(userLog);
        return true;
    }
}

(1)设置为Required

情况1:当两个都设置为Required,两个代码都正常,则两个都执行成功。

【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制_第15张图片

【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制_第16张图片

【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制_第17张图片

 情况2:对userlogService中添加一条异常,另一个userService中的代码正常不做修改,此时观察两个表的情况:

【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制_第18张图片

【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制_第19张图片

情况3:在userLogService对发生的异常自己进行捕获并回滚,另一个代码正常不做修改,观察是否会影响另一个userinfo表。 

【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制_第20张图片

【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制_第21张图片

 总结:

(1)两个代码要么一起执行成功;

(2)要么当有一个发生异常,两个就都进行回滚;

(3)就算对其中一个发生的异常进行捕获并回滚处理,也是两个都进行回滚。

 (2)设置为REQUIRES_NEW

@Transactional(propagation = Propagation.REQUIRES_NEW)

情况1:当两个代码都正常的时候,数据都插入成功。

【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制_第22张图片 情况2:当有一个有异常的时候,不影响另一个。userLog表没有插入数据,userinfo表插入成功。

【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制_第23张图片

(3)设置为NEVER

给userLogService和userService都设置为NEVER级别,代码正常,也抛出异常。

@Transactional(propagation = Propagation.NEVER)

 (4)设置嵌套事务

@Transactional(propagation = Propagation.NESTED)

情况1:当userService和userLogService都正常的时候,执行成功。

【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制_第24张图片

情况2:当userLogService有异常的时候,都执行失败;

情况3:当userLogService有异常但是自动进行捕获并手动回滚的时候,uerLog表没有插入数据,但是userinfo表不受影响,数据插入成功。

【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制_第25张图片

问题1:嵌套事务NESTED和加入事务REQUIRED有什么区别?

(1)共同点:整个事务如果全部执行成功,二者的结果是⼀样的。
(2)不同点:如果事务执行到一半失败了,那么REQUIRED事务整个事务会全部回滚;而嵌套事务NESTED会局部回滚,不会影响上⼀个方法中执行的结果。


问题2:NESTED实现部分回滚的原因?

        嵌套事务只所以能够实现部分事务的回滚,是因为事务中有⼀个保存点(savepoint)的概念,嵌套事务进入之后相当于新建了⼀个保存点,回滚时只回滚到当前保存点,因此之前的事务是不受影响的;而REQUIRED 是加入到当前事务中,并没有创建事务的保存点,因此出现了回滚就是整个事务回滚,这就是嵌套事务和加入事务的区别。


【Spring事务学习】事务分类 && 隔离级别 && 事务传播机制_第26张图片 和七月就要说再见啦~

 

你可能感兴趣的:(Spring学习,spring,学习,数据库,mybatis,spring,boot,java)