MySQL 事务和 Spring 事务
MySQL 事务和 Spring 事务是数据库操作和 Java 应用开发中保证数据一致性的重要机制,二者既相关联又有区别。下面从概念、特性、实现方式等方面详细解析:
一、MySQL 事务
MySQL 事务是数据库层面的事务机制,用于保证一组数据库操作的原子性、一致性、隔离性和持久性(ACID 特性)。
1. 核心特性(ACID)
- 原子性(Atomicity):事务一组事务中的所有操作要么全部成功,要么全部失败回滚,不会出现部分执行的情况。
-- 示例:转账事务
START TRANSACTION;
UPDATE
UPDATE account SET balance = balance - 100 WHERE id = 1; -- A账户转出
UPDATE account SET balance = balance + 100 WHERE id = 2; -- B账户转入
COMMIT; -- 全部成功则提交
-- 若中间出错,执行 ROLLBACK; 回滚到事务开始前的状态
一致性(Consistency):事务执行前后,数据库从一个一致状态切换到另一个一致状态(如转账后总金额不变)。
隔离性(Isolation):多个事务并发执行时,彼此的操作相互隔离,避免干扰。MySQL 定义了 4 种隔离级别:
- 读未提交(Read Uncommitted):最低级别,可能读取到未提交的数据(脏读)。
- 读已提交(Read Committed):可避免脏读,但可能出现不可重复读。
- 可重复读(Repeatable Read):MySQL 默认级别,可避免脏读和不可重复读,但可能出现幻读。
- 串行化(Serializable):最高级别,完全串行执行,避免所有并发问题,但性能最差。
持久性(Durability):事务提交后,修改会永久保存到数据库,即使系统崩溃也不会丢失。
2. 事务控制语句
START TRANSACTION
/BEGIN
:开启事务。COMMIT
:提交事务,使修改永久生效。ROLLBACK
:回滚事务,撤销所有未提交的修改。SAVEPOINT <name>
:设置保存点,可回滚到指定点(而非整个事务)。SET TRANSACTION ISOLATION LEVEL <level>
:设置事务隔离级别。
3. 事务并发问题(隔离性要解决的核心)
当多个事务同时操作相同数据时,可能出现以下问题:
- 脏读:读取到其他事务未提交的修改(如 T1 修改数据未提交,T2 读取后 T1 回滚,T2 拿到无效数据)。
- 不可重复读:同一事务内多次读取同一数据,结果因其他事务提交而不同(如 T2 两次读 A 的余额,期间 T1 修改并提交,导致结果不一致)。
- 幻读:同一事务内多次范围查询,结果集行数因其他事务插入 / 删除而变化(如 T2 查询 “余额> 1000 的用户”,期间 T1 插入新用户并提交,导致 T2 再次查询多一行)。
- 丢失修改:两个事务同时修改数据,后提交的覆盖先提交的修改(如 T1 和 T2 同时读 A 的余额 = 1000,T1 先改为 500 提交,T2 基于 1000 改为 700 提交,最终 A=700,T1 的修改丢失)。
二、Spring 事务
Spring 事务是 Spring 框架提供的事务管理机制,它基于数据库事务,通过 AOP(面向切面编程)简化了事务控制,使开发者无需手动编写COMMIT
/ROLLBACK
等代码。
1. 核心作用
- 声明式事务管理:通过注解(如
@Transactional
)或 XML 配置声明事务规则,无需侵入业务代码。 - 统一事务管理:支持多种数据源(如 MySQL、Oracle)和事务 API(如 JDBC、Hibernate),提供一致的编程模型。
- 增强事务控制:扩展了数据库事务的功能,如事务传播行为、隔离级别设置、超时控制等。
2. 关键概念
事务传播行为:定义多个事务方法相互调用时,事务如何传递(如新建、复用、挂起等)。常用传播行为:
REQUIRED
(默认):如果当前有事务,则加入;否则新建事务。REQUIRES_NEW
:无论当前是否有事务,都新建一个事务(原事务挂起)。SUPPORTS
:如果当前有事务,则加入;否则以非事务方式执行。NESTED
:如果当前有事务,则在嵌套事务中执行;否则新建事务。
隔离级别:Spring 事务的隔离级别基于数据库隔离级别,通过
@Transactional(isolation = ...)
设置,对应 MySQL 的 4 种级别(如Isolation.READ_COMMITTED
)。事务回滚规则:默认情况下,Spring 事务在遇到未捕获的
RuntimeException
或Error
时回滚,对受检异常(如IOException
)不回滚。可通过rollbackFor
/noRollbackFor
自定义规则:@Transactional(rollbackFor = Exception.class) // 所有Exception都回滚 public void transfer() {// 业务逻辑 }
3. 实现方式
声明式事务(推荐):通过
@Transactional
注解标记需要事务管理的方法,Spring 自动在方法执行前开启事务,执行成功后提交,异常时回滚。@Service public class AccountService {@Autowiredprivate JdbcTemplate jdbcTemplate;@Transactional // 声明事务public void transfer(Long fromId, Long toId, int amount) {jdbcTemplate.update("UPDATE account SET balance = balance - ? WHERE id = ?", amount, fromId);jdbcTemplate.update("UPDATE account SET balance = balance + ? WHERE id = ?", amount, toId);} }
编程式事务:通过
TransactionTemplate
手动控制事务,灵活性高但侵入业务代码:@Service public class AccountService {@Autowiredprivate TransactionTemplate transactionTemplate;public void transfer(Long fromId, Long toId, int amount) {transactionTemplate.execute(status -> {jdbcTemplate.update("UPDATE account SET balance = balance - ? WHERE id = ?", amount, fromId);jdbcTemplate.update("UPDATE account SET balance = balance + ? WHERE id = ?", amount, toId);return null;});} }
三、MySQL 事务与 Spring 事务的关系
底层依赖:Spring 事务的本质是对数据库事务的封装。无论是声明式还是编程式事务,最终都通过数据库的
COMMIT
/ROLLBACK
完成事务控制。职责分工:
- MySQL 事务:负责底层数据的 ACID 保证,是事务的 “执行者”。
- Spring 事务:负责高层的事务管理(如传播行为、异常回滚规则),是事务的 “管理者”。
隔离级别的关联:Spring 事务的隔离级别需数据库支持(如设置
Isolation.READ_COMMITTED
,需 MySQL 的隔离级别至少为该级别)。若数据库不支持,Spring 会忽略设置或抛出异常。常见问题:
- 事务不生效:可能因
@Transactional
注解应用在非 public 方法、自调用(同一类中方法调用)、异常被捕获未抛出等原因导致。 - 传播行为误用:如使用
REQUIRES_NEW
可能导致事务独立提交,违背业务一致性需求。
- 事务不生效:可能因
总结
- MySQL 事务是基础,保证数据操作的原子性和一致性;
- Spring 事务是上层封装,简化事务管理,提供更灵活的控制;
- 实际开发中,需结合二者特性:通过 Spring 声明式事务简化代码,同时理解 MySQL 隔离级别和 ACID 特性,避免因底层机制导致的问题。