【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,"⽤⼾注册");
    }
}

此时就不会影响父事务


🙉本篇文章到此结束

相关推荐
重生之我是Java开发战士4 小时前
【数据结构】优先级队列(堆)
java·数据结构·算法
菜鸟233号4 小时前
力扣216 组合总和III java实现
java·数据结构·算法·leetcode
dodod20124 小时前
Ubuntu24.04.3执行sudo apt install yarnpkg 命令失败的原因
java·服务器·前端
Evan芙4 小时前
搭建 LNMT 架构并配置 Tomcat 日志管理与自动备份
java·架构·tomcat
青云交4 小时前
Java 大视界 -- Java+Spark 构建企业级用户画像平台:从数据采集到标签输出全流程(437)
java·开发语言·spark·hbase 优化·企业级用户画像·标签计算·高并发查询
铉铉这波能秀4 小时前
正则表达式从入门到精通(字符串模式匹配)
java·数据库·python·sql·正则表达式·模式匹配·表格处理
武子康4 小时前
Java-202 RabbitMQ 生产安装与容器快速启动:Erlang 兼容、RPM 部署与常用命令
java·消息队列·rabbitmq·erlang·java-rabbitmq·mq
a程序小傲5 小时前
米哈游Java后端面试被问:Spring Boot Starter的制作原理
java·spring boot·后端
Misnearch5 小时前
Mock服务是什么?
java·后端·微服务·mock
后端小张5 小时前
【JAVA 进阶】深入理解Sentinel:分布式系统的流量守卫者
java·开发语言·spring boot·后端·spring·spring cloud·sentinel