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

JdbcTemplate下替代connection.setAutoCommit(false)的事务实现方案

问题根源分析
  • 连接不共享:你手动从DataSource获取的Connection,和JdbcTemplate执行SQL时用的不是同一个实例。JdbcTemplate默认每次操作都会从DataSource拿新连接(或连接池的空闲连接),所以你设置的setAutoCommit(false)对它完全无效。
  • 异常原因:try-with-resources会自动关闭连接,后续catch块里的commit/rollback操作都是在已关闭的连接上执行,自然抛出SQLRecoverableException;另外,就算不用try-with-resources,你手动拿的连接根本没被JdbcTemplate使用,rollback也不会影响JdbcTemplate执行的插入操作。
手动实现事务控制(模拟@Transactional原理)

核心思路是把手动控制的Connection绑定到当前线程上下文,让JdbcTemplate从线程上下文复用这个连接,而不是直接从DataSource取。Spring的TransactionSynchronizationManager就是干这个的,我们可以手动用它来实现。

修改后的PersonService.java

@Service
public class PersonService {

    @Autowired private PersonRepository personRepository;
    @Autowired private JdbcTemplate jdbcTemplate;
    @Autowired private DataSource dataSource;

    public void insertRecords() throws SQLException {
        // 事务外的插入:JdbcTemplate用默认方式获取连接
        Person person0 = new Person(0, "事务外的Person", 100);
        personRepository.save(person0);

        Connection connection = null;
        ConnectionHolder connectionHolder = null;
        try {
            // 1. 获取连接并关闭自动提交
            connection = dataSource.getConnection();
            connection.setAutoCommit(false);

            // 2. 将连接绑定到当前线程上下文
            connectionHolder = new ConnectionHolder(connection);
            TransactionSynchronizationManager.bindResource(dataSource, connectionHolder);

            // 3. 执行事务内的操作:此时JdbcTemplate会从线程上下文拿绑定的连接
            for (int i = 1; i <= 2; i++) {
                if (i == 2) {
                    throw new RuntimeException("模拟事务回滚异常");
                }
                Person person = new Person(0, "事务内Person " + i, 10 * i);
                personRepository.save(person);
            }

            // 4. 提交事务
            connection.commit();
        } catch (Exception e) {
            // 5. 回滚事务
            if (connection != null && !connection.isClosed()) {
                connection.rollback();
            }
            e.printStackTrace();
        } finally {
            // 6. 清理线程上下文,释放连接
            if (connectionHolder != null) {
                TransactionSynchronizationManager.unbindResource(dataSource);
                connectionHolder.release();
            }
            if (connection != null && !connection.isClosed()) {
                connection.setAutoCommit(true); // 恢复连接池的默认自动提交设置
                connection.close();
            }
        }
    }
}

PersonRepository.java无需修改

@Repository
public class PersonRepository {

    @Autowired private JdbcTemplate jdbcTemplate;

    public void save(Person person) throws SQLException {
        jdbcTemplate.update(
                "INSERT INTO PERSON(NAME, AGE) VALUES(?, ?)",
                new Object[]{person.getName(), person.getAge()}
        );
    }
}
关键逻辑解释
  • TransactionSynchronizationManager.bindResource():把Connection绑定到当前线程,JdbcTemplate内部会先检查线程上下文有没有绑定的Connection,有就用这个,没有才从DataSource拿新的。
  • ConnectionHolder:Spring封装Connection的工具类,帮我们管理连接状态。
  • 最终清理:必须解绑线程上下文的资源,否则会导致线程池中的线程携带旧连接,引发问题;同时要恢复连接的自动提交设置,因为连接池会复用连接,避免影响后续操作。
原代码异常补充说明
  • try-with-resources会在try块结束后自动关闭连接,后续catch块执行rollback时,连接已经关闭,所以抛出SQLRecoverableException。
  • 你手动获取的连接从未被JdbcTemplate使用,就算执行rollback,也不会回滚JdbcTemplate插入的数据,因为那些操作是在另一个连接上执行的。

内容的提问来源于stack exchange,提问作者ivoronline

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 23:30:08