一次子线程事务回滚实践笔记-编程式事务

(一)问题的引出、主要解决手段

在线程中使用 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);

注意编程式事务要显示提交

转载于:https://www.cnblogs.com/silyvin/p/9106647.html

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值