背景:
1: 接手的系统中,数据库操作部分如下,下文称为ECon的方式:
ECon con = null; try { con = ConMan.get("order"); ...//do something with con } catch (SQLException e) { if (con!=null) { try { con.rollback(); } catch (Exception ex) {} } } finally { Closer.close(con); }
为了保证事务,只能是在不同的方法中将con传来传去。
2:由于1中的方式极其不方便。团队倾向于使用spring来处理,所以很多方法就是用spring来写
问题
由于代码不断的在修改,就会产生ECon调用Spring写得代码、Spring调用ECon的旧代码等等,如何保证这些代码的事务一致性?
问题分析与解决
事务一致性要解决的问题
1:数据库连接Connection的一致性。在同一个request线程中,无论何时获取连接,都保证是同一个。
解决方案:必然使用ThreadLocal来存储。要么改写ConMan.get("order") 来适配Spring,要么改写Spring来适配ConMan.get("order"). 目前倾向于Spring,因为Spring中有DataSourceUtils来取得当前事务的连接。
暂不下结论。继续后面的分析。
2:由于ECon的代码中会catch exception回滚和 finally 关闭连接。 因为要和Spring整合在一起,互相嵌套调用,为了保证事务一致性,我们要保证内层的rollback和close没有真实的rollback和close,应该仅仅是标记。
只有最外层的回滚或关闭,才是真实的操作。
解决方案:这个特性和Spring的事务管理非常的像。果断翻阅Spring事务管理的源代码。
最后得到Spring的解决方案:
public void doLogic1(){
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can only be done programmatically
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
// execute your business logic here
doLogic2();
}
catch (MyException ex) {
txManager.rollback(status);
throw ex;
}
txManager.commit(status);
}
public void doLogic2(){ DefaultTransactionDefinition def = new DefaultTransactionDefinition(); // explicitly setting the transaction name is something that can only be done programmatically def.setName("SomeTxName"); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus status = txManager.getTransaction(def); try { // execute your business logic2 here } catch (MyException ex) { txManager.rollback(status); throw ex; } txManager.commit(status); }
这个例子就是 doLogic1调用doLogic2,两个方法都显式调用rollback 和 commit。 那么他们是如何保证事务一致性的呢。
最后发现基本逻辑如下:
第一层事务开始(如doLogic1()) TransactionStatus.isNewTransaction 返回 true 第二层事务开始(如doLogic2) TransactionStatus.isNewTransaction 返回 false 、 第二层事务结束: if(TransactionStatus.isNewTransaction()){ doLogic2返回是false,所以不会真正的提交 status.commit()//真实的提交 } 第一层事务结束 if(TransactionStatus.isNewTransaction()){ doLogic1返回的true,所有会真正的提交 status.commit()//真实的提交 }
同理rollback 和 close,Spring会判断当前的TransactionStatus 是否是新起的事务,如果是则真实操作,如果不是则在事务管理器中标记一下,到最外层的新新起事务中进行判断操作。
Spring的事务管理方式完全满足需求。至此,问题的解决方案全部搞定。
编码:
1:修改EConMan:
SpringEconManager.java : public static ECon get(String dsname, TransactionDefinition definition) { DataSourceTransactionManager transactionManager = getTransactionManager(dsname); TransactionStatus status = transactionManager.getTransaction(definition); DataSource dataSource = transactionManager.getDataSource(); ECon eCon = (ECon) DataSourceUtils.getConnection(dataSource); return SpringEcon.newSpringEcon(dsname, transactionManager, status, definition, dataSource, eCon); } /** * 用 SpringEconManager.close(econ), 替换 Closer.close(econ) * * @param con */ public static void close(ECon con) { if(con == null){ return; } if (con instanceof SpringEcon) { SpringEcon wrapperEcon = (SpringEcon) con; if (!wrapperEcon.getStatus().isCompleted()) { if (wrapperEcon.getStatus().isRollbackOnly()) { rollback(con); } else { commit(con); } } DataSourceUtils.releaseConnection(wrapperEcon.getEcon(), wrapperEcon.getDatasource()); } else { Closer.close(con); } } /** * 用 SpringEconManager.rollback(econ), 替换econ.rollback() * * @param con */ public static void rollback(ECon con) { if(con == null){ return; } if (con instanceof SpringEcon) { SpringEcon wrapperEcon = (SpringEcon) con; TransactionStatus status = wrapperEcon.getStatus(); wrapperEcon.getTransactionManager().rollback(status); } else { try { con.rollback(); } catch (SQLException e) { Throwables.propagate(e); } } } /** * 用 SpringEconManager.commit(econ), 替换econ.commit() * * @param con */ public static void commit(ECon con) { if(con == null){ return; } if (con instanceof SpringEcon) { SpringEcon wrapperEcon = (SpringEcon) con; TransactionStatus status = wrapperEcon.getStatus(); if (!status.isRollbackOnly()) { wrapperEcon.getTransactionManager().commit(status); } } else { try { con.commit(); } catch (SQLException e) { Throwables.propagate(e); } } }
2:使用SpringEcon继承Econ,覆盖相关方法:
SpringEcon.java : @Override public void commit() throws SQLException { SpringEconManager.commit(this); } @Override public void rollback() throws SQLException { SpringEconManager.rollback(this); } @Override public void close() { SpringEconManager.close(this); }
3:旧代码清理:
只需将出现 con = ConMan.get("order"); 改为 con = SpringEconManager.get("order");
事务测试:
仅展示表明思路的代码:
1: Spring 调用ECon
@Transactional public void testRollback(String name) { insert1(name); jdbcinsert2(name); throw new RuntimeException(); } @Transactional public void testRollback1(String name) { // rollback and catch excpetion insert1WithRollback(name); jdbcinsert2(name); } @Transactional public void testRollback2(String name) { jdbcinsert2(name); // rollback and catch excpetion insert1WithRollback(name); } @Transactional public void testRollback3(String name) { jdbcinsert2(name); // rollback and catch excpetion insert1WithRollback(name); SpringEconManager.commit(SpringEconManager.get("order")); } @Transactional public void testCommit(String name) { insert1(name); jdbcinsert2(name); }
2: Econ调用Spring
@Test public void testRollback() { ECon con = SpringEconManager.get("order"); String name = UUID.randomUUID().toString(); try { insert1(name); jdbcinsert2(name); SpringEconManager.rollback(con); } finally { SpringEconManager.close(con); } Assert.assertTrue(CollectionUtils.isEmpty(query1(name))); Assert.assertTrue(CollectionUtils.isEmpty(query2(name))); } @Test public void testRollback1() { ECon con = SpringEconManager.get("order"); String name = UUID.randomUUID().toString(); try { // rollback and catch excpetion insert1WithRollback(name); jdbcinsert2(name); } finally { SpringEconManager.close(con); } Assert.assertTrue(CollectionUtils.isEmpty(query1(name))); Assert.assertTrue(CollectionUtils.isEmpty(query2(name))); } @Test public void testRollback2() { ECon con = SpringEconManager.get("order"); String name = UUID.randomUUID().toString(); try { jdbcinsert2(name); // rollback and catch excpetion insert1WithRollback(name); } finally { SpringEconManager.close(con); } Assert.assertTrue(CollectionUtils.isEmpty(query1(name))); Assert.assertTrue(CollectionUtils.isEmpty(query2(name))); } @Test public void testRollback3() { ECon con = SpringEconManager.get("order"); String name = UUID.randomUUID().toString(); try { jdbcinsert2(name); // rollback and catch excpetion insert1WithRollback(name); SpringEconManager.commit(con); } finally { SpringEconManager.close(con); } Assert.assertTrue(CollectionUtils.isEmpty(query1(name))); Assert.assertTrue(CollectionUtils.isEmpty(query2(name))); }