(一)问题的引出、主要解决手段
在线程中使用 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);
注意编程式事务要显示提交