解决的痛点
在数据库事务操作中,经常会出现"事务未按预期生效"的情况。 其中最常见的两类场景是:
- 同类内部方法调用导致事务代理未生效
- 对事务边界的控制过度依赖 @Transactional 注解
下面举一个在开发中可能会遇到的一个事务失效的场景:
java
//保存父方法
public void saveParentMethod() {
//插入parent
StockInfo stockInfo = new StockInfo();
stockInfo.setProductId("parent");
this.stockInfoMapper.insertSelective(stockInfo);
//插入child
this.saveChildStockInfo();
}
//保存子方法
@Transactional(rollbackFor = Exception.class)
public void saveChildStockInfo() {
StockInfo stockInfo = new StockInfo();
stockInfo.setProductId("child");
this.stockInfoMapper.insertSelective(stockInfo);
//制造异常
int i = 1 / 0;
}
我们想当然的认为:parent应该入库而child不应该
然而实际情况是:child也入库了。
原因并不是事务失效,而是
@Transactional根本没有生效。 Spring 的事务是基于代理实现的,同类内部方法调用不会经过代理,因此事务注解不会被触发。
OK,TransactionHelper工具类,就是解决类似的情况,并且可以在一个类中使用private定义的私有方法,同样能进行事务。TransactionHelper 的核心作用是: 让事务控制从「依赖 Spring AOP 代理」转变为「显式、可控的代码级事务边界」。更适合以下场景:
- 复杂流程编排
- 同类内部方法调用
- 希望显式声明事务边界的位置
使用方式
Java
@Component
@RequiredArgsConstructor
public class YourClass {
// 构造注入TransactionHelper
private final TransactionHelper transactionHelper;
public Result<Void> executeBiz(TaskContext context) {
// ...
// 事务
transactionHelper.runInTransaction(() -> {
// 保存订单
saveOrder(order);
// 更新库存数量
updateStore(128);
});
return Result.ok();
}
// 保存订单
private void saveOrder(Order order) {
orderMapper.save(order)
}
// 更新库存数量
private void updateStore(int count) {
storeMapper.updateCount(count);
}
}
这里TransactionHelper充分利用了JDK8之后的函数式编程特点。把要进行事务的方法,当做参数传入。最终利用spring的PlatformTransactionManager统一控制事务行为。
注意:
runInTransaction 中的代码块不应再嵌套 @Transactional 注解, 否则容易造成事务传播语义混乱。
TransactionHelper庐山真面目
java
/**
* 事务帮助类
*
* @author 老马
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class TransactionHelper {
private final PlatformTransactionManager transactionManager;
/**
* -1 表示不覆盖 Spring 默认配置
*/
public static final int DEFAULT_TIMEOUT = TransactionDefinition.TIMEOUT_DEFAULT;
/**
* 原事务(REQUIRED)
*/
public <T> T runInTransaction(Supplier<T> supplier) {
return execute(TransactionDefinition.PROPAGATION_REQUIRED, DEFAULT_TIMEOUT, supplier);
}
public void runInTransaction(Runnable runnable) {
runInTransaction(DEFAULT_TIMEOUT, runnable);
}
public <T> T runInTransaction(int timeout, Supplier<T> supplier) {
return execute(TransactionDefinition.PROPAGATION_REQUIRED, timeout, supplier);
}
public void runInTransaction(int timeout, Runnable runnable) {
execute(TransactionDefinition.PROPAGATION_REQUIRED, timeout, () -> {
runnable.run();
return null;
});
}
/**
* 新事务(REQUIRES_NEW)
*/
public <T> T runInNewTransaction(Supplier<T> supplier) {
return execute(TransactionDefinition.PROPAGATION_REQUIRES_NEW, DEFAULT_TIMEOUT, supplier);
}
public void runInNewTransaction(Runnable runnable) {
runInNewTransaction(DEFAULT_TIMEOUT, runnable);
}
public <T> T runInNewTransaction(int timeout, Supplier<T> supplier) {
return execute(TransactionDefinition.PROPAGATION_REQUIRES_NEW, timeout, supplier);
}
public void runInNewTransaction(int timeout, Runnable runnable) {
execute(TransactionDefinition.PROPAGATION_REQUIRES_NEW, timeout, () -> {
runnable.run();
return null;
});
}
/**
* 通用事务执行逻辑
*/
private <T> T execute(int propagation, int timeout, Supplier<T> supplier) {
TransactionTemplate template = buildTemplate(propagation, timeout);
return template.execute(status -> {
try {
return supplier.get();
} catch (RuntimeException e) {
status.setRollbackOnly();
log.error("事务回滚:propagation={}, timeout={}", propagation, timeout, e);
throw e;
} catch (Exception e) {
status.setRollbackOnly();
log.error("事务回滚:propagation={}, timeout={}", propagation, timeout, e);
throw new RuntimeException(e);
} catch (Throwable t) {
status.setRollbackOnly();
log.error("事务回滚:propagation={}, timeout={}", propagation, timeout, t);
throw t;
}
});
}
/**
* 构建事务模板
*/
private TransactionTemplate buildTemplate(int propagation, int timeout) {
TransactionTemplate template = new TransactionTemplate(transactionManager);
template.setPropagationBehavior(propagation);
// timeout = -1 表示使用 Spring 默认配置
if (timeout != DEFAULT_TIMEOUT) {
template.setTimeout(timeout);
}
return template;
}
}
补充说明:
- 为确保 Error / 非 RuntimeException 场景下事务同样回滚, execute 方法中统一捕获 Throwable 并显式标记 rollbackOnly。
- TransactionHelper 不会吞异常, 异常会继续向上抛出,由上层统一处理, 以避免出现"事务回滚但业务误以为成功"的问题。
使用中特别注意
⚠ 特别注意(强约束):
TransactionHelper 基于 Spring 本地事务(PlatformTransactionManager),因此仅对【单一数据源】生效。如下代码是有效的:
java
@DS(TableNameConstant.DATABASE_NAME_RTPAY)
public void testTransaction() {
transactionHelper.runInTransaction(() -> {
bizServiceHelpler.insertMerchant();
bizServiceHelpler.insertCity();
});
}
在 DS 多数据源场景下:
- 同一个事务中 禁止切换数据源
- 不同数据源之间 无法实现原子回滚
如果业务需要跨数据源事务控制,请使用 DS 框架提供的 @DSTransactional,或明确拆分为多个独立事务处理。