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