【JavaEE】Spring(9):Spring事务


一、Spring实现事务

这里说的事务就是在数据库中提到的事务,本篇主要学习如何使用Spring实现事务

1.1 Spring声明式事务 @Transactional

通过@Transactional来操作事务

1. 添加依赖:

XML 复制代码
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-tx</artifactId>
</dependency>

2. 在需要的方法上添加 @Transactional ,这样在进入方法时就会开启事务,方法执行完成后自动提交事务,如果在方法执行的过程中出现异常,且该异常没有被捕获就会触发事务回滚

以代码为例

java 复制代码
@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @Transactional
    @RequestMapping("/registry")
    public String registry(String name, String password) throws IOException {

        //用户注册
        userService.registryUser(name, password);
        log.info("用户数据插入成功");

        //强制程序抛出异常
        int a = 10/0;

        return "注册成功";
    }
}

我们在上述代码中故意写了异常,此时运行程序,就会触发事务回滚;虽然日志中显示用户数据插入成功,但实际上并未插入成功

@Transactional的作用

@Transactional可以修饰类和方法:

  • 修饰 public方法时 @Transactional才会生效,如果修饰的不是public方法则不会生效,但也不会报错
  • 修饰类时,该类的所有public方法都会生效

前面说过程序中抛出异常,且异常没有被捕获,则会触发事务回滚

java 复制代码
@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @Transactional
    @RequestMapping("/registry")
    public String registry(String name, String password) throws IOException {

        //用户注册
        userService.registryUser(name, password);
        log.info("用户数据插入成功");

        try {
            int a = 10/0;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return "注册成功";
    }
}

此时再运行程序,事务就会提交成功,不会触发事务回滚

那如果异常被捕获后,仍然想触发事务回滚该怎么办,有两种解决方案:

1. 重新抛出异常

java 复制代码
try {
    int a = 10/0;
} catch (Exception e) {
    throw e;
}

2. 手动回滚事务

java 复制代码
try {
    int a = 10/0;
} catch (Exception e) {
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}

通过 TransactionAspectSupport.currentTransactionStatus() 获取到当前事务,然后通过setRollbackOnly();回滚事务

二、 @Transactional 的三大属性

2.1 rollbackFor

rollbackFor可以指定触发事务回滚的异常类型,@Transactional默认只有运行时异常Error时才会触发事务回滚,当抛出非运行时异常时,不会触发事务回滚

修改代码,使其抛出IOException(非运行时异常):

java 复制代码
@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @Transactional
    @RequestMapping("/registry")
    public String registry(String name, String password) throws IOException {

        //用户注册
        userService.registryUser(name, password);
        log.info("用户数据插入成功");

        if (true) {
            throw new IOException();
        }
    
        return "注册成功";
    }
}

运行程序,虽然抛出异常,但是事务提交成功

如果面对IOException这种非运行时异常,也想触发事务回滚,就可以通过配置rollbackFor来完成

java 复制代码
@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @Transactional(rollbackFor = Exception.class)
    @RequestMapping("/registry")
    public String registry(String name, String password) throws IOException {

        //用户注册
        userService.registryUser(name, password);
        log.info("用户数据插入成功");

        if (true) {
            throw new IOException();
        }
    
        return "注册成功";
    }
}

修改成上述代码后,程序中只要抛出Exception及其子类的异常,都会触发事务回滚

2.2 isolation

通过isolations属性可以设置事务的隔离级别

Spring中事务隔离级别有5种:

  • Isolation.DEFAULT:以连接的数据库的事务隔离级别为主
  • Isolation.READ_UNCOMMITTED:读未提交,对应SQL标准中 READ UNCOMMITTED
  • Isolation.READ_COMMITTED:读已提交,对应SQL标准中 READ COMMITTED
  • Isolation.REPEATABLE_READ:可重复读,对应SQL标准中 REPEATABLE READ
  • Isolation.SERIALIZABLE:串行化,对应SQL标准中 SERIALIZABLE
java 复制代码
@Transactional(isolation = Isolation.READ_COMMITTED);

2.3 事务传播机制

2.3.1 什么是事务传播机制

事务传播机制:当多个事务方法存在调用关系时,比如方法A的执行过程中需要调用方法B,此时方法B是加入A的事务还是创建自己的事务?事务传播机制就是处理这样的问题的

2.3.2 有哪些事务传播机制

  • Propagation.REQUIRED:默认的事务传播级别,如果当前存在事务,则加入该事务,如果没有则新建一个事务
  • Propagation.SUPPORTS:如果当前存在事务,则加入该事务,如果没有则以非事务的方式运行
  • Propagation.MANDATORY:如果当前存在事务,则加入该事务,如果没有则抛出异常
  • Propagation.REQUIRES_NEW:无论当前有没有事务,都新建一个事务,如果当前有事务则将当前这个事务挂起(不用)
  • Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,则将当前的事务挂起(不用)
  • Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常
  • Propagation.NESTED:如果当前存在事务,则新建一个事务作为当前事务的嵌套事务来运行,如果当前没有事务,则该取值等价于Propagation.REQUIRED

2.3.3 事务传播机制代码演示

