面试官:多线程事务怎么回滚?说用@Transactional可以回去等通知了!

大家好,我是力哥。

最近有一个大数据量插入的操作入库的业务场景,需要先做一些其他修改操作,然后在执行插入操作,由于插入数据可能会很多,用到多线程去拆分数据并行处理来提高响应时间,如果有一个线程执行失败,则全部回滚;

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

下面用一个简单示例演示多线程事务:

公用的类和方法

/** * 平均拆分list方法. * @param source * @param n * @param <T> * @return */ public static <T> List<List<T>> averageAssign(List<T> source,int n){ List<List<T>> result=newArrayList<List<T>>(); int remaider=source.size()%n; int number=source.size()/n; int offset=0;//偏移量 for(int i=0;i<n;i++){ List<T> value=null; 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; }
/**线程池配置 * @version V1.0 */ public class ExecutorConfig { privatestaticintmaxPoolSize= Runtime.getRuntime().availableProcessors(); privatevolatilestatic ExecutorService executorService; publicstatic ExecutorService getThreadPool() { if (executorService == null){ synchronized (ExecutorConfig.class){ if (executorService == null){ executorService = newThreadPool(); } } } return executorService; } private static ExecutorService newThreadPool(){ intqueueSize=500; intcorePool= Math.min(5, maxPoolSize); returnnew ThreadPoolExecutor(corePool, maxPoolSize, 10000L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(queueSize),newThreadPoolExecutor.AbortPolicy()); } privateExecutorConfig(){ } }
/** 获取sqlSession * @version V1.0 */ @Component public class SqlContext { @Resource private SqlSessionTemplate sqlSessionTemplate; public SqlSession getSqlSession(){ SqlSessionFactorysqlSessionFactory= sqlSessionTemplate.getSqlSessionFactory(); return sqlSessionFactory.openSession(); } }

示例事务不成功操作

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案
【点击此处即可/免费获取】​​​

/** * 测试多线程事务. * @param employeeDOList */ @Override @Transactional(rollbackFor = Exception.class) public void saveThread(List<EmployeeDO> employeeDOList) { try { //先做删除操作,如果子线程出现异常,此操作不会回滚 this.getBaseMapper().delete(null); //获取线程池 ExecutorServiceservice= ExecutorConfig.getThreadPool(); //拆分数据,拆分5份 List<List<EmployeeDO>> lists=averageAssign(employeeDOList,2); //执行的线程 Thread []threadArray = newThread[lists.size()]; //监控子线程执行完毕,再执行主线程,要不然会导致主线程关闭,子线程也会随着关闭 CountDownLatchcountDownLatch=newCountDownLatch(lists.size()); AtomicBooleanatomicBoolean=newAtomicBoolean(false); for (inti=0;i<lists.size();i++){ List<EmployeeDO> list = lists.get(i); threadArray[i] = newThread(() -> { try { if (!atomicBoolean.get()){ thrownew ServiceException("001","出现异常"); } //批量添加,mybatisPlus中自带的batch方法 this.saveBatch(list); }finally { countDownLatch.countDown(); } }); } for (inti=0; i <lists.size(); i++){ service.execute(threadArray[i]); } //当子线程执行完毕时,主线程再往下执行 countDownLatch.await(); System.out.println("添加完毕"); }catch (Exception e){ log.info("error",e); thrownew ServiceException("002","出现异常"); } }

//测试用例 @RunWith(SpringRunner.class) @SpringBootTest(classes = { ThreadTest01.class, MainApplication.class}) public class ThreadTest01 { @Resource private EmployeeBO employeeBO; /** * 测试多线程事务. * @throws InterruptedException */ @Test public void MoreThreadTest2() throws InterruptedException { intsize=10; List<EmployeeDO> employeeDOList = new ArrayList<>(size); for (inti=0; i<size;i++){ EmployeeDOemployeeDO=newEmployeeDO(); employeeDO.setEmployeeName("lol"+i); employeeDO.setAge(18); employeeDO.setGender(1); employeeDO.setIdNumber(i+"XX"); employeeDO.setCreatTime(Calendar.getInstance().getTime()); employeeDOList.add(employeeDO); } try { employeeBO.saveThread(employeeDOList); System.out.println("添加成功"); }catch (Exception e){ e.printStackTrace(); } } }

测试结果:

可以发现子线程组执行时,所有线程执行失败抛出异常,但是主线程中执行的删除操作,没有回滚,Transactional注解没有生效.

使用sqlSession控制手动提交事务

@Resource SqlContext sqlContext; /** * 测试多线程事务. * @param employeeDOList */ @Override public void saveThread(List<EmployeeDO> employeeDOList)throws SQLException { // 获取数据库连接,获取会话(内部自有事务) SqlSessionsqlSession= sqlContext.getSqlSession(); Connectionconnection= sqlSession.getConnection(); try { // 设置手动提交 connection.setAutoCommit(false); //获取mapper EmployeeMapperemployeeMapper= sqlSession.getMapper(EmployeeMapper.class); //先做删除操作 employeeMapper.delete(null); //获取执行器 ExecutorServiceservice= ExecutorConfig.getThreadPool(); List<Callable<Integer>> callableList = newArrayList<>(); //拆分list List<List<EmployeeDO>> lists=averageAssign(employeeDOList, 5); for (inti=0;i<lists.size();i++){ AtomicBooleanatomicBoolean=newAtomicBoolean(true); if (i==lists.size()-1){ atomicBoolean.set(false); } List<EmployeeDO> list = lists.get(i); //使用返回结果的callable去执行, Callable<Integer> callable = () -> { if (!atomicBoolean.get()){ thrownew ServiceException("001","出现异常"); } return employeeMapper.saveBatch(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); thrownew ServiceException("002","出现异常"); }finally { connection.close(); } }
// sql <insert id="saveBatch" parameterType="List"> INSERT INTO employee(employee_id,age,employee_name,birth_date,gender,id_number,creat_time,update_time,status) values <foreach collection="list" item="item" index="index" separator=","> ( #{ item.employeeId}, #{ item.age}, #{ item.employeeName}, #{ item.birthDate}, #{ item.gender}, #{ item.idNumber}, #{ item.creatTime}, #{ item.updateTime}, #{ item.status} ) </foreach> </insert>

数据库中一条数据:

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案
【点击此处即可/免费获取】​​​

测试结果:抛出异常

删除操作的数据回滚了,数据库中的数据依旧存在,说明事务成功了.

成功操作示例:

@Resource SqlContext sqlContext; /** * 测试多线程事务. * @param employeeDOList */ @Override public void saveThread(List<EmployeeDO> employeeDOList)throws SQLException { // 获取数据库连接,获取会话(内部自有事务) SqlSessionsqlSession= sqlContext.getSqlSession(); Connectionconnection= sqlSession.getConnection(); try { // 设置手动提交 connection.setAutoCommit(false); EmployeeMapperemployeeMapper= sqlSession.getMapper(EmployeeMapper.class); //先做删除操作 employeeMapper.delete(null); ExecutorServiceservice= ExecutorConfig.getThreadPool(); List<Callable<Integer>> callableList = newArrayList<>(); List<List<EmployeeDO>> lists=averageAssign(employeeDOList, 5); for (inti=0;i<lists.size();i++){ List<EmployeeDO> list = lists.get(i); Callable<Integer> callable = () -> employeeMapper.saveBatch(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); thrownew ServiceException("002","出现异常"); // throw new ServiceException(ExceptionCodeEnum.EMPLOYEE_SAVE_OR_UPDATE_ERROR); } }

数据库中数据:

删除的删除了,添加的添加成功了,测试成功.

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

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

相关文章

基于深度学习YOLOv10的铁路轨道缺陷检测系统(YOLOv10+YOLO数据集+UI界面+Python项目源码+模型)

一、项目介绍 摘要 本项目基于YOLOv10目标检测算法&#xff0c;开发了一套高效、精准的铁路轨道缺陷智能检测系统&#xff0c;用于自动识别轨道表面的四种常见缺陷&#xff1a;裂纹&#xff08;Crack&#xff09;、断裂&#xff08;Putus&#xff09;、剥落&#xff08;Spall…

async Task方法返回null会发生什么?(C#异步编程避坑指南)

第一章&#xff1a;async Task方法返回null会发生什么&#xff1f; 在C#中&#xff0c;async Task 方法的设计初衷是表示一个将在未来完成的异步操作。然而&#xff0c;如果此类方法意外或故意返回 null&#xff0c;将会引发运行时异常&#xff0c;而非编译错误。这是因为 Task…

基于深度学习YOLOv10的钢铁腐蚀生锈检测系统(YOLOv10+YOLO数据集+UI界面+Python项目源码+模型)

一、项目介绍 摘要 本项目基于YOLOv10目标检测算法&#xff0c;开发了一套高精度的钢铁腐蚀生锈智能检测系统&#xff0c;专注于识别金属表面的腐蚀区域&#xff08;Corrosion&#xff09;。该系统在数据集上进行训练与优化&#xff0c;能够自动检测钢铁结构&#xff08;如桥…

Spring和SpringMVC为什么需要父子容器?

大家好&#xff0c;我是力哥。最近不少粉丝问力哥Spring和SpringMVC为什么需要父子容器&#xff1f;今天力哥来总结下&#xff0c;大家可以参考。 2026年&#xff0c;力哥又开始收Java学员了&#xff01; 在Spring框架中&#xff0c;父子容器的概念对于复杂应用的管理和模块…

Emotion2Vec+ Large部署卡顿?3步解决显存不足问题实战案例

Emotion2Vec Large部署卡顿&#xff1f;3步解决显存不足问题实战案例 1. 问题背景&#xff1a;语音情感识别系统为何启动缓慢&#xff1f; 你是不是也遇到过这种情况&#xff1a;刚部署完 Emotion2Vec Large 语音情感识别系统&#xff0c;满怀期待地打开 WebUI&#xff0c;结…

cv_resnet18_ocr-detection降本方案:低成本GPU部署节省60%

cv_resnet18_ocr-detection降本方案&#xff1a;低成本GPU部署节省60% 在OCR&#xff08;光学字符识别&#xff09;技术广泛应用的今天&#xff0c;企业对文字检测模型的部署成本越来越敏感。尤其是面对高精度需求时&#xff0c;动辄需要A100、V100等高端GPU资源&#xff0c;导…

自定义表单源码系统如何助力企业实现多场景高效运营

温馨提示&#xff1a;文末有资源获取方式在数字化时代&#xff0c;一个灵活多功能的表单系统能够显著提升企业运营效率和客户满意度。我们介绍的这款自定义表单系统源码&#xff0c;以其通用性和强大功能&#xff0c;成为各行各业实现信息收集、支付处理和预约管理的理想选择。…

基于深度学习的道路交通信号检测系统(YOLOv8+YOLO数据集+UI界面+Python项目源码+模型)

一、项目介绍 摘要 本项目基于YOLOv8深度学习框架&#xff0c;开发了一个高效准确的道路交通信号检测系统&#xff0c;能够识别21类常见的道路交通标志和信号。系统使用精心构建的专用数据集进行训练&#xff0c;包含训练集1376张、验证集488张和测试集229张图像&#xff0c;…

fft npainting lama混合精度训练配置:AMP加速收敛技巧

fft npainting lama混合精度训练配置&#xff1a;AMP加速收敛技巧 1. 引言&#xff1a;图像修复的工程实践与性能优化需求 在图像修复任务中&#xff0c;fft npainting lama 已成为当前主流的开源方案之一。它基于深度卷积网络和傅里叶空间特征建模&#xff0c;在物体移除、水…

十位营销领导者谈2026年哪些将延续,哪些将淘汰,哪些将规模化

2026年&#xff0c;AI普及、信息过载和经济压力迫使企业重塑市场进入策略&#xff0c;从渐进式调整转向精准、有纪律的增长模式。AI成为基础设施&#xff0c;用于优化内部流程和合规&#xff0c;但营销决策仍需人类监督。核心营销本质不变&#xff1a;故事叙述、个性化营销、基…

多功能表单源码系统的核心优势 带完整的搭建部署教程

温馨提示&#xff1a;文末有资源获取方式 在当今线上业务高速发展的环境中&#xff0c;一个能够无缝衔接信息收集、支付与流程管理的工具至关重要。我们诚意向您推荐一款经过深度开发的多功能自定义表单系统源码&#xff0c;它不仅是简单的信息收集工具&#xff0c;更是一个驱动…

unet人像卡通化更新日志:v1.0功能全面解读

unet人像卡通化更新日志&#xff1a;v1.0功能全面解读 1. 功能概述 unet person image cartoon compound人像卡通化工具由科哥开发&#xff0c;基于阿里达摩院 ModelScope 平台的 DCT-Net 模型构建&#xff0c;致力于将真实人物照片高效、自然地转换为卡通风格图像。该工具不…

多功能表单源码系统,解决信息收集、客户预约与线上收款的综合型工具

温馨提示&#xff1a;文末有资源获取方式面对日益增长的在线化需求&#xff0c;企业亟需一款能同时解决信息收集、客户预约与线上收款的综合型工具。我们推出的这款功能全面的自定义表单系统源码&#xff0c;正是为此而生。它集创新性、通用性与易用性于一身&#xff0c;源码获…

如何利用C++23的模块化系统重构百万行代码?真实案例分享

第一章&#xff1a;C23新特性概览与模块化重构的契机C23作为C语言演进的重要里程碑&#xff0c;引入了一系列现代化特性&#xff0c;显著提升了代码的可读性、性能和开发效率。其中&#xff0c;模块&#xff08;Modules&#xff09;的正式标准化为大型项目的组织方式带来了根本…

Open-AutoGLM安全吗?敏感操作确认机制深度解析

Open-AutoGLM安全吗&#xff1f;敏感操作确认机制深度解析 Open-AutoGLM 是智谱开源的一款面向手机端的 AI Agent 框架&#xff0c;基于视觉语言模型实现对移动设备的自动化控制。它通过 ADB&#xff08;Android Debug Bridge&#xff09;与设备通信&#xff0c;结合多模态理解…

CAM++能否做语音克隆检测?反欺诈应用探索

CAM能否做语音克隆检测&#xff1f;反欺诈应用探索 1. 引言&#xff1a;当声音也能被“复制”时&#xff0c;我们如何识别真伪&#xff1f; 你有没有想过&#xff0c;一段听起来完全真实的语音&#xff0c;可能根本不是真人说的&#xff1f;随着AI语音合成技术的飞速发展&…

如何提高召回率?FSMN-VAD敏感度参数调整指南

如何提高召回率&#xff1f;FSMN-VAD敏感度参数调整指南 1. FSMN-VAD 离线语音端点检测控制台简介 你是否在处理长录音时&#xff0c;被大量无效静音段困扰&#xff1f;是否希望自动切分语音片段却苦于精度不够&#xff1f;今天介绍的 FSMN-VAD 离线语音端点检测工具&#xf…

Qwen3-0.6B从零开始:新手开发者部署全流程详解

Qwen3-0.6B从零开始&#xff1a;新手开发者部署全流程详解 你是不是也对大模型跃跃欲试&#xff0c;但一想到复杂的环境配置、依赖安装和API调用就望而却步&#xff1f;别担心&#xff0c;这篇文章就是为你量身打造的。我们聚焦阿里巴巴最新开源的小参数模型——Qwen3-0.6B&am…

紧急警告:C++项目中出现undefined reference?立即检查这6个关键点!

第一章&#xff1a;undefined reference错误的本质解析 undefined reference 是C/C编译过程中最常见的链接错误之一&#xff0c;它表明编译器成功生成了目标文件&#xff0c;但在链接阶段无法找到某些函数或变量的定义。该错误并非语法问题&#xff0c;而是符号解析失败的体现。…

为什么你的fwrite没写入?深度解读C语言二进制写入陷阱

第一章&#xff1a;为什么你的fwrite没写入&#xff1f;从现象到本质 在使用C语言进行文件操作时&#xff0c; fwrite 函数看似简单&#xff0c;却常出现“调用成功但文件无内容”的诡异现象。这背后往往涉及缓冲机制、文件指针状态或系统调用的深层逻辑。 缓冲区未刷新导致数…