Java并发编程之事务管理详解
关键词:Java 并发、事务管理、ACID、JDBC 事务、Spring 事务、多线程事务、分布式事务
✅ 摘要
在高并发系统中,事务的正确性和一致性是保障数据安全的关键。Java 提供了丰富的事务管理机制,包括 JDBC 原生事务控制、Spring 的声明式事务、以及基于 AOP 的事务增强 等方式。
本文将深入讲解 Java 中事务的核心概念、事务的 ACID 特性、事务的传播行为、事务失效场景、并发环境下的事务问题、Spring Boot 中的事务管理实践等内容,并提供大量 可运行的示例代码,帮助你全面掌握 Java 事务的使用方式与底层原理。
📌 一、什么是事务?
1.1 事务的基本定义
事务(Transaction)是一组数据库操作,这些操作要么全部成功执行,要么全部失败回滚,保证数据库状态的一致性。
1.2 事务的四大特性(ACID)
特性 | 描述 |
---|---|
A(Atomicity)原子性 | 事务是一个不可分割的工作单位,事务中的操作要么都做,要么都不做 |
C(Consistency)一致性 | 事务必须使数据库从一个一致性状态变换到另一个一致性状态 |
I(Isolation)隔离性 | 多个用户并发访问数据库时,数据库为每一个用户开启事务,不能被其他事务的操作干扰 |
D(Durability)持久性 | 事务一旦提交,对数据库的修改就是永久性的 |
📌 二、Java 中的事务实现方式
2.1 JDBC 原生事务控制
Connection conn = null;
try {conn = dataSource.getConnection();conn.setAutoCommit(false); // 关闭自动提交// 执行 SQL 操作PreparedStatement ps1 = conn.prepareStatement("UPDATE account SET balance = balance - 100 WHERE id = 1");ps1.executeUpdate();PreparedStatement ps2 = conn.prepareStatement("UPDATE account SET balance = balance + 100 WHERE id = 2");ps2.executeUpdate();conn.commit(); // 提交事务
} catch (SQLException e) {if (conn != null) {try {conn.rollback(); // 回滚事务} catch (SQLException ex) {ex.printStackTrace();}}e.printStackTrace();
} finally {if (conn != null) {try {conn.close();} catch (SQLException e) {e.printStackTrace();}}
}
📌 注意点:
- 默认情况下,JDBC 是自动提交模式(autoCommit = true)
- 使用事务时需手动关闭自动提交,并显式调用 commit 或 rollback
2.2 Spring 事务管理
Spring 提供了声明式事务管理能力,开发者只需通过注解即可完成事务控制。
示例:
@Service
public class AccountService {@Autowiredprivate AccountMapper accountMapper;@Transactionalpublic void transfer(Long fromId, Long toId, BigDecimal amount) {accountMapper.deduct(fromId, amount);accountMapper.add(toId, amount);// 模拟异常,触发事务回滚if (amount.compareTo(BigDecimal.ZERO) > 0) {throw new RuntimeException("转账失败");}}
}
📌 说明:
@Transactional
注解表示该方法需要事务管理- 如果方法中抛出未检查异常(如
RuntimeException
),Spring 会自动回滚事务 - 若抛出的是检查型异常(checked exception),默认不会回滚,除非指定
@Transactional(rollbackFor = Exception.class)
📌 三、Spring 事务的传播行为(Propagation)
Spring 支持多种事务传播行为,用于控制方法之间的事务边界。
传播行为 | 含义 |
---|---|
REQUIRED (默认) | 如果当前没有事务,则新建一个事务;如果已经存在事务,则加入该事务 |
REQUIRES_NEW | 总是新建事务,并挂起当前事务(如果有) |
SUPPORTS | 当前有事务则加入,没有则以非事务方式执行 |
NOT_SUPPORTED | 以非事务方式执行,挂起当前事务 |
MANDATORY | 必须存在事务,否则抛异常 |
NEVER | 不能存在事务,否则抛异常 |
NESTED | 在嵌套事务内执行,外部事务可以提交或回滚,内部事务不影响外部事务 |
示例:
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {// ...methodB(); // methodB 也具有事务
}@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {// 即使 methodA 有事务,methodB 也会开启新事务
}
📌 四、事务失效的常见原因及解决方案
原因 | 描述 | 解决方案 |
---|---|---|
自调用问题 | 同一个类中,methodA() 调用 methodB() ,而 methodB 有 @Transactional 注解,但事务不生效 | 使用 AopContext.currentProxy() 调用代理对象 |
异常被捕获未抛出 | 方法中捕获了异常但未重新抛出,导致 Spring 无法感知异常发生 | 抛出异常或手动设置回滚 |
方法不是 public | @Transactional 只能作用于 public 方法 | 修改为 public 方法 |
事务配置错误 | 数据源未正确配置事务管理器 | 检查 @EnableTransactionManagement 和 PlatformTransactionManager 配置 |
非 Spring 管理的 Bean | 手动 new 出来的对象无法被 Spring 管理事务 | 使用依赖注入获取 Bean |
📌 五、并发环境下事务的问题
5.1 丢失更新(Lost Update)
两个事务同时更新同一记录,后提交的事务覆盖了前者的更改。
✅ 解决方案:
- 加锁(悲观锁/乐观锁)
- 使用
@Version
实现乐观锁版本号机制
5.2 脏读(Dirty Read)
事务 A 读取到了事务 B 未提交的数据。
✅ 解决方案:
- 设置事务隔离级别为
READ COMMITTED
5.3 不可重复读(Non-Repeatable Read)
事务 A 在两次查询之间,事务 B 提交了更改,导致 A 查询结果不同。
✅ 解决方案:
- 设置事务隔离级别为
REPEATABLE READ
5.4 幻读(Phantom Read)
事务 A 在范围查询期间,事务 B 插入了新记录,导致 A 第二次查询出现“幻影”行。
✅ 解决方案:
- 设置事务隔离级别为
SERIALIZABLE
或使用间隙锁(Gap Lock)
📌 六、Spring Boot 中的事务配置
6.1 启用事务管理
@SpringBootApplication
@EnableTransactionManagement
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}
6.2 自定义事务管理器(适用于多数据源)
@Configuration
public class TransactionConfig {@Beanpublic PlatformTransactionManager transactionManager(DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}
}
📌 七、多线程下的事务问题
在多线程环境中,每个线程拥有独立的事务上下文,事务无法跨线程传播。
7.1 问题示例
@Transactional
public void multiThreadOperation() {new Thread(() -> {// 此处的数据库操作不在事务中accountMapper.updateBalance(...);}).start();
}
7.2 解决方案
- 将事务逻辑封装为异步任务,并由事务管理器统一调度
- 使用
TransactionTemplate
编程式事务 - 使用线程池配合
ThreadLocal
传递事务上下文(适用于复杂场景)
📌 八、分布式事务简介(简要)
在微服务架构下,多个服务之间操作同一个业务逻辑,就涉及分布式事务。
常见方案:
方案 | 特点 |
---|---|
2PC / XA 两阶段提交 | 强一致性,性能差,资源锁定时间长 |
TCC 补偿事务 | 最终一致性,开发成本高 |
Saga 模式 | 事件驱动,适合长周期流程 |
Seata 分布式事务框架 | 阿里开源,支持 AT 模式、TCC 模式等 |
消息队列 + 本地事务表 | 适用于最终一致性场景 |
✅ 总结
模块 | 内容 |
---|---|
事务基础 | ACID 特性、JDBC 事务控制 |
Spring 事务 | @Transactional 、传播行为、事务失效处理 |
事务并发问题 | 丢失更新、脏读、不可重复读、幻读 |
Spring Boot 配置 | 启用事务、多数据源事务管理 |
多线程事务 | 事务上下文隔离、线程池整合建议 |
分布式事务 | 2PC、TCC、Seata 简介 |
📚 参考资料
- Spring Framework 官方文档 - Transaction Management