自学内容网 自学内容网

Spring实现事务二

.

在这里插入图片描述

上一次我们讲到,Spring实现事务的方式有两种,并且,为实现这两种方式,我们做了一些准备工作,那么接下来,我将带着大家,来继续学习事务的相关知识

编程式事务

SpringBoot内置了两个对象
DataSourceTransactionManager 事务管理器. 用来获取事务(开启事务), 提交或回滚事务
TransactionDefinition 是事务的属性, 在获取事务的时候需要将TransactionDefinition 传递进去从而获得⼀个事务TransactionStatus

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserInfoController {
    @Autowired
    private UserInfoService userInfoService;
    @Autowired
    // JDBC 事务管理器
    private DataSourceTransactionManager dataSourceTransactionManager;
    @Autowired
    // 定义事务属性
    private TransactionDefinition transactionDefinition;

    @RequestMapping("/login")
    public String login(String userName,String password){
        //开启事务
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
        Integer result = userInfoService.insert(userName,password);
        //提交事务
        dataSourceTransactionManager.commit(transactionStatus);
        //回滚事务
//        dataSourceTransactionManager.rollback(transactionStatus);
        if(result<=0){
            return  "添加失败";
        }else {
            return "添加成功";
        }
    }
}

运行程序: http://127.0.0.1:8080/login?userName=zhangsan&password=123456
可以看到,当我们的代码中使用事务提交的时候,数据库中确实被插入了userName=zhangsan&password=123456,但是如果采用事务回滚,那么既是打印的日志显示插入成功,数据库中却并没有新增数据

声明式事务@Transactional


@RestController
public class UserInfoController2 {
    @Autowired
    private UserInfoService userInfoService;
    @Transactional( isolation = READ_COMMITTED,rollbackFor = Exception.class)
    @RequestMapping("/login2")
    public String login(String userName,String password) throws IOException {
        Integer result = userInfoService.insert(userName,password);
        if(result<=0){
            return  "添加失败";
        }else {
            Integer i = 10/0;
            return "添加成功";
        }
    }
}

运行程序,可以看到,当程序出现异常过后,事务就会自动回滚,但是当不存在 Integer i = 10/0;这一句的时候,数据库就会正常新增数据.

@Transactional 作用

可以用来修饰方法或类:
• 修饰方法时: 只有修饰public 方法时才生效(修饰其他方法时不会报错, 也不生效)[推荐]
• 修饰类时: 对 @Transactional 修饰的类中所有的 public 方法都生效.
方法/类被 @Transactional 注解修饰时, 在目标方法执行开始之前, 会自动开启事务, 方法执行结束之后, 自动提交事务.
如果在方法执行过程中, 出现异常, 且异常未被捕获, 就进行事务回滚操作.
如果异常被程序捕获, 方法就被认为是成功执行, 依然会提交事务.

对上述代码进行修改过后,

@RestController
public class UserInfoController2 {
    @Autowired
    private UserInfoService userInfoService;
    @Transactional( isolation = READ_COMMITTED,rollbackFor = Exception.class)
    @RequestMapping("/login2")
    public String login(String userName,String password) throws IOException {
        Integer result = userInfoService.insert(userName,password);
        if(result<=0){
            return  "添加失败";
        }else {
               try {
                  Integer integer = 10/0;
               }catch (Exception e){
                  e.printStackTrace();
               }
            return "添加成功";
        }
    }
}

执行程序,可以看到,程序虽然报错,但是事务依旧提交成功了,数据库的数据已经成功新增了,如果在捕获了异常的情况下,想要将事务进行回滚,有以下两种方式

抛出异常

@RestController
public class UserInfoController2 {
    @Autowired
    private UserInfoService userInfoService;
    @Transactional
    @RequestMapping("/login2")
    public String login(String userName,String password){
        Integer result = userInfoService.insert(userName,password);
        if(result<=0){
            return  "添加失败";
        }else {
            try {
                Integer integer = 10/0;
            }catch (Exception e){
                throw e;
            }
            return "添加成功";
        }
    }
}

手动回滚

