You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring中如何验证JdbcClient执行的事务是否成功

Spring中如何验证JdbcClient执行的事务是否成功

我最近也碰到过完全一样的问题:用Spring的JdbcClient执行包含两个INSERT的事务SQL,明明在pgAdmin里确认两行数据都插进去了,但代码里拿到的返回值res却一直是0,根本没法用这个值判断事务到底成没成功。先看看我当时的代码,和你的几乎一模一样:

String query = """
    BEGIN TRANSACTION;
    INSERT INTO usuario ( email, senha, nome, status ) VALUES ( :email, :senha, :nome, 'Ativo' );
    INSERT INTO organizador ( id, cpf_cnpj ) VALUES ( ( SELECT us.id FROM usuario AS us WHERE us.email = :email AND us.senha = :senha ), :cpf );
    COMMIT;
""";
int res = jdbcClient
    .sql(query)
    .param("email", email)
    .param("senha", senha)
    .param("nome", nome)
    .param("cpf", cpf)
    .update();
System.out.println("result: " + res);
return res == 2;

为什么返回值是0?

其实原因很直白:当你执行包含BEGIN/COMMIT的复合SQL语句时,JdbcClient的update()方法只会返回最后一条语句的影响行数。而最后一条是COMMIT,这条语句本身不会影响任何数据行,所以返回0是正常的,但这就导致我们没法通过返回行数判断事务是否成功。

几种实用解决办法

1. 用Spring自带的事务管理(最推荐)

Spring本身提供了完善的事务管理机制,不管是声明式(@Transactional注解)还是编程式(TransactionTemplate),都比自己写BEGIN/COMMIT更规范,而且能通过异常和返回值清晰判断事务状态。

比如用编程式事务的例子:

// 先注入TransactionTemplate
@Autowired
private TransactionTemplate transactionTemplate;

// 执行事务操作
Boolean transactionSuccess = transactionTemplate.execute(status -> {
    // 插入usuario表,用RETURNING直接获取生成的id,比后续查询更高效
    Long usuarioId = jdbcClient.sql("INSERT INTO usuario ( email, senha, nome, status ) VALUES ( :email, :senha, :nome, 'Ativo' ) RETURNING id")
            .param("email", email)
            .param("senha", senha)
            .param("nome", nome)
            .query(Long.class)
            .single();
    
    // 插入organizador表
    int organizadorRows = jdbcClient.sql("INSERT INTO organizador ( id, cpf_cnpj ) VALUES ( :id, :cpf )")
            .param("id", usuarioId)
            .param("cpf", cpf)
            .update();
    
    // 确认第二个插入操作成功(影响1行)
    return organizadorRows == 1;
});

// 如果transactionSuccess为true,说明事务完全成功
return Boolean.TRUE.equals(transactionSuccess);

要是用声明式事务更简单,只要给方法加个@Transactional注解,然后在方法里依次执行两个INSERT操作,只要方法没抛出异常,事务就成功了。

2. 修改SQL,返回受影响的总行数

如果你一定要自己写事务SQL,可以在最后加一条查询,返回两个INSERT的影响行数之和。利用PostgreSQL的GET DIAGNOSTICS来获取每个语句的影响行数:

BEGIN TRANSACTION;
INSERT INTO usuario ( email, senha, nome, status ) VALUES ( :email, :senha, :nome, 'Ativo' );
GET DIAGNOSTICS @rows1 = ROW_COUNT;
INSERT INTO organizador ( id, cpf_cnpj ) VALUES ( ( SELECT us.id FROM usuario AS us WHERE us.email = :email AND us.senha = :senha ), :cpf );
GET DIAGNOSTICS @rows2 = ROW_COUNT;
COMMIT;
SELECT @rows1 + @rows2 AS total_rows;

然后代码里用query()方法获取这个总和:

Integer totalRows = jdbcClient.sql(query)
        .param("email", email)
        .param("senha", senha)
        .param("nome", nome)
        .param("cpf", cpf)
        .query(Integer.class)
        .single();
return totalRows == 2;

不过这里要注意:用SELECT us.id的方式获取用户id有风险,如果存在相同email和senha的用户,可能拿到错误的id,还是推荐用RETURNING子句获取刚插入的id更可靠。

3. 通过捕获异常判断

不管你用哪种方式,只要事务执行失败,JdbcClient都会抛出DataAccessException(Spring对JDBC异常的封装)。所以可以用try-catch块来捕获异常,没抛异常就说明事务成功:

try {
    jdbcClient.sql(query)
            .param("email", email)
            .param("senha", senha)
            .param("nome", nome)
            .param("cpf", cpf)
            .update();
    // 只要没抛异常,就说明事务执行成功
    return true;
} catch (DataAccessException e) {
    // 事务失败,这里可以记录日志或者做其他处理
    e.printStackTrace();
    return false;
}

这种方法最简单,但缺点是没法知道具体哪一步出错了,不过能满足“判断事务是否成功”的基本需求。

总结

最推荐的还是用Spring自带的事务管理,不仅更符合Spring的最佳实践,还能灵活控制事务的隔离级别、传播行为等,后续维护也更方便。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 08:25:26