事务管理
事务管理是企业级应用的核心功能,Spring 提供了强大且灵活的事务管理支持。本章详细介绍 Spring 事务管理的原理和使用方法。
什么是事务?
事务是一组操作的逻辑单元,这些操作要么全部成功,要么全部失败。事务具有四个关键特性,称为 ACID:
原子性(Atomicity):事务中的所有操作要么全部完成,要么全部不完成,不会停留在中间状态。
一致性(Consistency):事务执行前后,数据库从一个一致性状态转换到另一个一致性状态。
隔离性(Isolation):多个事务并发执行时,每个事务都感觉不到其他事务的存在。
持久性(Durability):事务完成后,对数据的修改是永久性的,即使系统故障也不会丢失。
Spring 事务管理概述
Spring 提供了统一的事务管理抽象,支持多种事务管理器:
| 事务管理器 | 适用场景 |
|---|---|
| DataSourceTransactionManager | JDBC、MyBatis |
| JpaTransactionManager | JPA |
| HibernateTransactionManager | Hibernate |
| JtaTransactionManager | 分布式事务 |
Spring 事务管理的核心优势:
统一编程模型:无论使用哪种数据访问技术,事务管理代码都一样。
声明式事务:通过注解或 XML 配置声明事务,无需编写事务控制代码。
编程式事务:提供灵活的编程式事务控制 API。
与 Spring 生态集成:与 Spring 的 IOC、AOP 无缝集成。
声明式事务
声明式事务是 Spring 推荐的事务管理方式,通过 @Transactional 注解声明事务边界。
启用事务管理
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
@Bean
public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("password");
return dataSource;
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
Spring Boot 项目中,只需添加依赖,事务管理器会自动配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
@Transactional 基本用法
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private AccountDao accountDao;
@Transactional
public void registerUser(User user) {
userDao.insert(user);
Account account = new Account();
account.setUserId(user.getId());
accountDao.create(account);
}
}
registerUser 方法被事务包裹,如果任何一步失败,整个操作都会回滚。
事务传播行为
传播行为定义了事务方法之间如何相互影响:
| 传播行为 | 说明 |
|---|---|
| REQUIRED(默认) | 有事务则加入,无事务则新建 |
| SUPPORTS | 有事务则加入,无事务则以非事务方式执行 |
| MANDATORY | 必须在事务中执行,否则抛出异常 |
| REQUIRES_NEW | 总是新建事务,挂起当前事务 |
| NOT_SUPPORTED | 以非事务方式执行,挂起当前事务 |
| NEVER | 以非事务方式执行,有事务则抛出异常 |
| NESTED | 有事务则创建嵌套事务 |
@Service
public class UserService {
@Transactional(propagation = Propagation.REQUIRED)
public void method1() {
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void method2() {
}
@Transactional(propagation = Propagation.NESTED)
public void method3() {
}
}
REQUIRED 示例:
@Service
public class OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private InventoryService inventoryService;
@Transactional
public void placeOrder(Order order) {
orderDao.save(order);
inventoryService.decreaseStock(order.getProductId(), order.getQuantity());
}
}
@Service
public class InventoryService {
@Transactional
public void decreaseStock(Long productId, int quantity) {
inventoryDao.decreaseStock(productId, quantity);
}
}
placeOrder 和 decreaseStock 都标记了 @Transactional,但 decreaseStock 会加入 placeOrder 的事务,而不是创建新事务。
REQUIRES_NEW 示例:
@Service
public class OrderService {
@Autowired
private LogService logService;
@Transactional
public void placeOrder(Order order) {
orderDao.save(order);
logService.logOrder(order);
if (order.isInvalid()) {
throw new RuntimeException("订单无效");
}
}
}
@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logOrder(Order order) {
logDao.save(new OrderLog(order));
}
}
即使 placeOrder 回滚,logOrder 的日志记录也会独立提交。
隔离级别
隔离级别定义了一个事务与其他事务的隔离程度:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ_UNCOMMITTED | 可能 | 可能 | 可能 |
| READ_COMMITTED | 不可能 | 可能 | 可能 |
| REPEATABLE_READ | 不可能 | 不可能 | 可能 |
| SERIALIZABLE | 不可能 | 不可能 | 不可能 |
@Service
public class UserService {
@Transactional(isolation = Isolation.READ_COMMITTED)
public User getUser(Long id) {
return userDao.findById(id);
}
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void updateUser(User user) {
userDao.update(user);
}
}
脏读:读取到其他事务未提交的数据。
不可重复读:同一事务中两次读取同一数据,结果不同(其他事务修改了数据)。
幻读:同一事务中两次查询结果集不同(其他事务插入或删除了数据)。
只读事务
@Transactional(readOnly = true)
public List<User> getAllUsers() {
return userDao.findAll();
}
只读事务可以优化数据库性能,某些数据库会进行优化。但注意,只读事务中执行写操作不会自动报错,只是提示数据库可以进行优化。
超时设置
@Transactional(timeout = 30)
public void longRunningOperation() {
}
超时单位为秒,超过时间事务会自动回滚。
回滚规则
默认情况下,事务只在遇到 RuntimeException 和 Error 时回滚。可以通过 rollbackFor 和 noRollbackFor 自定义:
@Transactional(rollbackFor = Exception.class)
public void method1() throws Exception {
}
@Transactional(rollbackFor = {IOException.class, SQLException.class})
public void method2() throws IOException, SQLException {
}
@Transactional(noRollbackFor = IllegalArgumentException.class)
public void method3() {
}
类级别注解
@Transactional 可以放在类级别,作用于所有 public 方法:
@Service
@Transactional
public class UserService {
public void save(User user) {
}
public void delete(Long id) {
}
@Transactional(readOnly = true)
public User findById(Long id) {
return userDao.findById(id);
}
}
方法级别的注解会覆盖类级别的配置。
编程式事务
编程式事务提供更细粒度的事务控制,适用于复杂场景。
使用 TransactionTemplate
@Service
public class UserService {
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private UserDao userDao;
public void registerUser(User user) {
transactionTemplate.execute(status -> {
try {
userDao.insert(user);
accountDao.create(new Account(user.getId()));
} catch (Exception e) {
status.setRollbackOnly();
throw e;
}
return null;
});
}
public User getUserWithTransaction(Long id) {
return transactionTemplate.execute(status -> {
return userDao.findById(id);
});
}
}
使用 PlatformTransactionManager
@Service
public class UserService {
@Autowired
private PlatformTransactionManager transactionManager;
@Autowired
private UserDao userDao;
public void registerUser(User user) {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("registerUserTransaction");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = transactionManager.getTransaction(def);
try {
userDao.insert(user);
accountDao.create(new Account(user.getId()));
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
}
编程式事务 vs 声明式事务
| 特性 | 声明式事务 | 编程式事务 |
|---|---|---|
| 代码简洁性 | 简洁 | 繁琐 |
| 灵活性 | 较低 | 较高 |
| 适用场景 | 大多数场景 | 复杂事务逻辑 |
| 可读性 | 好 | 一般 |
推荐优先使用声明式事务,只有在需要细粒度控制时才使用编程式事务。
事务失效场景
了解事务失效的场景对于正确使用事务至关重要。
1. 方法非 public
@Transactional 只对 public 方法有效:
@Service
public class UserService {
@Transactional
private void save(User user) {
}
}
2. 同类内部调用
同一类中方法互调不经过代理:
@Service
public class UserService {
public void methodA() {
methodB();
}
@Transactional
public void methodB() {
}
}
解决方案:
@Service
public class UserService {
@Autowired
private UserService self;
public void methodA() {
self.methodB();
}
@Transactional
public void methodB() {
}
}
3. 异常被捕获
异常被捕获后没有重新抛出:
@Service
public class UserService {
@Transactional
public void save(User user) {
try {
userDao.insert(user);
accountDao.create(new Account(user.getId()));
} catch (Exception e) {
log.error("保存失败", e);
}
}
}
解决方案:重新抛出异常或手动回滚:
@Transactional
public void save(User user) {
try {
userDao.insert(user);
accountDao.create(new Account(user.getId()));
} catch (Exception e) {
log.error("保存失败", e);
throw new RuntimeException(e);
}
}
4. 异常类型不匹配
默认只回滚 RuntimeException,受检异常不会回滚:
@Service
public class UserService {
@Transactional
public void save(User user) throws IOException {
userDao.insert(user);
throw new IOException("IO异常");
}
}
解决方案:指定 rollbackFor:
@Transactional(rollbackFor = Exception.class)
public void save(User user) throws IOException {
}
5. 数据库不支持事务
某些数据库或存储引擎不支持事务,如 MySQL 的 MyISAM 引擎。
6. 事务传播行为设置不当
@Service
public class UserService {
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void save(User user) {
}
}
NOT_SUPPORTED 表示以非事务方式执行。
事务最佳实践
1. 事务范围最小化
事务应该只包含必要的数据库操作,避免在事务中执行耗时操作:
@Transactional
public void processOrder(Order order) {
orderDao.save(order);
inventoryService.decreaseStock(order.getProductId(), order.getQuantity());
sendEmail(order);
}
private void sendEmail(Order order) {
}
将 sendEmail 移出事务,避免邮件发送失败导致事务回滚。
2. 合理设置隔离级别
根据业务需求选择合适的隔离级别,不要一味追求最高隔离级别:
@Transactional(isolation = Isolation.READ_COMMITTED)
public User getUser(Long id) {
return userDao.findById(id);
}
3. 避免长事务
长事务会占用数据库连接,影响系统性能:
@Transactional
public void batchInsert(List<User> users) {
for (User user : users) {
userDao.insert(user);
}
}
大数据量操作应该分批处理:
public void batchInsert(List<User> users) {
List<List<User>> batches = Lists.partition(users, 1000);
for (List<User> batch : batches) {
insertBatch(batch);
}
}
@Transactional
public void insertBatch(List<User> batch) {
for (User user : batch) {
userDao.insert(user);
}
}
4. 只读事务用于查询
@Transactional(readOnly = true)
public List<User> findAll() {
return userDao.findAll();
}
5. 合理使用 REQUIRES_NEW
只在确实需要独立事务的场景使用:
@Transactional
public void placeOrder(Order order) {
orderDao.save(order);
logService.saveLog(order);
}
@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(Order order) {
logDao.save(new OrderLog(order));
}
}
小结
本章详细介绍了 Spring 事务管理:
- 事务基础:ACID 特性
- 声明式事务:@Transactional 注解的使用
- 传播行为:REQUIRED、REQUIRES_NEW、NESTED 等
- 隔离级别:READ_COMMITTED、REPEATABLE_READ 等
- 编程式事务:TransactionTemplate 和 PlatformTransactionManager
- 事务失效场景:非 public 方法、内部调用、异常捕获等
- 最佳实践:事务范围最小化、避免长事务等
下一章我们将学习 Spring 的数据访问支持。