@RestController
public class UserInfoController2 {
    @Autowired
    private UserInfoService userInfoService;
    @Transactional
    @RequestMapping("/login2")
    public String login(String userName,String password){
        Integer result = userInfoService.insert(userName,password);
        if(result<=0){
            return  "添加失败";
        }else {
            try {
                Integer integer = 10/0;
            }catch (Exception e){
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            }
            return "添加成功";
        }
    }
}

@Transactional 详解

rollbackFor

: 异常回滚属性. 指定能够触发事务回滚的异常类型. 可以指定多个异常类型

可以看到,以上两种方式,都可以在捕获异常的情况下进行事务的回滚,但是有特殊情况,@RestController默认只在遇到运行时异常和Error时抛出异常才会回滚, 非运行时异常不回滚. 即Exception的子类中, 除了RuntimeException及其子类.
在这里插入图片描述
这里我们假设抛出一个IOException

@RestController
public class UserInfoController3 {
    @Autowired
    private UserInfoService userInfoService;
    @Transactional
    @RequestMapping("/login2")
    public String login(String userName,String password) throws IOException {
        Integer result = userInfoService.insert(userName,password);
        if(result<=0){
            return  "添加失败";
        }else {
            if (true){
                throw new IOException();
            }
            return "添加成功";
        }
    }
}

结果显示,虽然出现了异常,但事务仍旧提交了
如果我们需要所有异常都回滚, 需要来配置 @Transactional 注解当中的 rollbackFor 属性, 通
过 rollbackFor 这个属性指定出现何种异常类型时事务进行回滚

@RestController
public class UserInfoController3 {
    @Autowired
    private UserInfoService userInfoService;
    @Transactional(rollbackFor = {Exception.class,Error.class})
    @RequestMapping("/login2")
    public String login(String userName,String password) throws IOException {
        Integer result = userInfoService.insert(userName,password);
        if(result<=0){
            return  "添加失败";
        }else {
            if (true){
                throw new IOException();
            }
            return "添加成功";
        }
    }
}

结果显示,事务得到了回滚,数据库中并未添加数据

事务隔离级别

SQL 标准定义了四种隔离级别, MySQL 全都⽀持. 这四种隔离级别分别是:

  1. 读未提交(READ UNCOMMITTED): 读未提交, 也叫未提交读. 该隔离级别的事务可以看到其他事务中未提交的数据.因为其他事务未提交的数据可能会发⽣回滚, 但是该隔离级别却可以读到, 我们把该级别读到的数据称之为脏数据, 这个问题称之为脏读
  2. 读提交(READ COMMITTED): 读已提交, 也叫提交读. 该隔离级别的事务能读取到已经提交事务的数据,该隔离级别不会有脏读的问题.但由于在事务的执⾏中可以读取到其他事务提交的结果, 所以在不同时间的相同 SQL 查询可能会得到不同的结果, 这种现象叫做不可重复读
  3. 可重复读(REPEATABLE READ): 事务不会读到其他事务对已有数据的修改, 即使其他事务已提交. 也就可以确保同⼀事务多次查询的结果⼀致, 但是其他事务新插⼊的数据, 是可以感知到的. 这也就引发了幻读问题. 可重复读, 是 MySQL 的默认事务隔离级别.
    ⽐如此级别的事务正在执⾏时, 另⼀个事务成功的插⼊了某条数据, 但因为它每次查询的结果都是⼀样的, 所以会导致查询不到这条数据, ⾃⼰重复插⼊时⼜失败(因为唯⼀约束的原因). 明明在事务中查询不到这条信息,但⾃⼰就是插⼊不进去, 这个现象叫幻读.
  4. 串⾏化(SERIALIZABLE): 序列化, 事务最⾼隔离级别. 它会强制事务排序, 使之不会发⽣冲突, 从⽽解决了脏读, 不可重复读和幻读问题, 但因为执⾏效率低, 所以真正使⽤的场景并不多
    在数据库中通过以下 SQL 查询全局事务隔离级别和当前连接的事务隔离级别:
select @@global.tx_isolation,@@tx_isolation;

Spring 中事务隔离级别

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

通过以下代码进行实现
在这里插入图片描述


原文地址:https://blog.csdn.net/qq_65291053/article/details/135990016

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!