Propagation.REQUIRED

用户注册

java 复制代码
@RequestMapping("/propaga")
@RestController
public class PropagationController {

    @Autowired
    private UserService userService;

    @Autowired
    private LogService logService;

    @Transactional(propagation = Propagation.REQUIRED)
    @RequestMapping("/p1")
    public String r3(String name,String password){
        //用户注册
        userService.registryUser(name,password);
        //记录操作⽇志
        logService.insertLog(name,"用户注册");
        return "r3";
    }
}

用户注册包含插入用户数据和记录日志两个操作:

java 复制代码
@Slf4j
@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;
    @Transactional(propagation = Propagation.REQUIRED)
    public void registryUser(String name,String password){
        //插⼊⽤⼾信息
        userInfoMapper.insert(name,password);
    }
}
java 复制代码
@Slf4j
@Service
public class LogService {

    @Autowired
    private LogInfoMapper logInfoMapper;

    @Transactional(propagation = Propagation.REQUIRED)
    public void insertLog(String name,String op){
        int a=10/0;
        //记录⽤⼾操作
        logInfoMapper.insertLog(name,"⽤⼾注册");
    }
}

运行数据库,发现没有插入数据

流程描述:

  1. p1方法开始事务

  2. 用户注册,插入一条数据,执行成功(和p1使用同⼀个事务)

  3. 记录操作日志,插⼊⼀条数据,出现异常,执行失败(和p1使用同⼀个事务)

  4. 因为步骤3出现异常,事务回滚,步骤2和3使用同⼀个事务,所以步骤2的数据也回滚了

最终所有操作都没有执行成功

REQUIRES_NEW

将UserService和LogService中的方法的事务传播机制改为REQUIRES_NEW:

java 复制代码
@Slf4j
@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void registryUser(String name,String password){
        //插⼊⽤⼾信息
        userInfoMapper.insert(name,password);
    }
}
java 复制代码
@Slf4j
@Service
public class LogService {

    @Autowired
    private LogInfoMapper logInfoMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void insertLog(String name,String op){
        int a=10/0;
        //记录⽤⼾操作
        logInfoMapper.insertLog(name,"⽤⼾注册");
    }
}

用户数据插入成功,日志数据插入失败,因为这两个方法都新建了事务,LogService事务抛出异常不影响UserService事务

NESTED

java 复制代码
@Slf4j
@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;
    @Transactional(propagation = Propagation.NESTED)
    public void registryUser(String name,String password){
        //插⼊⽤⼾信息
        userInfoMapper.insert(name,password);
    }
}
java 复制代码
@Slf4j
@Service
public class LogService {

    @Autowired
    private LogInfoMapper logInfoMapper;

    @Transactional(propagation = Propagation.NESTED)
    public void insertLog(String name,String op){
        int a=10/0;
        //记录⽤⼾操作
        logInfoMapper.insertLog(name,"⽤⼾注册");
    }
}

运行程序,发现没有数据插入

流程描述:

  1. Controller中p1方法开始事务

  2. UserService用户注册,插入⼀条数据(嵌套p1事务)

  3. LogService 记录操作日志,插⼊⼀条数据,出现异常,执行失败(嵌套p1事务,回滚当前事务,数据添加失败)

  4. 由于是嵌套事务,LogService出现异常之后,往上找调⽤它的⽅法和事务,所以用户注册也失败了

  5. 最终结果是两个数据都没有添加

p1事务可以认为是父事务,嵌套事务是子事务,父事务出现异常,子事务也会回滚,子事务出现异常,如果不进行处理,也会导致父事务回滚

NESTED可以实现部分事务回滚(捕获异常后,手动事务回滚):

java 复制代码
@Service
public class LogService {
    @Autowired
    private LogInfoMapper logInfoMapper;
    @Transactional(propagation = Propagation.NESTED)
    public void insertLog(String name,String op){
        try {
            int a=10/0;
        } catch (Exception e){
            //回滚当前事务
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        //记录⽤⼾操作
        logInfoMapper.insertLog(name,"⽤⼾注册");
    }
}

此时就不会影响父事务


🙉本篇文章到此结束

相关推荐
莫非技术栈11 分钟前
Spring Boot统一异常拦截实践指南
java·spring boot·后端
程序员黄同学2 小时前
Java 中的 Spring 框架,以及 Spring Boot 和 Spring Cloud 的区别?
java·spring boot·spring
夕阳下的一片树叶9133 小时前
后端生成二维码
java
fajianchen3 小时前
图文并茂-jvm内存模型
java·jvm·算法
fly spider3 小时前
wait/notify/join/设计模式
java·开发语言·设计模式
m0_748254883 小时前
Spring Boot框架知识总结(超详细)
java·spring boot·后端
极乐丶醉卧沙场4 小时前
获取程序运行目录 (jar运行目录)
java·后端·jar
jonyleek4 小时前
「JVS更新日志」生产计划排程系统APS已上线!以及智能BI、低代码、规则引擎2.6更新说明
java·大数据·低代码·数据分析·软件需求
wanghao6664554 小时前
JVM面试题总结(题目来源JavaGuide)
java·jvm·面试