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
相关产品推荐
相关产品推荐

