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

