parallelStream与Spring事务相遇?不是冤家不聚头~

简介: parallelStream与Spring事务相遇?不是冤家不聚头~

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


事情是这样的:运营人员反馈,通过Excel导入数据时,有一部分成功了,有一部分未导入。初步猜测,是事务未生效导致的。


查看代码,发现导入部分已经通过@Transcational注解进行事务控制了,为什么还会出现事务不生效的问题呢?


下面我们就进行具体的案例分析,Let’s go!


事务不生效的代码

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


 @Transactional(rollbackFor = Exception.class)

public void batchInsert(List<Order> list) {

 list.parallelStream().forEach(order -> orderMapper.save(order));

}

1

2

3

4

逻辑很简单,遍历list,然后批量插入Order数据到数据库。在该方法上使用@Transactional来声明出现异常时进行回滚。


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


下面一探究竟。


JDK 8 的Stream

上面代码中涉及到了两个知识点:parallelStream和@Transactional,我们先来铺垫一下parallelStream相关知识。


在JDK8 中引入了Stream API的概念和实现,这里的Stream有别于 InputStream 和OutputStream,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

1

2

3

4

代码看起来方便清爽多了。关于Stream的基本处理流程如下:


image.pngimage.pngparallelStream的基本使用方式如下:

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

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

image.png而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时,很容易抓瞎。



目录
相关文章
|
15天前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
|
6月前
|
Java Spring
Spring中事务失效的场景
因为Spring事务是基于代理来实现的,所以某个加了@Transactional的⽅法只有是被代理对象调⽤时, 那么这个注解才会⽣效 , 如果使用的是被代理对象调用, 那么@Transactional会失效 同时如果某个⽅法是private的,那么@Transactional也会失效,因为底层cglib是基于⽗⼦类来实现 的,⼦类是不能重载⽗类的private⽅法的,所以⽆法很好的利⽤代理,也会导致@Transactianal失效 如果在业务中对异常进行了捕获处理 , 出现异常后Spring框架无法感知到异常, @Transactional也会失效
|
6月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——常见问题总结
本文总结了Spring Boot中使用事务的常见问题,虽然通过`@Transactional`注解可以轻松实现事务管理,但在实际项目中仍有许多潜在坑点。文章详细分析了三个典型问题:1) 异常未被捕获导致事务未回滚,需明确指定`rollbackFor`属性;2) 异常被try-catch“吃掉”,应避免在事务方法中直接处理异常;3) 事务范围与锁范围不一致引发并发问题,建议调整锁策略以覆盖事务范围。这些问题看似简单,但一旦发生,排查难度较大,因此开发时需格外留意。最后,文章提供了课程源代码下载地址,供读者实践参考。
123 0
|
6月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——Spring Boot 事务配置
本文介绍了 Spring Boot 中的事务配置与使用方法。首先需要导入 MySQL 依赖,Spring Boot 会自动注入 `DataSourceTransactionManager`,无需额外配置即可通过 `@Transactional` 注解实现事务管理。接着通过创建一个用户插入功能的示例,展示了如何在 Service 层手动抛出异常以测试事务回滚机制。测试结果表明,数据库中未新增记录,证明事务已成功回滚。此过程简单高效,适合日常开发需求。
810 0
|
6月前
|
Java 数据库 微服务
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——事务相关
本文介绍Spring Boot事务配置管理,阐述事务在企业应用开发中的重要性。事务确保数据操作可靠,任一异常均可回滚至初始状态,如转账、购票等场景需全流程执行成功才算完成。同时,事务管理在Spring Boot的service层广泛应用,但根据实际需求也可能存在无需事务的情况,例如独立数据插入操作。
118 0
|
4月前
|
人工智能 Java 数据库连接
Spring事务失效场景
本文深入探讨了Spring框架中事务管理可能失效的几种常见场景及解决方案,包括事务方法访问级别不当、方法内部自调用、错误的异常处理、事务管理器或数据源配置错误、数据库不支持事务以及不合理的事务传播行为或隔离级别。通过合理配置和正确使用`@Transactional`注解,开发者可以有效避免这些问题,确保应用的数据一致性和完整性。
204 10
|
3月前
|
Java 关系型数据库 MySQL
【Spring】【事务】初学者直呼学会了的Spring事务入门
本文深入解析了Spring事务的核心概念与使用方法。Spring事务是一种数据库事务管理机制,通过确保操作的原子性、一致性、隔离性和持久性(ACID),维护数据完整性。文章详细讲解了声明式事务(@Transactional注解)和编程式事务(TransactionTemplate、PlatformTransactionManager)的区别与用法,并探讨了事务传播行为(如REQUIRED、REQUIRES_NEW等)及隔离级别(如READ_COMMITTED、REPEATABLE_READ)。
237 1
|
6月前
|
SQL Java 数据库连接
Spring中的事务是如何实现的
1. Spring事务底层是基于数据库事务和AOP机制的 2. ⾸先对于使⽤了@Transactional注解的Bean,Spring会创建⼀个代理对象作为Bean 3. 当调⽤代理对象的⽅法时,会先判断该⽅法上是否加了@Transactional注解 4. 如果加了,那么则利⽤事务管理器创建⼀个数据库连接 5. 并且修改数据库连接的autocommit属性为false,禁⽌此连接的⾃动提交,这是实现Spring事务⾮ 常重要的⼀步 6. 然后执⾏当前⽅法,⽅法中会执⾏sql 7. 执⾏完当前⽅法后,如果没有出现异常就直接提交事务 8. 如果出现了异常,并且这个异常是需要回滚的就会回滚事务
|
6月前
|
JavaScript Java 开发者
Spring事务失效,常见的情况有哪些?
本文总结了Spring事务失效的7种常见情况,包括未启用事务管理功能、方法非public类型、数据源未配置事务管理器、自身调用问题、异常类型错误、异常被吞以及业务和事务代码不在同一线程中。同时提供了两种快速定位事务相关Bug的方法:通过查看日志(设置为debug模式)或调试代码(在TransactionInterceptor的invoke方法中设置断点)。文章帮助开发者更好地理解和解决Spring事务中的问题。
200 7
|
8月前
|
SQL Java 关系型数据库
【SpringFramework】Spring事务
本文简述Spring中数据库及事务相关衍伸知识点。
99 9

热门文章

最新文章