(一)問題的引出、主要解決手段
在線程中使用 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
(四)擴展:編程式事務和聲明式事務
編程式事務:
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);
注意編程式事務要顯示提交

