統一Spring 和 非spring事務處理的方案


背景

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)));

    }

                


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM