一次子線程事務回滾實踐筆記-編程式事務


(一)問題的引出、主要解決手段

在線程中使用 batchupdate ,中的每一條記錄都會自動的commit(但仍使用一個數據庫連接會話,有點像hibernate一級緩存的概念,多個事務,一個會話),如果有異常,則只有異常的數據執行失敗,其他數據不會rollback,並且后續的數據可以繼續執行

業務中這樣導致多線程任務異常數據的捕捉十分不易,必須使batchupdate批次有一個失敗,就全部失敗,然后打印日志,重爬該批次數據。

而線程作為非spring托管類,無法直接使用聲明式事務解決

作者使用編程式事務解決了batchupdate的事務控制,只要有一次exception,則所有的數據都rollback,commit 時會一次把所有的數據 提交

選自:jdbctemplate batchupdate 的事務管理  http://blog.csdn.net/huijianpang/article/details/44780385



(二)我這里用了另一篇文文章的代碼

Spring的@Transactional事務無法處理thread線程的解決方案

問題描述:

在Spring的web項目中,查詢了多行數據,對這些數據遍歷處理,並對每一條數據采取線程的方式去執行,方式如下:

復制代碼
 1 new Thread(new Runnable() {
 2   @Override
 3   public void run() {
 4      try {
 5         processEachPlan(learn); // 處理逐條數據
 6      } catch (Exception e) {
 7         Logger.info("異常信息:" + e.toString());
 8      }
 9   }
10 }).start();
復制代碼

問題在於run(){}方法中的processEachPlan(learn)不受聲明式事務管理了,但是我的需求是讓每一個processEachPlan(learn)都在各自的事務中管理,這樣能夠保證逐條處理的數據的完整性。

解決方案:

我的想法是能否自己控制事務,解決方式如下:

復制代碼
 1 new Thread(new Runnable() {
 2   @Override
 3   public void run() {
 4     // spring無法處理thread的事務,聲明式事務無效
 5     DefaultTransactionDefinition def = new DefaultTransactionDefinition();
 6      def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
 7      PlatformTransactionManager txManager = ContextLoader.getCurrentWebApplicationContext().getBean(PlatformTransactionManager.class);
 8      TransactionStatus status = txManager.getTransaction(def);
 9                     
10      try {
11         processEachPlan(learn);
12         txManager.commit(status); // 提交事務
13      } catch (Exception e) {
14         Logger.info("異常信息:" + e.toString());
15         txManager.rollback(status); // 回滾事務
16       }
17   }
18 }).start();
復制代碼

如上代碼中,大概意思就是獲取了spring配置中的bean,以及相關定義來開啟事務,processEachPlan(learn)如果執行成功,那么commit()提交事務,如果出現異常,那么rollback()回滾。在我的項目中,測試是成功的。


我這里也成功了


(三)其中有個小插曲,我的

spring boot環境 ContextLoader.getCurrentWebApplicationContext()返回null


參考了這個帖子:http://www.oschina.net/question/2416168_2189114

springboot中,ContextLoader.getCurrentWebApplicationContext()獲取的為Null

中,

另外推薦使用ApplicationContextAware的方式獲取ApplicationContext,這樣對非web及web環境都有很好的支持,我的工程這樣寫的:

@Component @Lazy(false) public class ApplicationContextRegister implements ApplicationContextAware { private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationContextRegister.class);   private static ApplicationContext APPLICATION_CONTEXT;   /**  * 設置spring上下文  *  * @param applicationContext spring上下文  * @throws BeansException  */  @Override  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { LOGGER.debug("ApplicationContext registed-->{}", applicationContext);  APPLICATION_CONTEXT = applicationContext;  } public static ApplicationContext getApplicationContext() { return APPLICATION_CONTEXT;  }
}

想起來,以前解決過提取spring boot環境 ApplicationContext的問題:

spring boot 的 ApplicationContext 及 getbean

最終,

        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        PlatformTransactionManager txManager = SpringUtil.getBean(PlatformTransactionManager.class);
        TransactionStatus status = txManager.getTransaction(def);
@Component
public class SpringUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext = null;
// 非@import顯式注入,@Component是必須的,且該類必須與main同包或子包
    // 若非同包或子包,則需手動import 注入,有沒有@Component都一樣
    // 可復制到Test同包測試

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if(SpringUtil.applicationContext == null){
            SpringUtil.applicationContext  = applicationContext;
        }
        System.out.println("---------------com.ilex.jiutou.util.Test.Main.SubPackage.SpringUtil---------------");
    }

    //獲取applicationContext
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    //通過name獲取 Bean.
    public static Object getBean(String name){
        return getApplicationContext().getBean(name);

    }

    //通過class獲取Bean.
    public static <T> T getBean(Class<T> clazz){
        return getApplicationContext().getBean(clazz);
    }

    //通過name,以及Clazz返回指定的Bean
    public static <T> T getBean(String name,Class<T> clazz){
        return getApplicationContext().getBean(name, clazz);
    }

}

done


(四)擴展:編程式事務和聲明式事務

Spring事務管理實現方式之編程式事務與聲明式事務詳解

編程式事務:

1)PlatformTransactionManager——本文采用

2)使用TransactionTemplate

聲明式事務:

1)@Transactional

2)<tx:advice> & aop


(五)3.2出現問題:Unable to fetch a connection in 30 seconds, none available[size:100; busy

參考:

hibernate數據庫連接池爆滿的原因及源碼分析

查下來是hibernate連接池爆掉了,原因是事務未提交

txManager.commit(status);

注意編程式事務要顯示提交


免責聲明!

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



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