标签:spring jdbctemplate autocommit 事务 transaction
这是工作中遇到的一个真实问题的处理过程,如果对分析过程不感兴趣,可以直接跳到最后看最终方案。
我们在持久化这一层,并没有用任何的ORM框架(无论是Hibernate还是MyBatis,亦或是DBUtils),而是采用了在JDBCTemplate基础上进行了简单的包装,同时我们也决定将AutoCommit设置为True,在sql语句执行完成后立即提交。这样做相比于@Transactional注解或者通过AOP来控制事务性能更好,也更方便。
在评估了优劣之后,便开始使用这种方式,随之我们也遇到了一个真实的问题。这里我们把涉及公司的信息全部隐藏掉,简化后的需求是:一个业务要连续执行两个表的insert操作,必须保证同时生效或失败。当然采用补偿的方式也能达到效果,但是考虑到我们的用户量不是十分巨大,而且未来一段时间内用户不会暴增,采用补偿有点得不偿失,所以决定在特定情况下采用手动控制事务,其余情况默认AutoCommit为True。在定了这个目标之后,开始研究如果在JDBCTemplate基础上实现。
首先尝试的是获得Connection,并设置AutoCommit为False,代码如下:
DataSource dataSource = ((JdbcTemplate)namedParameterJdbcTemplate.getJdbcOperations()).getDataSource(); Connection connection = DataSourceUtils.getConnection(dataSource); connection.setAutoCommit(false);
设置之后,测试发现并不生效,此时已经知道这么做是没有用的。接下来我们进一步分析,看在JDBCTemplate中是如何获得数据库连接的,可以通过打断点的方式查看,每次获得的connection对象的hashCode不一致。
我们知道NamedParameterJdbcTemplate这个类持有一个JdbcTemplate的实例,我们从NamedParameterJdbcTemplate的update方法逐层跟进去,发现最终调用的是JdbcTemplate类的下面方法:
protected int update(final PreparedStatementCreator psc, final PreparedStatementSetter pss) throws DataAccessException
这个方法又调用了下面的方法:
public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action) throws DataAccessException
在这个execute方法里我们找到了JdbcTemplate获得数据库连接的方式,即:
Connection con = DataSourceUtils.getConnection(getDataSource());
继续跟踪进去,发现最终调用的是DataSourceUtils的下面方法:
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
conHolder.setConnection(dataSource.getConnection());
}
return conHolder.getConnection();
}
// Else we either got no holder or an empty thread-bound holder here.
logger.debug("Fetching JDBC Connection from DataSource");
Connection con = dataSource.getConnection();
if (TransactionSynchronizationManager.isSynchronizationActive()) {
logger.debug("Registering transaction synchronization for JDBC Connection");
// Use same Connection for further JDBC actions within the transaction.
// Thread-bound object will get removed by synchronization at transaction completion.
ConnectionHolder holderToUse = conHolder;
if (holderToUse == null) {
holderToUse = new ConnectionHolder(con);
}
else {
holderToUse.setConnection(con);
}
holderToUse.requested();
TransactionSynchronizationManager.registerSynchronization(
new ConnectionSynchronization(holderToUse, dataSource));
holderToUse.setSynchronizedWithTransaction(true);
if (holderToUse != conHolder) {
TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
}
}
return con;
}解释一下上面的代码:每次获得数据库连接时,都是首先判断TransactionSynchronizationManager里面是否包含了ConnectionHolder,如果包含了则直返回,如果未包含,则首先从DataSource中获得一个Connection,然后分两种情况进行处理:
一 当TransactionSynchronizationManager.isSynchronizationActive()为True时,则初始化ConnectionHolder,并调用TransactionSynchronizationManager.bindResource(dataSource, holderToUse);完成绑定。关于绑定的范围,我们看一下TransacionSynchronizationManager代码中变量的定义,就能知道了。
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<Map<Object, Object>>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<Set<TransactionSynchronization>>("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName =
new NamedThreadLocal<String>("Current transaction name");
private static final ThreadLocal<Boolean> currentTransactionReadOnly =
new NamedThreadLocal<Boolean>("Current transaction read-only status");
private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
new NamedThreadLocal<Integer>("Current transaction isolation level");
private static final ThreadLocal<Boolean> actualTransactionActive =
new NamedThreadLocal<Boolean>("Actual transaction active");都是ThreadLocal的,也就是生效范围是当前线程内。
二 不处理ConnectionHolder,直接返回connection。
看到这里,大家一定知道该怎么处理了,接下来给出我们最终的修改代码(省略掉了catch中的全部):
TransactionSynchronizationManager.initSynchronization();
DataSource dataSource = ((JdbcTemplate)jdbcTemplate.getJdbcOperations()).getDataSource();
Connection connection = DataSourceUtils.getConnection(dataSource);
try {
connection.setAutoCommit(false);
//需要操作数据库的两个insert,或者提供回调给业务开发人员
connection.commit();
} catch (SQLException e) {
} finally {
try {
TransactionSynchronizationManager.clearSynchronization();
} catch (IllegalStateException e) {
}
try {
connection.setAutoCommit(true);
} catch (SQLException e) {
}
}
最后总结一下,Spring的JDBCTemplate提供的操作是很丰富的,只是平时没有注意到。在遇到问题时一定不要慌,仔细分析逻辑、阅读源码,相信问题一定能够得到解决。
本文出自 “谷风” 博客,请务必保留此出处http://1202955.blog.51cto.com/1192955/1926768
JDBCTemplate在AutoCommit为True时手动控制事务
标签:spring jdbctemplate autocommit 事务 transaction
原文地址:http://1202955.blog.51cto.com/1192955/1926768