【Spring 事务】核心概念与实战:从手动控制到注解自动事务

文章目录

    • 一、事务
      • [1.1 什么是事务](#1.1 什么是事务)
      • [1.2 为什么需要事务?](#1.2 为什么需要事务?)
      • [1.3 事务的操作​](#1.3 事务的操作)
    • [二、Spring 中事务的实现​](#二、Spring 中事务的实现)
      • [2.1 Spring 编程式事务](#2.1 Spring 编程式事务)
      • [2.2 Spring 声明式事务 @Transactional​](#2.2 Spring 声明式事务 @Transactional)
        • [2.2.1 @Transactional 作用​](#2.2.1 @Transactional 作用)
      • [2.3 @Transactional 详解​](#2.3 @Transactional 详解)
        • [2.3.1 rollbackFor​](#2.3.1 rollbackFor)

一、事务

1.1 什么是事务

事务是一组操作的集合,是一个不可分割的操作。​

事务会把所有的操作作为一个整体,一起向数据库提交或者是撤销操作请求。所以这组操作要么同时成功,要么同时失败。​

1.2 为什么需要事务?

在程序开发中,事务的核心价值是保证一组关联操作的原子性(要么全部成功,要么全部失败),避免因部分操作成功、部分操作失败导致的数据不一致问题。

  1. 解决 "部分操作成功" 导致的数据异常:在多步关联操作中,若没有事务控制,某一步执行失败后,已执行的步骤无法回滚,会造成数据逻辑错误。
  • 典型场景:转账操作核心逻辑是 "A 账户扣 100 元 + B 账户加 100 元"。若没有事务:A 账户扣钱成功后,B 账户加钱操作因网络波动、代码异常等失败,会导致 A 账户的 100 元凭空消失,资金数据不一致。
    若有事务:两步操作被视为一个整体,只要其中一步失败,整个事务回滚,A 账户的扣款会撤销,数据恢复到操作前状态。
  1. 保证业务逻辑的完整性:很多业务场景中,多个数据操作是 "强关联" 的,缺少任何一步都会导致业务逻辑断裂。
  2. 应对并发场景下的数据一致性问题:在多用户并发操作同一数据时,事务结合隔离级别(如读已提交、可重复读),可避免脏读、不可重复读、幻读等问题,保证每个用户看到的数据是可靠的。

1.3 事务的操作​

事务的操作主要有三步:​

  1. 开启事务:start transaction/ begin(一组操作前开启事务)
  2. 提交事务:commit(这组操作全部成功,提交事务)
  3. 回滚事务:rollback(这组操作中间任何一个操作出现异常,回滚事务)

二、Spring 中事务的实现​

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

  1. 编程式事务(手动写代码操作事务)。
  2. 声明式事务(利用注解自动开启和提交事务)。

2.1 Spring 编程式事务

Spring 手动操作事务和上面 MySQL 操作事务类似,有 3 个重要操作步骤:

  • 开启事务(获取事务)
  • 提交事务
  • 回滚事务

SpringBoot 内置了两个对象:​

  1. DataSourceTransactionManager 事务管理器:用来获取事务(开启事务)、提交或回滚事务。
  2. TransactionDefinition 是事务的属性,在获取事务的时候需要将 TransactionDefinition 传递进去从而获得一个事务 TransactionStatus。

代码实现:​

java 复制代码
@RequestMapping("/user")
@RestController
public class UserController {
    // JDBC 事务管理器
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;
    // 定义事务属性
    @Autowired
    private TransactionDefinition transactionDefinition;

    @Autowired
    private UserService userService;

    @RequestMapping("/registry")
    public String registry(String name, String password) {
        // 开启事务​
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
        //用户注册​
        userService.registryUser(name, password);
        //提交事务
        dataSourceTransactionManager.commit(transactionStatus);
        //回滚事务​
        //dataSourceTransactionManager.rollback(transactionStatus);
        return "注册成功";
    }
}

运行后观察数据库的结果,数据插入成功。​

观察事务回滚: dataSourceTransactionManager.rollback(transactionStatus);

运行后观察数据库,虽然程序返回"注册成功",但数据库并没有新增数据。​

2.2 Spring 声明式事务 @Transactional​

声明式事务的实现很简单,两步操作:​

  1. 添加依赖
xml 复制代码
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
</dependency>
  1. 在需要事务的方法上添加 @Transactional 注解就可以实现了。无需手动开启事务和提交事务,进入方法时自动开启事务,方法执行完会自动提交事务,如果中途发生了没有处理的异常会自动回滚事务。

代码实现:​

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

    @Transactional
    @RequestMapping("/registry")
    public String registry(String name, String password) {
        //用户注册​
        userService.registryUser(name, password);
        return "注册成功";
    }
}

运行程序,发现数据插入成功。

修改程序,使之出现异常​

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

    @Transactional
    @RequestMapping("/registry")
    public String registry(String name, String password) {
        //用户注册
        userService.registryUser(name, password);
        log.info("用户数据插入成功");
        //强制程序抛出异常
        int a = 10 / 0;
        return "注册成功";
    }
}

运行程序:​发现虽然日志显示数据插入成功,但数据库却没有新增数据,事务进行了回滚。

一般会在业务逻辑层当中来控制事务,因为在业务逻辑层当中,一个业务功能可能会包含多个数据访问的操作。在业务逻辑层来控制事务,我们就可以将多个数据访问操作控制在一个事务范围内。

2.2.1 @Transactional 作用​

@Transactional 可以用来修饰方法或类: ​

  • 修饰方法时:只有修饰public 方法时才生效(修饰其他方法时不会报错,也不生效)[推荐]
  • 修饰类时:对@Transactional 修饰的类中所有的 public 方法都生效。

方法/类被 @Transactional 注解修饰时,在目标方法执行开始之前,会自动开启事务,方法执行结束之后,自动提交事务。如果在方法执行过程中,出现异常,且异常未被捕获,就进行事务回滚操作。如果异常被程序捕获,方法就被认为是成功执行,依然会提交事务。

示例:对异常进行捕获​

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

    @Transactional
    @RequestMapping("/registry")
    public String registry(String name, String password) {
        //用户注册
        userService.registryUser(name, password);
        log.info("用户数据插入成功");
        //对异常进行捕获​
        try {
            //强制程序抛出异常​
            int a = 10 / 0;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "注册成功";
    }
}

运行程序,发现虽然程序出错了,但是由于异常被捕获了,所以事务依然得到了提交。​

如果需要事务进行回滚,有以下两种方式:​

  1. 重新抛出异常
java 复制代码
@Slf4j
@RequestMapping("/trans")
@RestController
public class TransactionalController {
    @Autowired
    private UserService userService;

    @Transactional
    @RequestMapping("/registry")
    public String registry(String name, String password) throws Exception {
        //用户注册
        userService.registryUser(name, password);
        log.info("用户数据插入成功");
        //对异常进行捕获​
        try {
            //强制程序抛出异常
            int a = 10 / 0;
        } catch (Exception e) {
            //将异常重新抛出去
            throw e;
        }
        return "注册成功";
    }
}
  1. 手动回滚事务
    使用 TransactionAspectSupport.currentTransactionStatus() 得到当前的事务,并使用 setRollbackOnly 设置回滚。
java 复制代码
@Slf4j
@RequestMapping("/trans")
@RestController
public class TransactionalController {
    @Autowired
    private UserService userService;

    @Transactional
    @RequestMapping("/registry")
    public String registry(String name, String password) {
        //用户注册
        userService.registryUser(name, password);
        log.info("用户数据插入成功");
        //对异常进行捕获​
        try {
            //强制程序抛出异常​
            int a = 10 / 0;
        } catch (Exception e) {
            // 手动回滚事务​
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return "注册成功";
    }
}

2.3 @Transactional 详解​

@Transactional 注解当中的三个常见属性:​

  1. rollbackFor:异常回滚属性。指定能够触发事务回滚的异常类型。可以指定多个异常类型
  2. Isolation:事务的隔离级别。默认值为Isolation.DEFAULT
  3. propagation:事务的传播机制。默认值为 Propagation.REQUIRED
2.3.1 rollbackFor​

@Transactional 默认只在遇到运行时异常和Error时才会回滚,非运行时异常不回滚。即 Exception的子类中,除了RuntimeException及其子类。

如果需要所有异常都回滚,需要来配置@Transactional 注解当中的rollbackFor 属性,通过rollbackFor 这个属性指定出现何种异常类型时事务进行回滚。

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

    @Transactional(rollbackFor = Exception.class)
    @RequestMapping("/r2")
    public String r2(String name, String password) throws IOException {
        //用户注册
        userService.registryUser(name, password);
        log.info("用户数据插入成功");
        if (true) {
            throw new IOException();
        }
        return "r2";
    }
}

运行程序,事务会回滚,数据库不会新增数据。​

结论:

  • 在Spring的事务管理中,默认只在遇到运行时异常RuntimeException和Error时才会回滚。
  • 如果需要回滚指定类型的异常,可以通过rollbackFor属性来指定。

至于Transactional 的另外两个属性:事务的隔离级别、事务的传播机制会在后面的章节进行介绍。

相关推荐
要开心吖ZSH2 小时前
Spring AI Alibaba 个人学习笔记
人工智能·学习·spring·spring ai·springaialibaba
马猴烧酒.2 小时前
【团队空间|第十一天】基础功能实现,RBAC权限控制,ShardingSphere详解
java·开发语言·数据库
fengxin_rou2 小时前
从 String 到 Zset:Redis 核心数据结构全解析及排行榜应用
java·开发语言·redis·多线程
世界尽头与你2 小时前
CVE-2025-55752_ Apache Tomcat 安全漏洞
java·安全·网络安全·渗透测试·tomcat·apache
Re.不晚2 小时前
Java进阶之路--线程最最详细讲解
java·开发语言
遨游xyz2 小时前
数据结构-栈
java·数据结构·算法
海南java第二人2 小时前
Flink动态字符串处理框架:构建灵活可配置的实时数据管道
java·flink
lbb 小魔仙2 小时前
MyBatis-Plus 系统化实战:从基础 CRUD 到高级查询与性能优化
java·性能优化·mybatis
BLUcoding2 小时前
Docker 离线安装和镜像源配置
java·docker·eureka