明天这篇文章跟大家分享一个实战中的Bug及解决方案和技术延长。

事件是这样的:经营人员反馈,通过Excel导入数据时,有一部分胜利了,有一部分未导入。初步猜想,是事务未失效导致的。

查看代码,发现导入局部曾经通过@Transcational注解进行事务管制了,为什么还会呈现事务不失效的问题呢?

上面咱们就进行具体的案例剖析,Let's go!

事务不失效的代码

这里写一段简略的伪代码来演示展现一下事务不失效的代码:

  @Transactional(rollbackFor = Exception.class)    public void batchInsert(List<Order> list) {        list.parallelStream().forEach(order -> orderMapper.save(order));    }

逻辑很简略,遍历list,而后批量插入Order数据到数据库。在该办法上应用@Transactional来申明出现异常时进行回滚。

但事实状况是,其中某一条数据执行异样时,事务并没有进行回滚。这到底是为什么呢?

上面一探到底。

JDK 8 的Stream

下面代码中波及到了两个知识点:parallelStream和@Transactional,咱们先来铺垫一下parallelStream相干常识。

在JDK8 中引入了Stream API的概念和实现,这里的Stream有别于 InputStreamOutputStream,Stream API 是解决对象流而不是字节流。

比方,咱们能够通过如下形式来基于Stream进行实现:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); numbers.stream().forEach(num->System.out.println(num));输入:1 2 3 4 5 6 7 8 9

代码看起来不便清新多了。对于Stream的根本解决流程如下:

在这些Stream API中,还提供了一个并行处理的API,也就是parallelStream。它能够将工作拆分子工作,分发给多个处理器同时解决,之后合并。这样做的目标很显著是为了晋升解决效率。

parallelStream的根本应用形式如下:

// 并行执行流list.stream().parallel().filter(e -> e > 10).count()

针对上述代码,对应的流程如下:

而parallelStream会将流划分成多个子流,扩散到不同的CPU并行处理,而后合并处理结果。其中,parallelStream默认是基于ForkJoinPool.commonPool()线程池来实现并行处理的。

通常状况下,咱们能够认为并行会比串行快,但还是有前提条件的:

  • 处理器外围数量:并行处理外围数越多,解决效率越高;
  • 解决数据量:解决数据量越大劣势越显著;

但并行处理也面临着一系列的问题,比方:资源竞争、死锁、线程切换、事务、可见性、线程平安等问题。

@Transactional事务处理

下面理解了parallelStream的基本原理及个性之后,再来看看@Transactional的事务处理个性。

@Transactional是Spring提供的基于注解的一种申明式事务形式,该注解只能使用到public的办法上。

基本原理:当一个办法被@Transactional注解之后,Spring会基于AOP在办法执行之前开启一个事务。当办法执行结束之后,依据办法是否报错,来决定回滚或提交事务。

在默认代理模式下,只有指标办法由内部办法调用时,能力被Spring的事务拦截器拦挡。所以,在同一个类中的两个办法间接调用,不会被Spring的事务拦截器拦挡。这是事务不失效的一个场景,但在上述案例中,并不存在这种状况。

Spring在处理事务时,会从连接池中取得一个jdbc connection,将连贯绑定到线程上(基于ThreadLocal),那么同一个线程中用到的就是同一个connection了。具体实现在DataSourceTransactionManager#doBegin办法中。

Bug综合剖析

在理解了parallelStream和@Transactional的相干常识之后,咱们会发现:parallelStream解决时开启了多线程,而@Transactional在处理事务时会(基于ThreadLocal)将连贯绑定到以后线程,因为@Transactional绑定治理的是主线程的事务,而parallelStream开启的新的线程与主线程无关。因而,事务也就有效了。

此时,将parallelStream改为一般的stream,事务可失常回滚。这就提醒咱们,在应用基于@Transactional形式治理事务时,谨慎应用多线程解决。

问题拓展

尽管parallelStream带来了更高的性能,但也要辨别场景进行应用。即使是在不须要事务管理的状况下,如果parallelStream使用不当,也会造成同一时间对数据库发动大量申请等问题。

因而,在stream与parallelStream之间进行抉择时,还要思考几个问题:

  • 是否须要并行?数据量比拟大,处理器外围数比拟多的状况下才会有性能晋升。
  • 工作之间是否是独立的,是否会引起任何竞态条件?比方:是否共享变量。
  • 执行后果是否取决于工作的调用程序?并行执行的程序是不确定的。

小结

本篇文章讲述的Bug尽管简略,但如果不理解parallelStream与@Transactional注解的个性,还是很难排查的。而且也让咱们意识到,尽管Spring通过@Transactional将事务管理进行了简化解决,但作为开发者,还是须要深刻理解一下它的根本运作原理。不然,在排查bug时,很容易抓瞎。

博主简介:《SpringBoot技术底细》技术图书作者,热爱钻研技术,写技术干货文章。

公众号:「程序新视界」,博主的公众号,欢送关注~

技术交换:请分割博主微信号:zhuan2quan