在Spring Boot中实现跨请求的事务管理是一个复杂的需求,因为HTTP协议是无状态的,而数据库事务通常绑定在一个请求的上下文中。不过,有几种方案可以实现类似的效果:
方案1:使用编程式事务 + 状态存储(推荐)
1.1 手动控制事务边界
@Service
public class DistributedTransactionService {
@Autowired
private PlatformTransactionManager transactionManager;
@Autowired
private TransactionTemplate transactionTemplate;
// 存储事务状态
private Map<String, TransactionStatus> transactionStore = new ConcurrentHashMap<>();
/**
* 第一阶段:开始事务并保存状态
*/
public String startPendingTransaction() {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = transactionManager.getTransaction(def);
String transactionId = UUID.randomUUID().toString();
transactionStore.put(transactionId, status);
// 执行业务操作
// yourBusinessLogic();
return transactionId;
}
/**
* 第二阶段:提交事务
*/
public void commitTransaction(String transactionId) {
TransactionStatus status = transactionStore.get(transactionId);
if (status != null) {
try {
// 执行业务验证
if (validateBusiness()) {
transactionManager.commit(status);
} else {
transactionManager.rollback(status);
}
} finally {
transactionStore.remove(transactionId);
}
}
}
/**
* 回滚事务
*/
public void rollbackTransaction(String transactionId) {
TransactionStatus status = transactionStore.get(transactionId);
if (status != null) {
transactionManager.rollback(status);
transactionStore.remove(transactionId);
}
}
}
方案2:使用Saga模式(分布式事务)
2.1 添加依赖
<dependency>
<groupId>io.eventuate.tram.sagas</groupId>
<artifactId>eventuate-tram-sagas-spring-orchestration-simple-dsl</artifactId>
<version>0.21.0.RELEASE</version>
</dependency>
2.2 实现Saga协调器
public class TwoPhaseTransactionSaga implements SimpleSaga<TwoPhaseTransactionData> {
@Autowired
private TransactionService transactionService;
@Override
public SagaDefinition<SimpleSagaDsl<TwoPhaseTransactionData>, TwoPhaseTransactionData> getSagaDefinition() {
return Saga
.step()
.invokeLocal(this::beginTransaction)
.withCompensation(this::rollbackTransaction)
.step()
.invokeLocal(this::validateAndCommit)
.build();
}
private void beginTransaction(TwoPhaseTransactionData data) {
// 开始事务,但不提交
transactionService.beginPendingTransaction(data.getTransactionId());
}
private void validateAndCommit(TwoPhaseTransactionData data) {
// 等待其他请求验证
boolean isValid = waitForValidation(data.getTransactionId());
if (isValid) {
transactionService.commitTransaction(data.getTransactionId());
} else {
throw new RuntimeException("Validation failed");
}
}
private void rollbackTransaction(TwoPhaseTransactionData data) {
transactionService.rollbackTransaction(data.getTransactionId());
}
}
方案3:使用数据库SAVEPOINT(适用于同一连接)
@Service
public class SavepointTransactionService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private DataSource dataSource;
public void executeWithPendingCommit() {
Connection conn = null;
Savepoint savepoint = null;
try {
conn = DataSourceUtils.getConnection(dataSource);
conn.setAutoCommit(false); // 关闭自动提交
// 创建保存点
savepoint = conn.setSavepoint("pending_transaction");
// 执行第一阶段操作
jdbcTemplate.update("INSERT INTO table1 ...");
jdbcTemplate.update("UPDATE table2 ...");
// 此时事务未提交,等待其他请求
// 在另一个请求中验证并提交
// validateAndCommit(conn, savepoint);
} catch (Exception e) {
if (savepoint != null) {
conn.rollback(savepoint);
}
throw new RuntimeException(e);
}
}
/**
* 在另一个请求中调用
*/
public void validateAndCommit(Connection conn, Savepoint savepoint) {
try {
// 执行业务验证
boolean isValid = validateBusinessLogic();
if (isValid) {
conn.commit(); // 提交事务
} else {
conn.rollback(savepoint); // 回滚到保存点
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (conn != null) {
try {
conn.setAutoCommit(true);
conn.close();
} catch (SQLException e) {
// 处理异常
}
}
}
}
}
方案4:使用消息队列实现最终一致性
@Service
public class MessageBasedTransactionService {
@Autowired
private JmsTemplate jmsTemplate;
@Autowired
private TransactionalEventPublisher eventPublisher;
/**
* 第一阶段:发布事件但不提交
*/
@Transactional
public void phaseOne(String businessData) {
// 保存业务数据
businessRepository.save(businessData);
// 发布事件(在事务提交后才发送)
eventPublisher.publishEvent(new TransactionPendingEvent(businessData));
}
/**
* 监听事件,执行第二阶段
*/
@JmsListener(destination = "transaction.pending")
public void handlePendingTransaction(TransactionPendingEvent event) {
// 执行业务验证
boolean isValid = validateBusiness(event.getData());
if (isValid) {
// 执行提交操作
commitTransaction(event.getTransactionId());
} else {
// 执行回滚操作
rollbackTransaction(event.getTransactionId());
}
}
}
方案5:使用状态机管理事务状态
@Service
public class StateMachineTransactionService {
public enum TransactionState {
PENDING, VALIDATING, COMMITTED, ROLLBACKED
}
public enum TransactionEvent {
VALIDATE, COMMIT, ROLLBACK
}
@Transactional
public String startTransactionWithStateMachine() {
String transactionId = UUID.randomUUID().toString();
// 创建状态机
StateMachine<TransactionState, TransactionEvent> stateMachine =
stateMachineFactory.getStateMachine(transactionId);
// 初始状态为PENDING
stateMachine.sendEvent(TransactionEvent.VALIDATE);
// 执行业务操作
executeBusinessLogic();
// 返回事务ID供后续查询
return transactionId;
}
@Transactional
public void commitTransaction(String transactionId) {
StateMachine<TransactionState, TransactionEvent> stateMachine =
stateMachineFactory.getStateMachine(transactionId);
if (stateMachine.getState().getId() == TransactionState.VALIDATING) {
stateMachine.sendEvent(TransactionEvent.COMMIT);
// 执行实际的提交逻辑
executeCommit();
}
}
}
最佳实践建议
-
考虑使用成熟的分布式事务框架:
-
Seata
-
Atomikos
-
Narayana
-
-
设计注意事项:
// 1. 设置超时机制 @Transactional(timeout = 300) // 5分钟超时 // 2. 使用异步处理 @Async public CompletableFuture<String> processAsync() { // 异步处理 } // 3. 添加幂等性处理 public void processWithIdempotent(String idempotentKey) { if (processedKeys.contains(idempotentKey)) { return; // 已处理过 } // 执行业务逻辑 processedKeys.add(idempotentKey); } -
清理机制:
@Component
public class TransactionCleanupScheduler {@Scheduled(fixedDelay = 60000) // 每分钟清理一次 public void cleanupPendingTransactions() { // 清理超时的事务 transactionStore.entrySet().removeIf(entry -> isTransactionTimeout(entry.getValue()) ); }}
注意事项
-
事务隔离级别:长时间挂起的事务可能导致锁竞争
-
连接池限制:挂起的事务会占用数据库连接
-
超时处理:需要设置合理的超时时间
-
异常处理:确保异常情况下能正确清理资源
-
分布式环境:在集群环境下需要共享事务状态
根据你的具体场景选择最合适的方案,对于简单的需求,方案1(编程式事务)通常足够;对于复杂的分布式场景,建议使用Saga模式或成熟的分布式事务框架。