Spring 事务管理详解
一、知识概述
事务管理是企业级应用开发中不可或缺的部分,Spring 提供了强大且灵活的事务管理能力,支持声明式事务和编程式事务两种方式。声明式事务通过 AOP 实现,使得业务代码与事务管理代码完全分离,大大简化了开发。
Spring 事务管理的核心内容:
- 事务管理器:PlatformTransactionManager
- 事务定义:TransactionDefinition
- 事务传播行为:Propagation
- 事务隔离级别:Isolation
- 事务回滚规则:Rollback rules
理解 Spring 事务管理,是开发企业级 Java 应用的必备技能。
二、知识点详细讲解
2.1 事务的基本概念
ACID 特性
- 原子性(Atomicity):事务是不可分割的工作单位
- 一致性(Consistency):事务使数据库从一个一致状态变到另一个一致状态
- 隔离性(Isolation):多个事务并发执行时互不干扰
- 持久性(Durability):事务一旦提交,对数据库的改变是永久的
事务问题
- 脏读:读到其他事务未提交的数据
- 不可重复读:同一事务两次读取结果不同(数据被修改)
- 幻读:同一事务两次读取记录数不同(数据被增删)
2.2 Spring 事务抽象
核心接口
// 事务管理器
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition);
void commit(TransactionStatus status);
void rollback(TransactionStatus status);
}
// 事务定义
public interface TransactionDefinition {
int getPropagationBehavior(); // 传播行为
int getIsolationLevel(); // 隔离级别
int getTimeout(); // 超时时间
boolean isReadOnly(); // 是否只读
String getName(); // 事务名称
}
// 事务状态
public interface TransactionStatus {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
boolean isCompleted();
}
2.3 事务传播行为
| 传播行为 | 说明 |
|---|---|
| REQUIRED | 有事务则加入,无则新建(默认) |
| SUPPORTS | 有事务则加入,无则以非事务运行 |
| MANDATORY | 必须在事务中运行,否则抛异常 |
| REQUIRES_NEW | 总是新建事务,挂起当前事务 |
| NOT_SUPPORTED | 以非事务运行,挂起当前事务 |
| NEVER | 以非事务运行,有事务则抛异常 |
| NESTED | 有事务则嵌套事务,无则新建 |
传播行为详解
REQUIRED(默认):
调用者有事务 → 加入调用者事务
调用者无事务 → 新建事务
REQUIRES_NEW:
调用者有事务 → 挂起调用者事务,新建独立事务
调用者无事务 → 新建事务
NESTED:
调用者有事务 → 创建嵌套事务(savepoint)
调用者无事务 → 新建事务
2.4 事务隔离级别
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ_UNCOMMITTED | ✓ | ✓ | ✓ |
| READ_COMMITTED | ✗ | ✓ | ✓ |
| REPEATABLE_READ | ✗ | ✗ | ✓ |
| SERIALIZABLE | ✗ | ✗ | ✗ |
2.5 声明式事务
@Transactional 注解
@Transactional(
propagation = Propagation.REQUIRED, // 传播行为
isolation = Isolation.DEFAULT, // 隔离级别
timeout = -1, // 超时时间(秒)
readOnly = false, // 是否只读
rollbackFor = {}, // 回滚异常类型
noRollbackFor = {}, // 不回滚异常类型
value = "" // 事务管理器名称
)
回滚规则
- 默认:RuntimeException 和 Error 回滚
- checked 异常默认不回滚
- 通过 rollbackFor 指定回滚异常
2.6 事务失效场景
- 方法非 public
- 自调用问题:同类内部方法调用
- 异常被捕获:异常未抛出
- 异常类型错误:checked 异常未指定 rollbackFor
- 数据库不支持事务
- 事务传播行为设置错误
- 代理对象问题
三、代码示例
3.1 声明式事务基础
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
// 实体类
public class User {
private Long id;
private String username;
private BigDecimal balance;
public User() {}
public User(Long id, String username, BigDecimal balance) {
this.id = id;
this.username = username;
this.balance = balance;
}
// getter/setter
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public BigDecimal getBalance() { return balance; }
public void setBalance(BigDecimal balance) { this.balance = balance; }
@Override
public String toString() {
return "User{id=" + id + ", username='" + username + "', balance=" + balance + "}";
}
}
// DAO 层
@Repository
public class UserDao {
private final Map<Long, User> database = new ConcurrentHashMap<>();
public User findById(Long id) {
return database.get(id);
}
public void save(User user) {
database.put(user.getId(), user);
}
public void updateBalance(Long id, BigDecimal amount) {
User user = database.get(id);
if (user != null) {
user.setBalance(user.getBalance().add(amount));
}
}
}
// Service 层 - 声明式事务
@Service
public class UserService {
private final UserDao userDao;
public UserService(UserDao userDao) {
this.userDao = userDao;
}
// 基本事务配置
@Transactional
public void createUser(User user) {
userDao.save(user);
System.out.println("创建用户: " + user);
}
// 只读事务
@Transactional(readOnly = true)
public User getUser(Long id) {
return userDao.findById(id);
}
// 指定回滚异常
@Transactional(rollbackFor = Exception.class)
public void updateUser(User user) throws Exception {
userDao.save(user);
// checked 异常也会回滚
}
// 指定不回滚异常
@Transactional(noRollbackFor = BusinessException.class)
public void processUser(Long id) {
User user = userDao.findById(id);
// BusinessException 不会触发回滚
}
}
// 业务异常
public class BusinessException extends RuntimeException {
public BusinessException(String message) {
super(message);
}
}
3.2 事务传播行为示例
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
@Service
public class OrderService {
private final OrderDao orderDao;
private final InventoryService inventoryService;
private final PaymentService paymentService;
public OrderService(OrderDao orderDao, InventoryService inventoryService,
PaymentService paymentService) {
this.orderDao = orderDao;
this.inventoryService = inventoryService;
this.paymentService = paymentService;
}
// REQUIRED(默认):创建订单事务
@Transactional
public void createOrder(Order order) {
orderDao.save(order);
// 调用其他服务,加入当前事务
inventoryService.deductStock(order.getProductId(), order.getQuantity());
paymentService.processPayment(order.getUserId(), order.getAmount());
}
}
@Service
public class InventoryService {
private final InventoryDao inventoryDao;
public InventoryService(InventoryDao inventoryDao) {
this.inventoryDao = inventoryDao;
}
// REQUIRED:加入调用者事务
@Transactional(propagation = Propagation.REQUIRED)
public void deductStock(Long productId, int quantity) {
Inventory inventory = inventoryDao.findByProductId(productId);
if (inventory.getStock() < quantity) {
throw new RuntimeException("库存不足");
}
inventory.setStock(inventory.getStock() - quantity);
inventoryDao.save(inventory);
}
// REQUIRES_NEW:独立事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logInventoryChange(Long productId, int quantity, String operation) {
// 即使外部事务回滚,这条日志也会保存
InventoryLog log = new InventoryLog(productId, quantity, operation);
inventoryDao.saveLog(log);
}
}
@Service
public class PaymentService {
private final PaymentDao paymentDao;
private final NotificationService notificationService;
public PaymentService(PaymentDao paymentDao, NotificationService notificationService) {
this.paymentDao = paymentDao;
this.notificationService = notificationService;
}
@Transactional
public void processPayment(Long userId, BigDecimal amount) {
Payment payment = new Payment(userId, amount);
paymentDao.save(payment);
// 发送通知(独立事务)
notificationService.sendPaymentNotification(userId, amount);
}
}
@Service
public class NotificationService {
// NOT_SUPPORTED:不使用事务
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void sendPaymentNotification(Long userId, BigDecimal amount) {
// 通知发送不需要事务
System.out.println("发送支付通知: userId=" + userId + ", amount=" + amount);
}
}
// NESTED 示例
@Service
public class BatchProcessService {
private final ItemService itemService;
public BatchProcessService(ItemService itemService) {
this.itemService = itemService;
}
@Transactional
public void batchProcess(List<Long> itemIds) {
for (Long itemId : itemIds) {
try {
// 嵌套事务:单个失败不影响整体
itemService.processItem(itemId);
} catch (Exception e) {
// 记录失败,继续处理下一个
System.out.println("处理失败: " + itemId + ", 原因: " + e.getMessage());
}
}
}
}
@Service
public class ItemService {
@Transactional(propagation = Propagation.NESTED)
public void processItem(Long itemId) {
// 作为嵌套事务执行
// 如果失败,只回滚这个嵌套事务
// 外部事务可以继续
}
}
3.3 事务隔离级别示例
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.annotation.Isolation;
@Service
public class AccountService {
private final AccountDao accountDao;
public AccountService(AccountDao accountDao) {
this.accountDao = accountDao;
}
// 读已提交(Oracle 默认)
@Transactional(isolation = Isolation.READ_COMMITTED)
public Account getAccount(Long id) {
return accountDao.findById(id);
}
// 可重复读(MySQL 默认)
@Transactional(isolation = Isolation.REPEATABLE_READ)
public Account getAccountForReport(Long id) {
// 同一事务内多次读取结果一致
Account account1 = accountDao.findById(id);
// ... 其他操作
Account account2 = accountDao.findById(id);
// account1 == account2
return account2;
}
// 串行化(最高隔离级别)
@Transactional(isolation = Isolation.SERIALIZABLE)
public void criticalOperation(Long id) {
// 完全避免并发问题
// 但性能最低
}
}
// 脏读演示
@Service
public class DirtyReadDemo {
private final AccountDao accountDao;
public DirtyReadDemo(AccountDao accountDao) {
this.accountDao = accountDao;
}
// 事务A:修改但未提交
@Transactional
public void updateBalance(Long id, BigDecimal amount) throws InterruptedException {
Account account = accountDao.findById(id);
account.setBalance(account.getBalance().add(amount));
accountDao.save(account);
// 模拟长时间未提交
Thread.sleep(10000);
// 可能回滚
if (amount.compareTo(BigDecimal.ZERO) < 0) {
throw new RuntimeException("余额不足");
}
}
// READ_UNCOMMITTED:可能读到未提交数据
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public Account readUncommitted(Long id) {
// 可能读到脏数据
return accountDao.findById(id);
}
// READ_COMMITTED:只能读到已提交数据
@Transactional(isolation = Isolation.READ_COMMITTED)
public Account readCommitted(Long id) {
// 不会读到脏数据
return accountDao.findById(id);
}
}
3.4 事务回滚规则
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class TransferService {
private final AccountDao accountDao;
private final AuditService auditService;
public TransferService(AccountDao accountDao, AuditService auditService) {
this.accountDao = accountDao;
this.auditService = auditService;
}
// 默认:RuntimeException 回滚
@Transactional
public void transfer(Long fromId, Long toId, BigDecimal amount) {
Account from = accountDao.findById(fromId);
Account to = accountDao.findById(toId);
if (from.getBalance().compareTo(amount) < 0) {
throw new RuntimeException("余额不足"); // 回滚
}
from.setBalance(from.getBalance().subtract(amount));
to.setBalance(to.getBalance().add(amount));
accountDao.save(from);
accountDao.save(to);
}
// checked 异常默认不回滚
@Transactional
public void transferWithChecked(Long fromId, Long toId, BigDecimal amount)
throws InsufficientBalanceException {
Account from = accountDao.findById(fromId);
if (from.getBalance().compareTo(amount) < 0) {
throw new InsufficientBalanceException("余额不足"); // 不回滚!
}
// 数据已修改但不会回滚
}
// 指定 checked 异常回滚
@Transactional(rollbackFor = InsufficientBalanceException.class)
public void transferWithRollback(Long fromId, Long toId, BigDecimal amount)
throws InsufficientBalanceException {
Account from = accountDao.findById(fromId);
if (from.getBalance().compareTo(amount) < 0) {
throw new InsufficientBalanceException("余额不足"); // 会回滚
}
}
// 指定所有异常回滚
@Transactional(rollbackFor = Exception.class)
public void safeOperation() throws Exception {
// 任何异常都会回滚
}
// 指定特定异常不回滚
@Transactional(noRollbackFor = BusinessException.class)
public void businessOperation() {
// BusinessException 不会触发回滚
throw new BusinessException("业务异常");
}
}
// checked 异常
public class InsufficientBalanceException extends Exception {
public InsufficientBalanceException(String message) {
super(message);
}
}
3.5 编程式事务
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.beans.factory.annotation.Autowired;
@Service
public class ProgrammaticTransactionService {
private final TransactionTemplate transactionTemplate;
private final UserDao userDao;
// 方式1:使用 TransactionTemplate(推荐)
public ProgrammaticTransactionService(
PlatformTransactionManager transactionManager,
UserDao userDao) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
this.userDao = userDao;
}
public void createUserWithTemplate(User user) {
transactionTemplate.execute(status -> {
try {
userDao.save(user);
System.out.println("用户创建成功: " + user);
} catch (Exception e) {
status.setRollbackOnly();
System.out.println("用户创建失败,回滚: " + e.getMessage());
}
return null;
});
}
public User getUserWithResult(Long id) {
return transactionTemplate.execute(status -> {
User user = userDao.findById(id);
if (user == null) {
status.setRollbackOnly();
}
return user;
});
}
// 自定义事务配置
public void customTransaction(User user) {
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
transactionTemplate.setTimeout(30); // 30秒超时
transactionTemplate.executeWithoutResult(status -> {
userDao.save(user);
});
}
}
// 方式2:直接使用 PlatformTransactionManager
@Service
public class ManualTransactionService {
private final PlatformTransactionManager transactionManager;
private final UserDao userDao;
public ManualTransactionService(
PlatformTransactionManager transactionManager,
UserDao userDao) {
this.transactionManager = transactionManager;
this.userDao = userDao;
}
public void createUserManual(User user) {
// 定义事务
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("createUserTransaction");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// 获取事务状态
TransactionStatus status = transactionManager.getTransaction(def);
try {
userDao.save(user);
System.out.println("用户创建成功");
// 提交事务
transactionManager.commit(status);
} catch (Exception e) {
System.out.println("用户创建失败: " + e.getMessage());
// 回滚事务
transactionManager.rollback(status);
}
}
}
3.6 事务失效场景
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
@Service
public class TransactionFailDemo {
private final UserDao userDao;
public TransactionFailDemo(UserDao userDao) {
this.userDao = userDao;
}
// ❌ 失效场景1:方法非 public
@Transactional
private void privateMethod() {
// 事务不生效
}
// ❌ 失效场景2:自调用问题
public void outerMethod() {
// 直接调用同类方法,代理不生效
innerMethod(); // 事务不生效
}
@Transactional
public void innerMethod() {
userDao.save(new User(1L, "test", BigDecimal.ZERO));
}
// ✅ 解决方案:注入自身
@Autowired
private TransactionFailDemo self;
public void outerMethodFixed() {
self.innerMethod(); // 通过代理调用,事务生效
}
// ❌ 失效场景3:异常被捕获
@Transactional
public void caughtException() {
try {
userDao.save(new User(1L, "test", BigDecimal.ZERO));
throw new RuntimeException("异常");
} catch (Exception e) {
// 异常被捕获,事务不回滚
System.out.println("捕获异常: " + e.getMessage());
}
}
// ✅ 解决方案:重新抛出或手动回滚
@Transactional
public void rethrowException() {
try {
userDao.save(new User(1L, "test", BigDecimal.ZERO));
throw new RuntimeException("异常");
} catch (Exception e) {
System.out.println("捕获异常: " + e.getMessage());
throw e; // 重新抛出
}
}
// ❌ 失效场景4:错误的异常类型
@Transactional
public void checkedException() throws Exception {
userDao.save(new User(1L, "test", BigDecimal.ZERO));
throw new Exception("checked 异常"); // 默认不回滚
}
// ✅ 解决方案:指定 rollbackFor
@Transactional(rollbackFor = Exception.class)
public void checkedExceptionFixed() throws Exception {
userDao.save(new User(1L, "test", BigDecimal.ZERO));
throw new Exception("checked 异常"); // 现在会回滚
}
// ❌ 失效场景5:finally 中清理数据
@Transactional
public void finallyBlock() {
try {
userDao.save(new User(1L, "test", BigDecimal.ZERO));
} finally {
// finally 在事务提交前执行
userDao.delete(1L); // 可能导致问题
}
}
}
3.7 多事务管理器
import org.springframework.context.annotation.*;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration
@EnableTransactionManagement
public class MultiTransactionConfig {
@Bean
@Primary
public DataSource primaryDataSource() {
return createDataSource("primary_db");
}
@Bean
public DataSource secondaryDataSource() {
return createDataSource("secondary_db");
}
@Bean
@Primary
public PlatformTransactionManager primaryTransactionManager(DataSource primaryDataSource) {
return new DataSourceTransactionManager(primaryDataSource);
}
@Bean
public PlatformTransactionManager secondaryTransactionManager(DataSource secondaryDataSource) {
return new DataSourceTransactionManager(secondaryDataSource);
}
private DataSource createDataSource(String dbName) {
// 创建数据源
return null; // 简化
}
}
// 使用指定的事务管理器
@Service
public class MultiDataSourceService {
private final PrimaryDao primaryDao;
private final SecondaryDao secondaryDao;
public MultiDataSourceService(PrimaryDao primaryDao, SecondaryDao secondaryDao) {
this.primaryDao = primaryDao;
this.secondaryDao = secondaryDao;
}
// 使用主数据源事务
@Transactional("primaryTransactionManager")
public void operatePrimary() {
primaryDao.save(new User(1L, "primary", BigDecimal.ZERO));
}
// 使用从数据源事务
@Transactional("secondaryTransactionManager")
public void operateSecondary() {
secondaryDao.save(new User(1L, "secondary", BigDecimal.ZERO));
}
// 分布式事务:需要特殊处理
@Transactional("primaryTransactionManager")
public void operateBoth() {
primaryDao.save(new User(1L, "primary", BigDecimal.ZERO));
// 这里的操作不在同一事务中!
secondaryDao.save(new User(1L, "secondary", BigDecimal.ZERO));
}
}
四、实战应用场景
4.1 银行转账
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import java.math.BigDecimal;
@Service
public class BankTransferService {
private final AccountDao accountDao;
private final TransferRecordDao recordDao;
private final NotificationService notificationService;
public BankTransferService(AccountDao accountDao,
TransferRecordDao recordDao,
NotificationService notificationService) {
this.accountDao = accountDao;
this.recordDao = recordDao;
this.notificationService = notificationService;
}
/**
* 银行转账 - 完整事务示例
*/
@Transactional(rollbackFor = Exception.class)
public TransferResult transfer(Long fromAccountId, Long toAccountId, BigDecimal amount) {
// 1. 查询账户
Account fromAccount = accountDao.findById(fromAccountId);
Account toAccount = accountDao.findById(toAccountId);
if (fromAccount == null || toAccount == null) {
throw new BusinessException("账户不存在");
}
// 2. 校验余额
if (fromAccount.getBalance().compareTo(amount) < 0) {
throw new BusinessException("余额不足");
}
// 3. 校验账户状态
if (!fromAccount.isActive() || !toAccount.isActive()) {
throw new BusinessException("账户状态异常");
}
// 4. 执行转账
fromAccount.setBalance(fromAccount.getBalance().subtract(amount));
toAccount.setBalance(toAccount.getBalance().add(amount));
accountDao.update(fromAccount);
accountDao.update(toAccount);
// 5. 记录转账流水
TransferRecord record = new TransferRecord();
record.setFromAccountId(fromAccountId);
record.setToAccountId(toAccountId);
record.setAmount(amount);
record.setStatus(TransferStatus.SUCCESS);
record.setCreateTime(new Date());
recordDao.save(record);
// 6. 发送通知(独立事务)
notificationService.sendTransferNotification(fromAccountId, toAccountId, amount);
return new TransferResult(true, "转账成功", record.getId());
}
/**
* 批量转账
*/
@Transactional(rollbackFor = Exception.class)
public BatchTransferResult batchTransfer(List<TransferRequest> requests) {
int successCount = 0;
int failCount = 0;
List<String> errors = new ArrayList<>();
for (TransferRequest request : requests) {
try {
transfer(request.getFromAccountId(),
request.getToAccountId(),
request.getAmount());
successCount++;
} catch (Exception e) {
failCount++;
errors.add(String.format("转账失败: from=%d, to=%d, 原因=%s",
request.getFromAccountId(),
request.getToAccountId(),
e.getMessage()));
}
}
return new BatchTransferResult(successCount, failCount, errors);
}
}
// 账户实体
public class Account {
private Long id;
private String accountNo;
private BigDecimal balance;
private boolean active;
// getter/setter
}
// 转账记录
public class TransferRecord {
private Long id;
private Long fromAccountId;
private Long toAccountId;
private BigDecimal amount;
private TransferStatus status;
private Date createTime;
// getter/setter
}
public enum TransferStatus {
PENDING, SUCCESS, FAILED
}
// 结果类
public class TransferResult {
private boolean success;
private String message;
private Long recordId;
public TransferResult(boolean success, String message, Long recordId) {
this.success = success;
this.message = message;
this.recordId = recordId;
}
// getter
}
4.2 订单处理
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
@Service
public class OrderProcessService {
private final OrderDao orderDao;
private final InventoryService inventoryService;
private final PaymentService paymentService;
private final CouponService couponService;
private final PointsService pointsService;
public OrderProcessService(OrderDao orderDao,
InventoryService inventoryService,
PaymentService paymentService,
CouponService couponService,
PointsService pointsService) {
this.orderDao = orderDao;
this.inventoryService = inventoryService;
this.paymentService = paymentService;
this.couponService = couponService;
this.pointsService = pointsService;
}
/**
* 创建订单 - 复杂事务处理
*/
@Transactional(rollbackFor = Exception.class)
public Order createOrder(OrderRequest request) {
// 1. 创建订单
Order order = new Order();
order.setUserId(request.getUserId());
order.setProductId(request.getProductId());
order.setQuantity(request.getQuantity());
order.setAmount(calculateAmount(request));
order.setStatus(OrderStatus.CREATED);
order.setCreateTime(new Date());
orderDao.save(order);
// 2. 扣减库存
try {
inventoryService.deductStock(request.getProductId(), request.getQuantity());
} catch (InsufficientStockException e) {
throw new BusinessException("库存不足");
}
// 3. 使用优惠券
if (request.getCouponId() != null) {
try {
couponService.useCoupon(request.getUserId(), request.getCouponId(), order.getId());
} catch (CouponException e) {
throw new BusinessException("优惠券使用失败: " + e.getMessage());
}
}
// 4. 处理支付
try {
paymentService.processPayment(order.getId(), order.getAmount());
order.setStatus(OrderStatus.PAID);
} catch (PaymentException e) {
order.setStatus(OrderStatus.PAYMENT_FAILED);
throw new BusinessException("支付失败: " + e.getMessage());
}
// 5. 增加积分(独立事务,失败不影响主流程)
try {
pointsService.addPoints(request.getUserId(), order.getAmount(), order.getId());
} catch (Exception e) {
// 积分失败不影响订单
System.out.println("积分增加失败: " + e.getMessage());
}
orderDao.update(order);
return order;
}
/**
* 取消订单
*/
@Transactional(rollbackFor = Exception.class)
public void cancelOrder(Long orderId) {
Order order = orderDao.findById(orderId);
if (order == null) {
throw new BusinessException("订单不存在");
}
if (order.getStatus() == OrderStatus.CANCELLED) {
throw new BusinessException("订单已取消");
}
if (order.getStatus() == OrderStatus.COMPLETED) {
throw new BusinessException("已完成的订单不能取消");
}
// 恢复库存
inventoryService.restoreStock(order.getProductId(), order.getQuantity());
// 退款
if (order.getStatus() == OrderStatus.PAID) {
paymentService.refund(orderId, order.getAmount());
}
// 恢复优惠券
if (order.getCouponId() != null) {
couponService.restoreCoupon(order.getCouponId());
}
// 扣除积分
pointsService.deductPoints(order.getUserId(), order.getAmount());
// 更新订单状态
order.setStatus(OrderStatus.CANCELLED);
order.setCancelTime(new Date());
orderDao.update(order);
}
private BigDecimal calculateAmount(OrderRequest request) {
// 计算金额逻辑
return BigDecimal.ZERO;
}
}
// 订单状态
public enum OrderStatus {
CREATED, // 已创建
PAID, // 已支付
SHIPPED, // 已发货
COMPLETED, // 已完成
CANCELLED, // 已取消
PAYMENT_FAILED // 支付失败
}
4.3 数据一致性保障
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.retry.annotation.Retryable;
import org.springframework.retry.annotation.Backoff;
@Service
public class DataConsistencyService {
private final OrderDao orderDao;
private final OutboxDao outboxDao;
public DataConsistencyService(OrderDao orderDao, OutboxDao outboxDao) {
this.orderDao = orderDao;
this.outboxDao = outboxDao;
}
/**
* Outbox 模式 - 保证数据一致性
* 将业务操作和消息发送放在同一事务中
*/
@Transactional(rollbackFor = Exception.class)
public void createOrderWithOutbox(Order order) {
// 1. 保存订单
orderDao.save(order);
// 2. 保存消息到 Outbox 表(同一事务)
OutboxMessage message = new OutboxMessage();
message.setAggregateType("Order");
message.setAggregateId(order.getId().toString());
message.setEventType("OrderCreated");
message.setPayload(serializeOrder(order));
message.setCreateTime(new Date());
outboxDao.save(message);
// 后台任务会扫描 Outbox 表并发送消息
}
/**
* 幂等性处理
*/
@Transactional(rollbackFor = Exception.class)
public void processOrderWithIdempotency(String requestId, Order order) {
// 检查是否已处理
if (orderDao.existsByRequestId(requestId)) {
System.out.println("请求已处理,跳过: " + requestId);
return;
}
// 设置请求ID
order.setRequestId(requestId);
orderDao.save(order);
}
/**
* 乐观锁 - 防止并发修改
*/
@Transactional(rollbackFor = Exception.class)
public boolean updateWithOptimisticLock(Long orderId, OrderUpdate update, int version) {
Order order = orderDao.findById(orderId);
if (order == null) {
throw new BusinessException("订单不存在");
}
// 版本检查
if (order.getVersion() != version) {
System.out.println("版本不匹配,当前版本: " + order.getVersion() + ", 请求版本: " + version);
return false;
}
// 更新订单
order.setStatus(update.getStatus());
order.setVersion(version + 1);
orderDao.update(order);
return true;
}
/**
* 重试机制
*/
@Retryable(
value = { OptimisticLockingFailureException.class },
maxAttempts = 3,
backoff = @Backoff(delay = 100, multiplier = 2)
)
@Transactional(rollbackFor = Exception.class)
public void updateWithRetry(Long orderId, OrderUpdate update) {
Order order = orderDao.findById(orderId);
if (order == null) {
throw new BusinessException("订单不存在");
}
order.setStatus(update.getStatus());
orderDao.update(order);
}
private String serializeOrder(Order order) {
// 序列化逻辑
return "";
}
}
// Outbox 消息
public class OutboxMessage {
private Long id;
private String aggregateType;
private String aggregateId;
private String eventType;
private String payload;
private Date createTime;
// getter/setter
}
五、事务最佳实践
5.1 事务边界设计
@Service
public class TransactionBoundaryDemo {
/**
* ✅ 正确:事务边界清晰
*/
@Transactional(rollbackFor = Exception.class)
public void goodExample(Long userId, OrderRequest request) {
// 所有数据库操作在一个事务中
User user = userDao.findById(userId);
Order order = createOrder(request);
updateInventory(request);
processPayment(order);
}
/**
* ❌ 错误:事务范围过大
*/
@Transactional(rollbackFor = Exception.class)
public void badExample(Long userId, OrderRequest request) {
// 包含了不必要的远程调用
User user = userDao.findById(userId);
// 远程调用可能很慢,不应该在事务中
UserInfo remoteInfo = remoteService.getUserInfo(userId);
Order order = createOrder(request);
updateInventory(request);
// 发送邮件也不应该在事务中
emailService.sendEmail(user.getEmail(), "订单创建成功");
}
/**
* ✅ 正确:分离事务和非事务操作
*/
public void correctExample(Long userId, OrderRequest request) {
// 远程调用放在事务外
UserInfo remoteInfo = remoteService.getUserInfo(userId);
// 只有必要的数据库操作在事务中
processOrderInTransaction(userId, request);
// 通知操作放在事务外
emailService.sendEmail(remoteInfo.getEmail(), "订单创建成功");
}
@Transactional(rollbackFor = Exception.class)
public void processOrderInTransaction(Long userId, OrderRequest request) {
User user = userDao.findById(userId);
Order order = createOrder(request);
updateInventory(request);
processPayment(order);
}
}
5.2 只读事务优化
@Service
public class ReadOnlyTransactionDemo {
/**
* ✅ 读操作使用只读事务
*/
@Transactional(readOnly = true)
public User getUser(Long id) {
return userDao.findById(id);
}
/**
* ✅ 报表查询使用只读事务
*/
@Transactional(readOnly = true)
public List<OrderReport> getOrderReport(Date startDate, Date endDate) {
return orderDao.findReportByDateRange(startDate, endDate);
}
/**
* ✅ 分页查询
*/
@Transactional(readOnly = true)
public Page<User> getUserList(int page, int size) {
return userDao.findAll(PageRequest.of(page, size));
}
/**
* ❌ 错误:只读事务中进行写操作
*/
@Transactional(readOnly = true)
public void wrongExample(User user) {
userDao.save(user); // 会抛出异常
}
}
5.3 事务超时设置
@Service
public class TransactionTimeoutDemo {
/**
* 设置事务超时
*/
@Transactional(timeout = 10) // 10秒超时
public void longRunningOperation() {
// 如果执行超过10秒,事务会自动回滚
}
/**
* 批量操作设置较长超时
*/
@Transactional(timeout = 300) // 5分钟超时
public void batchProcess(List<Long> ids) {
for (Long id : ids) {
processItem(id);
}
}
}
六、总结与最佳实践
最佳实践清单
-
事务边界
- 事务范围尽可能小
- 远程调用、文件操作放在事务外
- 只包含必要的数据库操作
-
传播行为选择
- 默认使用 REQUIRED
- 独立事务使用 REQUIRES_NEW
- 嵌套事务使用 NESTED
-
隔离级别选择
- 大多数场景使用默认
- 特殊场景按需设置
- 性能与一致性权衡
-
回滚规则
- 指定 rollbackFor = Exception.class
- 明确异常处理策略
- 避免异常被吞掉
-
性能优化
- 读操作使用 readOnly = true
- 合理设置超时时间
- 避免长事务
常见问题
-
事务不生效
- 检查方法是否为 public
- 检查是否存在自调用
- 检查异常是否被正确抛出
-
死锁问题
- 按固定顺序访问资源
- 减小事务范围
- 设置合理的超时时间
-
性能问题
- 避免长事务
- 使用只读事务
- 优化 SQL 语句
Spring 事务管理是企业级 Java 开发的核心技能。理解事务的传播行为、隔离级别、回滚规则,以及避免常见的陷阱,能够帮助我们构建出数据一致、性能优良的企业级应用。