news 2026/4/8 3:14:42

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
面试官:多线程事务怎么回滚?说用@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); } }

数据库中数据:

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/31 5:32:10

Qwen3-1.7B高并发部署案例:多用户访问性能优化策略

Qwen3-1.7B高并发部署案例&#xff1a;多用户访问性能优化策略 Qwen3-1.7B 是通义千问系列中的一款轻量级大语言模型&#xff0c;具备出色的推理能力与响应速度。它在保持较小参数规模的同时&#xff0c;依然能够支持复杂任务的生成与理解&#xff0c;非常适合用于需要快速部署…

作者头像 李华
网站建设 2026/4/2 15:16:10

学习干货_从迷茫到前行:我的网络安全学习之路

网络安全成长之路&#xff1a;从零基础到实战专家的学习指南&#xff08;建议收藏&#xff09; 本文作者"州弟"分享了自己从网络安全小白成长为专业人员的经历。他强调破除"学生思维"&#xff0c;通过实践而非死记硬背学习&#xff1b;推荐扎实掌握Linux、…

作者头像 李华
网站建设 2026/3/28 15:53:12

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

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

作者头像 李华
网站建设 2026/4/7 18:20:06

【Boost.Asio网络编程效率提升10倍】:异步I/O模型底层原理全解析

第一章&#xff1a;Boost.Asio网络编程的核心优势与应用场景 Boost.Asio 是一个基于 C 的跨平台库&#xff0c;专为异步 I/O 操作设计&#xff0c;广泛应用于高性能网络服务开发。其核心优势在于统一的异步模型、对底层操作系统的高效封装&#xff0c;以及对现代 C 特性的深度支…

作者头像 李华
网站建设 2026/4/4 4:31:14

C++ vector何时扩容?扩容因子是多少?真相全在这里(附实测数据)

第一章&#xff1a;C STL vector 扩容机制详解 C 标准模板库&#xff08;STL&#xff09;中的 std::vector 是最常用且高效的动态数组容器之一。其核心优势在于能够自动管理内存&#xff0c;支持动态扩容&#xff0c;从而在运行时灵活地添加或删除元素。 扩容触发条件 当向 ve…

作者头像 李华
网站建设 2026/3/30 19:04:37

【C++项目构建避坑指南】:为什么总出现undefined reference?真相曝光

第一章&#xff1a;undefined reference错误的本质解析 undefined reference 是C/C开发中常见的链接阶段错误&#xff0c;它表示编译器成功完成了源码的语法分析与目标文件生成&#xff0c;但在链接过程中无法找到某些符号&#xff08;函数、变量等&#xff09;的实际定义。该错…

作者头像 李华