事务工具类

解决的痛点

在数据库事务操作中,经常会出现"事务未按预期生效"的情况。 其中最常见的两类场景是:

  1. 同类内部方法调用导致事务代理未生效
  2. 对事务边界的控制过度依赖 @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;
    }
}

补充说明:

  1. 为确保 Error / 非 RuntimeException 场景下事务同样回滚, execute 方法中统一捕获 Throwable 并显式标记 rollbackOnly。
  2. TransactionHelper 不会吞异常, 异常会继续向上抛出,由上层统一处理, 以避免出现"事务回滚但业务误以为成功"的问题。

使用中特别注意

⚠ 特别注意(强约束):

TransactionHelper 基于 Spring 本地事务(PlatformTransactionManager),因此仅对【单一数据源】生效。如下代码是有效的:

java 复制代码
@DS(TableNameConstant.DATABASE_NAME_RTPAY)
public void testTransaction() {
    transactionHelper.runInTransaction(() -> {
        bizServiceHelpler.insertMerchant();
        bizServiceHelpler.insertCity();
    });
}

在 DS 多数据源场景下:

  • 同一个事务中 禁止切换数据源
  • 不同数据源之间 无法实现原子回滚

如果业务需要跨数据源事务控制,请使用 DS 框架提供的 @DSTransactional,或明确拆分为多个独立事务处理。

相关推荐
郝学胜-神的一滴6 小时前
超越Spring的Summer(一): PackageScanner 类实现原理详解
java·服务器·开发语言·后端·spring·软件构建
Tony Bai6 小时前
“Go 2,请不要发生!”:如果 Go 变成了“缝合怪”,你还会爱它吗?
开发语言·后端·golang
九.九6 小时前
CANN 算子生态的底层安全与驱动依赖:固件校验与算子安全边界的强化
大数据·数据库·安全
蓝帆傲亦6 小时前
代码革命!我用Claude Code 3个月完成1年工作量,这些实战经验全给你
jvm·数据库·oracle
亓才孓6 小时前
[JDBC]事务
java·开发语言·数据库
Victor3566 小时前
Hibernate(91)如何在数据库回归测试中使用Hibernate?
后端
PD我是你的真爱粉6 小时前
FastAPI使用tortoiseORM
数据库·fastapi
Victor3566 小时前
MongoDB(1)什么是MongoDB?
后端
Victor35613 小时前
https://editor.csdn.net/md/?articleId=139321571&spm=1011.2415.3001.9698
后端
Victor35613 小时前
Hibernate(89)如何在压力测试中使用Hibernate?
后端