多线程事务怎么回滚?

项目中用到了多线程去批量处理一些数据,当时想当然认为只要方法上加上@Transactional注解就好了,实际并未达到想要的处理效果。特此去学习了下关于多线程事务回滚相关方案,参考了网上其他资料,这里整理并记录下学习历程。
站在巨人的肩膀上,我们可以看的更远!

多线程事务怎么回滚?

  • 一、准备相关基础方法
    • 1.线程池配置
    • 2.list切分工具类
    • 3.SqlSession工具类
    • 4.员工实体类
    • 5.员工EmployeeMapper
    • 6.员工对应EmployeeMapper.xml
  • 二、业务处理
    • 1.EmployeeService接口
    • 2.测试多线程事务实现类
    • 3.员工Controller
  • 三、方案验证
    • 1.数据库表Employee存储1条原始数据,用于验证数据删除后是否被回滚。![在这里插入图片描述](https://img-blog.csdnimg.cn/cb341ba2f3e146e69dc3e913f1b411f8.png)
    • 2.EmployeeServiceImpl的saveThreadByTransactional方法
    • 3.EmployeeServiceImpl的saveThreadRollBack方法
  • 四、方案总结
    • 1.方案总结
  • 五.项目结构及下载

一、准备相关基础方法

这里以多线程、分批次插入数据库employee表为例子进行演示。

1.线程池配置

/*** 线程池配置*/
@Component
public class ExecutorConfig {private static int maxPoolSize = Runtime.getRuntime().availableProcessors();private volatile static ExecutorService executorService;public static ExecutorService getThreadPool() {if (executorService == null){synchronized (ExecutorConfig.class){if (executorService == null){executorService =  newThreadPool();}}}return executorService;}private static ExecutorService newThreadPool(){int queueSize = 1000;int corePool = Math.min(10, maxPoolSize);return new ThreadPoolExecutor(corePool, maxPoolSize, 10000L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>(queueSize),new ThreadPoolExecutor.AbortPolicy());}private ExecutorConfig(){}
}

2.list切分工具类

/*** list切分工具类*/
public class ListUtil {/*** 平均拆分list** @param source* @param n* @param <T>* @return*/public static <T> List<List<T>> AverageList(List<T> source, int n) {List<List<T>> result = new ArrayList<>();int remaider = source.size() % n;int number = source.size() / n;//偏移量int offset = 0;for (int i = 0; i < n; i++) {List<T> value;if (remaider > 0) {value = source.subList(i * number + offset, (i + 1) * number + offset + 1);remaider--;offset++;} else {value = source.subList(i * number + offset, (i + 1) * number + offset);}result.add(value);}return result;}
}

3.SqlSession工具类

/*** SqlSession工具类*/
@Component
public class SqlContext {@Resourceprivate SqlSessionTemplate sqlSessionTemplate;public SqlSession getSqlSession(){SqlSessionFactory sqlSessionFactory = sqlSessionTemplate.getSqlSessionFactory();return sqlSessionFactory.openSession();}
}

4.员工实体类

/*** 员工*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "employee")
public class Employee {@TableField(value = "employee_id")private Integer employeeId;@TableField(value = "employee_name")private String employeeName;@TableField(value = "age")private Integer age;
}

5.员工EmployeeMapper

@Repository
public interface EmployeeMapper extends BaseMapper<Employee> {int saveBatchRollBack(List Employee);
}

6.员工对应EmployeeMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.it.mapper.EmployeeMapper"><resultMap id="BaseResultMap" type="com.it.entity.Employee"><!--@Table `Employee`--><result column="employee_id" jdbcType="INTEGER" property="employee_id" /><result column="employee_name" jdbcType="VARCHAR" property="employee_name" /><result column="age" jdbcType="INTEGER" property="age" /></resultMap><sql id="Base_Column_List">employee_id, employee_name, age</sql><insert id="saveBatchRollBack">insert intoemployee (employee_id,age,employee_name)values<foreach collection="list" item="item" index="index" separator=",">(#{item.employeeId},#{item.age},#{item.employeeName})</foreach></insert>
</mapper>

二、业务处理

1.EmployeeService接口

public interface EmployeeService extends IService<Employee> {/*** 使用@Transactional测试多线程回滚失败*/void saveThreadByTransactional(List<Employee> employeeList);/*** 使用手动操作事务测试多线程回滚成功*/void saveThreadRollBack(List<Employee> employeeList) throws SQLException;
}

2.测试多线程事务实现类

/*** 测试多线程事务*/
@Service
@Slf4j
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService {@ResourceSqlContext sqlContext;/*** 多线程环境下Transactional失效场景** @param employeeList*/@Override@Transactional(rollbackFor = Exception.class)public void saveThreadByTransactional(List<Employee> employeeList) {try {// 先做删除操作,如果子线程出现异常,此操作不会回滚this.getBaseMapper().delete(null);// 获取线程池ExecutorService executorService = ExecutorConfig.getThreadPool();// 拆分数据,拆分6份List<List<Employee>> lists = ListUtil.AverageList(employeeList, 6);// 执行的线程Thread[] threadArray = new Thread[lists.size()];// 监控子线程执行完毕,再执行主线程,要不然会导致主线程关闭,子线程也会随着关闭CountDownLatch countDownLatch = new CountDownLatch(lists.size());AtomicBoolean atomicBoolean = new AtomicBoolean(true);for (int i = 0; i < lists.size(); i++) {if (i == lists.size() - 1) {// 最后一个atomicBoolean设置为falseatomicBoolean.set(false);}List<Employee> list = lists.get(i);threadArray[i] = new Thread(() -> {try {// 最后一个线程抛出异常if (!atomicBoolean.get()) {throw new RuntimeException("最后一个线程添加时抛出异常");}//批量添加,mybatisPlus中自带的batch方法this.saveBatch(list);} finally {countDownLatch.countDown();}});}for (int i = 0; i < lists.size(); i++) {executorService.execute(threadArray[i]);}// 当子线程执行完毕时,主线程再往下执行countDownLatch.await();System.out.println("employee列表添加完成");} catch (Exception e) {log.info("error", e);throw new RuntimeException("employee列表添加过程出现异常");}}/*** 使用sqlSession控制手动提交事务** @param employeeList*/@Overridepublic void saveThreadRollBack(List<Employee> employeeList) throws SQLException {{// 获取数据库连接,获取会话(内部自有事务)SqlSession sqlSession = sqlContext.getSqlSession();Connection connection = sqlSession.getConnection();try {// 设置手动提交connection.setAutoCommit(false);//获取mapperEmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);//先做删除操作employeeMapper.delete(null);//获取执行器ExecutorService service = ExecutorConfig.getThreadPool();List<Callable<Integer>> callableList = new ArrayList<>();//拆分listList<List<Employee>> lists = ListUtil.AverageList(employeeList, 6);AtomicBoolean atomicBoolean = new AtomicBoolean(true);for (int i = 0; i < lists.size(); i++) {if (i == lists.size() - 1) {atomicBoolean.set(false);}List<Employee> list = lists.get(i);//使用返回结果的callable去执行,Callable<Integer> callable = () -> {//让最后一个线程抛出异常if (!atomicBoolean.get()) {throw new Exception("出现异常");}return employeeMapper.saveBatchRollBack(list);};callableList.add(callable);}//执行子线程List<Future<Integer>> futures = service.invokeAll(callableList);for (Future<Integer> future : futures) {//如果有一个执行不成功,则全部回滚if (future.get() <= 0) {connection.rollback();return;}}connection.commit();System.out.println("添加完毕");} catch (Exception e) {connection.rollback();log.info("error", e);} finally {connection.close();}}
}

3.员工Controller

@RestController
@RequestMapping(value = "/employee")
public class EmployeeController {@AutowiredEmployeeService employeeService;@PostMapping("/saveThreadByTransactional")public ResponseEntity saveThreadByTransactional() {// 模拟需要插入12名员工到数据库List<Employee> list = IntStream.range(0, 12).mapToObj(i -> {Employee employee = new Employee();employee.setEmployeeId(i);employee.setEmployeeName("三丰" + i);employee.setAge(i + 100);return employee;}).collect(Collectors.toList());employeeService.saveThreadByTransactional(list);return new ResponseEntity<>(HttpStatus.OK);}@PostMapping("/saveThreadRollBack")public ResponseEntity saveThreadRollBack() throws SQLException {// 模拟需要插入12名员工到数据库List<Employee> list = IntStream.range(0, 12).mapToObj(i -> {Employee employee = new Employee();employee.setEmployeeId(i);employee.setEmployeeName("三丰" + i);employee.setAge(i + 100);return employee;}).collect(Collectors.toList());employeeService.saveThreadRollBack(list);return new ResponseEntity<>(HttpStatus.OK);}
}

三、方案验证

1.数据库表Employee存储1条原始数据,用于验证数据删除后是否被回滚。在这里插入图片描述

2.EmployeeServiceImpl的saveThreadByTransactional方法

该方法通过使用@Transactional注解尝试处理多线程事务回滚。
利用postman测试saveThreadByTransactional接口
在这里插入图片描述
发现控制台显示我们自定义的线程报错
在这里插入图片描述
在这里插入图片描述
查询数据库Employee表,发现代码中this.getBaseMapper().delete(null);
可以发现子线程组执行时,有一个线程执行失败,其他线程也会抛出异常,但是主线程中执行的删除操作,没有回滚(数据库中表数据也已经被删除完成),则证明@Transactional注解并不能在多线程下进行事务回滚!
在这里插入图片描述
在这里插入图片描述

3.EmployeeServiceImpl的saveThreadRollBack方法

该方法通过使用sqlSession控制,手动提交事务,在多线程下进行事务回滚。
利用postman测试saveThreadRollBack接口。
在这里插入图片描述
发现控制台显示我们自定义的线程报错。
在这里插入图片描述
在这里插入图片描述
查询数据库Employee表,发现数据并未被删除,证明多线程执行过程中失败了,事务被回滚了。
在这里插入图片描述

四、方案总结

1.方案总结

在Spring中可以使用@Transactional注解去控制事务,使出现异常时会进行回滚,在多线程中,这个注解则不会生效。
如果主线程需要先执行一些修改数据库的操作,当子线程在进行处理出现异常时,主线程修改的数据则不会回滚,导致数据错误。
通过使用sqlSession控制手动提交事务,可以达到主线程和子线程数据事务回滚。

五.项目结构及下载

在这里插入图片描述
源码地址springboot-cacheable,创作不易,欢迎star哦~

参考资料
支付宝一面:多线程事务怎么回滚?说用 @Transactional 可以回去等通知了!
多线程事务怎么回滚?
多线程如何实现事务回滚?一招帮你搞定!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/88884.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VR全景智慧文旅,用科技助力旅游业振兴

引言&#xff1a; 近年来&#xff0c;科技的迅猛发展将我们带入一个全新的数字化时代&#xff0c;而虚拟现实&#xff08;Virtual Reality&#xff0c;简称VR&#xff09;技术则以其令人惊叹的全新方式&#xff0c;影响着各个领域。其中&#xff0c;旅游业作为人们探索世界、体…

微服务03-RabbitMQ

1、简介 MQ,中文是消息中间件(队列)(MessageQueue),字面来看就是存放消息的队列。也就是事件驱动架构中的Broker。 简单来说,消息中间件就是指保存数据的一个容器(服务器),可以用于两个系统之间的数据传递。 几种常见MQ的对比: RabbitMQActiveMQRocketMQKafka公司…

云安全攻防(十)之 资源耗尽型攻击

资源耗尽型攻击 同为虚拟化技术&#xff0c;容器与虚拟机既存在相似之处&#xff0c;也有显著不同。在资源限制方面&#xff0c;无论使用 VMware、Virtual Box 还是 QEMU&#xff0c;我们都需要为即将创建的虚拟机设定明确的CPU、内存及硬盘资源阈值。在虚拟机内部进程看来&am…

Android自定义侧滑Item

源码地址&#xff1a;https://github.com/LanSeLianMa/CustomizeView/tree/master/cehuaitem 使用方式一&#xff1a;XML布局中直接使用 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com…

【Node.js】低代码平台源码

一、低代码简介 低代码管理系统是一种通过可视化界面和简化的开发工具&#xff0c;使非专业开发人员能够快速构建和管理应用程序的系统。它提供了一套预先定义的组件和模块&#xff0c;使用户可以通过拖放操作来设计应用程序的界面和逻辑。低代码管理系统还提供了自动化的工作…

STM32自带的DSP库的滤波初体验(一)

最近在弄STM32自带的DSP库里的滤波&#xff0c;记录一下&#xff1a; arm_fir_instance_q15 instance_q15_S; #define NUM_TAPS 16 //滤波系数的个数 #define BLOCK_SIZE 32 q15_t firStateF32[BLOCK_SIZE NUM_TAPS]; q15_t Fir_Coeff[NUM_TAPS] {-79, -136, 312, 6…

基于 SIFT 和 RANSAC 算法对高分辨率图像进行图像伪造检测(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

二叉树的存储结构(顺序存储)—— 数据结构与算法

&#x1f636;‍&#x1f32b;️Take your time ! &#x1f636;‍&#x1f32b;️ &#x1f4a5;个人主页&#xff1a;&#x1f525;&#x1f525;&#x1f525;大魔王&#x1f525;&#x1f525;&#x1f525; &#x1f4a5;代码仓库&#xff1a;&#x1f525;&#x1f525;魔…

从零开始学习 Java:简单易懂的入门指南之抽象类接口内部类(十一)

面向对象进阶&#xff08;抽象类&接口&内部类&#xff09; 第一章 抽象类1.1 概述1.1.1 抽象类引入 1.2 abstract使用格式1.2.1 抽象方法1.2.2 抽象类1.2.3 抽象类的使用 1.3 抽象类的特征1.4 抽象类的细节1.5 抽象类存在的意义 第二章 接口2.1 概述2.2 定义格式2.3 接…

机器学习 | Python实现KNN(K近邻)模型实践

机器学习 | Python实现KNN(K近邻)模型实践 目录 机器学习 | Python实现KNN(K近邻)模型实践基本介绍模型原理源码设计学习小结参考资料基本介绍 一句话就可以概括出KNN(K最近邻算法)的算法原理:综合k个“邻居”的标签值作为新样本的预测值。更具体来讲KNN分类过程,给定一个训…

第二十一章 重要HL7操作场景 - HL7批量消息

文章目录 第二十一章 重要HL7操作场景 - HL7批量消息支持的批处理格式处理传入的批次文档批处理模式自定义出库批量处理 第二十一章 重要HL7操作场景 - HL7批量消息 Production品支持 HL7 中的嵌套子文档&#xff08;批处理格式&#xff09;。每个子文档本身就是一个虚拟文档。…

【设计模式】MVC 模式

MVC 模式代表 Model-View-Controller&#xff08;模型-视图-控制器&#xff09; 模式。这种模式用于应用程序的分层开发。 Model&#xff08;模型&#xff09; - 模型代表一个存取数据的对象或 JAVA POJO。它也可以带有逻辑&#xff0c;在数据变化时更新控制器。View&#xff…

Python爬虫——requests_cookie登陆古诗文网

寻找登陆需要的参数 __VIEWSTATE:aiMG0UXAfCzak10C7436ZC/RXoZbM2lDlX1iU/4wjjdUNsW8QUs6W2/3M6XIKagQZrC7ooD8Upj8uCnpQMXjDAp6fS/NM2nGhnKO0KOSXfT3jGHhJAOBouMI3QnlpJCQKPXfVDJPYwh169MGLFC6trY __VIEWSTATEGENERATOR: C93BE1AE from: http://so.gushiwen.cn/user/collect.…

泰卦-地天卦

前言&#xff1a;否极泰来&#xff0c;但在易经里是泰卦在前&#xff0c;让我们分析下在否所期待否极后的泰卦是什么样的&#xff1f;本篇博客分析泰卦的卦辞和爻辞。 卦辞 小往大来&#xff0c;吉&#xff0c;亨。 篆曰&#xff1a;泰&#xff0c;小往大来&#xff0c;吉亨。…

面试热题(合并两个有序列表)

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 合并链表这类型题也是比较经典的题了&#xff0c;因为链表是由指针相互指向而确定位置&#xff0c;所以我们只需要改变某些节点的指针便可以做到对链表进行排序 今天这个方法…

C++小游戏贪吃蛇源码

graphics.h是针对DOS下的一个C语言图形库 (c也可以) 目前支持下载此头文件的常用的有两种: 1. EGE (Easy Graphics Engine)2. EasyX Graphics LibraryEGE, 全名Easy Graphics Engine, 是windows下的简易绘图库&#xff0c;是一个类似BGI(graphics.h)的面向C/C语言新手的图形库…

APP外包开发的iOS开发语言

学习iOS开发需要掌握Swift编程语言和相关的开发工具、框架和技术。而学习iOS开发需要时间和耐心&#xff0c;尤其是对于初学者。通过坚持不懈的努力&#xff0c;您可以逐步掌握iOS开发技能&#xff0c;构建出功能丰富、优质的移动应用。今天和大家分享学习iOS开发的一些建议方法…

掌握Python的X篇_32_使用python编辑pdf文件_pdfrw

本篇介绍利用python操作pdf文件&#xff0c;我们平时也会有合并和拆分pdf的需求&#xff0c;此时我们就可以使用本节内容。 文章目录 1. pdfrw的安装2. 切分pdf文件3. pdfrw官网及实现一版四面的实例 1. pdfrw的安装 pip install pdfrw官网地址&#xff1a;https://github.co…

机器学习深度学习——常见循环神经网络结构(RNN、LSTM、GRU)

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——RNN的从零开始实现与简洁实现 &#x1f4da;订阅专栏&#xff1a;机器学习&&深度学习 希望文章…

ETLCloud+MaxCompute实现云数据仓库的高效实时同步

MaxCompute介绍 MaxCompute是适用于数据分析场景的企业级SaaS&#xff08;Software as a Service&#xff09;模式云数据仓库&#xff0c;以Serverless架构提供快速、全托管的在线数据仓库服务&#xff0c;消除了传统数据平台在资源扩展性和弹性方面的限制&#xff0c;最小化用…