跳到主要内容

事务管理

事务管理是企业级应用的核心功能,Spring 提供了强大且灵活的事务管理支持。本章详细介绍 Spring 事务管理的原理和使用方法。

什么是事务?

事务是一组操作的逻辑单元,这些操作要么全部成功,要么全部失败。事务具有四个关键特性,称为 ACID:

原子性(Atomicity):事务中的所有操作要么全部完成,要么全部不完成,不会停留在中间状态。

一致性(Consistency):事务执行前后,数据库从一个一致性状态转换到另一个一致性状态。

隔离性(Isolation):多个事务并发执行时,每个事务都感觉不到其他事务的存在。

持久性(Durability):事务完成后,对数据的修改是永久性的,即使系统故障也不会丢失。

Spring 事务管理概述

Spring 提供了统一的事务管理抽象,支持多种事务管理器:

事务管理器适用场景
DataSourceTransactionManagerJDBC、MyBatis
JpaTransactionManagerJPA
HibernateTransactionManagerHibernate
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);
}
}

placeOrderdecreaseStock 都标记了 @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() {
}

超时单位为秒,超过时间事务会自动回滚。

回滚规则

默认情况下,事务只在遇到 RuntimeExceptionError 时回滚。可以通过 rollbackFornoRollbackFor 自定义:

@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 事务管理:

  1. 事务基础:ACID 特性
  2. 声明式事务:@Transactional 注解的使用
  3. 传播行为:REQUIRED、REQUIRES_NEW、NESTED 等
  4. 隔离级别:READ_COMMITTED、REPEATABLE_READ 等
  5. 编程式事务:TransactionTemplate 和 PlatformTransactionManager
  6. 事务失效场景:非 public 方法、内部调用、异常捕获等
  7. 最佳实践:事务范围最小化、避免长事务等

下一章我们将学习 Spring 的数据访问支持。