背景:
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))); }