(一)问题的引出、主要解决手段
在线程中使用 batchupdate ,中的每一条记录都会自动的commit(但仍使用一个数据库连接会话,有点像hibernate一级缓存的概念,多个事务,一个会话),如果有异常,则只有异常的数据执行失败,其他数据不会rollback,并且后续的数据可以继续执行
业务中这样导致多线程任务异常数据的捕捉十分不易,必须使batchupdate批次有一个失败,就全部失败,然后打印日志,重爬该批次数据。
而线程作为非spring托管类,无法直接使用声明式事务解决
作者使用编程式事务解决了batchupdate的事务控制,只要有一次exception,则所有的数据都rollback,commit 时会一次把所有的数据 提交
选自:jdbctemplate batchupdate 的事务管理 http://blog.csdn.net/huijianpang/article/details/44780385
(二)我这里用了另一篇文文章的代码
问题描述:
在Spring的web项目中,查询了多行数据,对这些数据遍历处理,并对每一条数据采取线程的方式去执行,方式如下:
1 new Thread(newRunnable() {2 @Override3 public voidrun() {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(newRunnable() {2 @Override3 public voidrun() {4 //spring无法处理thread的事务,声明式事务无效
5 DefaultTransactionDefinition def = newDefaultTransactionDefinition();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 classApplicationContextRegisterimplementsApplicationContextAware{ private static finalLogger LOGGER= LoggerFactory.getLogger(ApplicationContextRegister.class);private staticApplicationContext APPLICATION_CONTEXT;/*** 设置spring上下文**@paramapplicationContextspring上下文*@throwsBeansException*/@Overridepublic voidsetApplicationContext(ApplicationContext applicationContext)throwsBeansException{ LOGGER.debug("ApplicationContext registed-->{}",applicationContext);APPLICATION_CONTEXT= applicationContext;} public staticApplicationContextgetApplicationContext(){ returnAPPLICATION_CONTEXT;}
}
想起来,以前解决过提取spring boot环境 ApplicationContext的问题:
最终,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 getBean(Class clazz){
return getApplicationContext().getBean(clazz);
}
//通过name,以及Clazz返回指定的Bean
public static T getBean(String name,Class clazz){
return getApplicationContext().getBean(name, clazz);
}
}
done
(四)扩展:编程式事务和声明式事务
编程式事务:
1)PlatformTransactionManager——本文采用
2)使用TransactionTemplate
声明式事务:
1)@Transactional
2) & aop
(五)3.2出现问题:Unable to fetch a connection in 30 seconds, none available[size:100; busy
参考:
查下来是hibernate连接池爆掉了,原因是事务未提交
txManager.commit(status);
注意编程式事务要显示提交