当前位置: 首页 > news >正文

多线程事务怎么回滚

背景介绍


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

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

3,下面用一个简单示例演示多线程事务。

公用的类和方法


/*** 平均拆分list方法.* @param source* @param n* @param <T>* @return*/
public static <T> List<List<T>> averageAssign(List<T> source,int n){List<List<T>> result=new ArrayList<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 {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 = 500;int corePool = Math.min(5, maxPoolSize);return new ThreadPoolExecutor(corePool, maxPoolSize, 10000L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>(queueSize),new ThreadPoolExecutor.AbortPolicy());}private ExecutorConfig(){}
}
/** 获取sqlSession* @author 86182* @version V1.0*/
@Component
public class SqlContext {@Resourceprivate SqlSessionTemplate sqlSessionTemplate;public SqlSession getSqlSession(){SqlSessionFactory sqlSessionFactory = sqlSessionTemplate.getSqlSessionFactory();return sqlSessionFactory.openSession();}
}

示例事务不成功操作


 /*** 测试多线程事务.* @param employeeDOList*/
@Override
@Transactional
public void saveThread(List<EmployeeDO> employeeDOList) {try {//先做删除操作,如果子线程出现异常,此操作不会回滚this.getBaseMapper().delete(null);//获取线程池ExecutorService service = ExecutorConfig.getThreadPool();//拆分数据,拆分5份List<List<EmployeeDO>> lists=averageAssign(employeeDOList, 5);//执行的线程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.set(false);}List<EmployeeDO> list  = lists.get(i);threadArray[i] =  new Thread(() -> {try {//最后一个线程抛出异常if (!atomicBoolean.get()){throw new ServiceException("001","出现异常");}//批量添加,mybatisPlus中自带的batch方法this.saveBatch(list);}finally {countDownLatch.countDown();}});}for (int i = 0; i <lists.size(); i++){service.execute(threadArray[i]);}//当子线程执行完毕时,主线程再往下执行countDownLatch.await();System.out.println("添加完毕");}catch (Exception e){log.info("error",e);throw new ServiceException("002","出现异常");}finally {connection.close();}
}

数据库中存在一条数据:

Spring Boot 基础就不介绍了,推荐下这个实战教程:https://github.com/javastacks/spring-boot-best-practice

//测试用例
@RunWith(SpringRunner.class)
@SpringBootTest(classes = { ThreadTest01.class, MainApplication.class})
public class ThreadTest01 {@Resourceprivate EmployeeBO employeeBO;/***   测试多线程事务.* @throws InterruptedException*/@Testpublic  void MoreThreadTest2() throws InterruptedException {int size = 10;List<EmployeeDO> employeeDOList = new ArrayList<>(size);for (int i = 0; i<size;i++){EmployeeDO employeeDO = new EmployeeDO();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控制手动提交事务

 @ResourceSqlContext sqlContext;/*** 测试多线程事务.* @param employeeDOList*/
@Override
public void saveThread(List<EmployeeDO> employeeDOList) 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<EmployeeDO>> lists=averageAssign(employeeDOList, 5);AtomicBoolean atomicBoolean = new AtomicBoolean(true);for (int i =0;i<lists.size();i++){if (i==lists.size()-1){atomicBoolean.set(false);}List<EmployeeDO> list  = lists.get(i);//使用返回结果的callable去执行,Callable<Integer> callable = () -> {//让最后一个线程抛出异常if (!atomicBoolean.get()){throw new 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);throw new ServiceException("002","出现异常");}finally {connection.close();}
}
// sql
<insert id="saveBatch" parameterType="List">INSERT INTOemployee (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面试库小程序在线刷题,涵盖 2000+ 道 Java 面试题,几乎覆盖了所有主流技术面试题。

成功操作示例:

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

测试结果:

数据库中数据:

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

相关文章:

多线程事务怎么回滚

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

基于FPGA的时间数字转换(TDC)设计(五:基于Carry4的高精度TDC设计)

1.基于Carry4进位链设计原理 常见的基于FPGA开发的TDC有直接计数法,多相位时钟采样法,抽头延迟线法等,之前内容为基于多相位的TDC,本章节中,主要讲解基于抽头延迟线法。在Xilinx FPGA开发中,实现抽头延迟线法有很多种,如使用IODELAY构建延迟进位链,此处将介绍基于Carr…...

【C++】二叉搜索树的实现(递归和非递归实现)

文章目录1、二叉搜索树1.1 构建二叉搜索树1.2 二叉搜索树的插入1.3 二叉搜索树的删除1.4 二叉搜索树插入和删除的递归实现为了学习map和set的底层实现&#xff0c;需要知道红黑树&#xff0c;知道红黑树之前需要知道AVL树。 红黑树和AVL树都用到了二叉搜索树结构&#xff0c;所…...

春招来了,如何正确使用领英超高效招聘海外员工、挖掘人才?

金三银四到了&#xff0c;每年的这个时候都是企业招聘的好时机。而领英是目前全球最大的职场社交网络平台&#xff0c;基本上海外求职都是在使用它&#xff0c;所以很多企业涉及到海外招聘时&#xff0c;都会优先考虑领英&#xff0c;但是却经常缺乏一些经验技巧&#xff0c;今…...

Mysql中锁机制深入理解

Mysql中锁机制深入理解默认大家已经知道。分类性能悲观锁&#xff0c;乐观锁操作类型读锁&#xff0c;写锁&#xff0c;数据粒度表锁&#xff0c;行锁&#xff0c;页面锁更细粒度间隙锁&#xff0c;临键锁按使用来讲。由数据粒度出发。表锁&#xff0c;分为 共享锁&#xff0c;…...

去中心化社交网络协议除了Nostr还有哪些?

当下最火的去中心化社交软件Dmaus就是基于Nostr协议开发的&#xff0c;Nostr协议的基本情况之前的文章《一文了解去中心化社交网络协议Nostr》已经做了详细介绍&#xff0c;本文将介绍其他几个目前比较流行的去中心化社交协议。FarcasterFarcaster是由前Coinbase高管Dan Romero…...

【FT2000/4+X100】调试记录

订阅专栏 硬件环境FT2000/4+X100,单板结构,对外显示,运行银行麒麟操作系统。 一 生成UEFI.BIN,烧写在FT2000-4的QSPI Flash中 1 2 下载源文件 edk2-for-support.tar; 参考文件 ft2004c&D2000编译打包说明V1.0.5; 解压源文件; 根目录下 build2004C.sh为四核产品…...

我的Android启动优化—【黑白屏优化】

简述 在Android App使用过程中&#xff0c;对于应用的优化是一个加分项&#xff0c;举个例子&#xff0c;打开你的App需要2秒&#xff0c;人家0.5秒&#xff0c;这就是很大的用户体验上的优化。 问题的产生 在开发中&#xff0c;我们在启动app的时候&#xff0c;屏幕会出现一…...

TongWeb8编码设置说明

应用场景&#xff1a;在遇到中文问题时&#xff0c;常需要通过设置编码格式来解决问题。下面介绍TongWeb8的编码设置及优先级。一、web.xml中请求、响应编码的配置优先级最高在JavaEE8规范中web.xml增加了request, response编码配置&#xff0c;该配置优先级最高。<?xml ve…...

不同相机之间图片像素对应关系求解(单应性矩阵求解)

一、场景 相机1和相机2相对位置不变&#xff0c;相机拍摄图片有重叠&#xff0c;求他们交叠部分的一一对应关系。数学语言描述为已知相机1图片中P点像素(u1, v1)&#xff0c;相机1中P点在相机2图片中像素值为(u2, v2)&#xff0c;它们存在某种变换&#xff0c;求变换矩阵。 因为…...

远程管理时代,还得是智能化PDU才靠得住!

在如今这个信息技术高速发展的时代&#xff0c;数据中心IDC机房服务器数量与日俱增&#xff0c;提供DNS域名服务、主机托管服务、虚拟主机服务等服务的服务器是IDC最基本的功能之一。服务器需要7*24小时不间断持续工作&#xff0c;但当服务器数量很大&#xff0c;服务器工作、重…...

通俗易懂理解——布隆过滤器

文章目录概述本质优缺点优点&#xff1a;缺点&#xff1a;实际应用解决redis缓存穿透问题&#xff1a;概述 本质 本质&#xff1a;很长的二进制向量&#xff08;数组&#xff09; 主要作用&#xff1a;判断一个数据在这个数组中是否存在&#xff0c;如果不存在为0&#xff0c…...

TypeScript 学习之类型推导

在一些情况下&#xff0c;代码上没有显性明确类型&#xff0c;typescript 可以隐形推断出类型。 基础 let x 3;变量x的类型被推断为数字。 类型推断发生在初始化变量和成员&#xff0c;设置默认参数值和决定函数返回值时 最佳通用类型 let x [0, 1, null]; // 类型为 numb…...

Android四大组件——Service详解

Service 为后台运行&#xff0c;不可见&#xff0c;没有界面。优先级高于Activity&#xff08;内存不足时先杀掉Activity&#xff09;&#xff0c;运行在主线程且不能做耗时操作。 一、Service 启动方式 1、startService() 通过 startService 启动后&#xff0c;service会一直…...

svg转png

svg转png写了一个spring boot项目&#xff0c;支持传入svg文件转出png图片&#xff0c;并且自定义转出png的宽和高。主要代码如下&#xff1a;所需依赖如下&#xff1a;演示如下&#xff1a;首先&#xff0c;运行项目使用接口调用工具调用接口发送请求&#xff0c;提取文件1000…...

教你如何搭建人事OA-员工管理系统,demo可分享

1、简介1.1、案例简介本文将介绍&#xff0c;如何搭建人事OA-员工管理。1.2、应用场景人事OA-员工管理应用对员工信息进行管理&#xff0c;可办理入职、转正、离职等流程。2、设置方法2.1、表单搭建1&#xff09;新建表单【员工管理】&#xff0c;字段设置如下&#xff1a;名称…...

C++递推基础知识

文章目录一、递推的概念二、递推和递归的区别三、递推的实例1、最基础的&#xff1a;斐波那契数列2、变形版斐波那契数列3、较复杂的递推式求解&#xff1a;昆虫繁殖4、经典逆推问题&#xff1a;题目数量一、递推的概念 1、什么是递推算法&#xff1f; 递推算法&#xff1a;是…...

【Python入门第十天】Python 布尔

布尔表示两值之一&#xff1a;True 或 False。 布尔值 在编程中&#xff0c;通常需要知道表达式是 True 还是 False。 可以计算 Python 中的任何表达式&#xff0c;并获得两个答案之一&#xff0c;即 True 或 False。 比较两个值时&#xff0c;将对表达式求值&#xff0c;P…...

WebDAV之π-Disk派盘+Piktures

Piktures支持WebDAV方式连接π-Disk派盘。推荐一款简单易用&#xff0c;功能超级强大的智能相册应用。Piktures智能相册是一款简单易用&#xff0c;功能超级强大的智能相册应用&#xff0c;它不仅可以访问本地和云照片&#xff0c;还可以照片编辑器&#xff0c;而且它同时还是一…...

Revit问题:Navisworks中导入的rvt模型角度不正确调整

一、Navisworks中导入的rvt模型角度不正确调整方法 通常情况下&#xff0c;我们做好一个Revit模型&#xff0c;有时候出于成果保护或者鉴于Revit自带的碰撞检测效果不够直观、Revit模型体量太大&#xff0c;需要一个轻量化的模型展示&#xff0c;我们通常情况下会使用Autodesk公…...

RestClient

什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端&#xff0c;它允许HTTP与Elasticsearch 集群通信&#xff0c;而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级&#xff…...

ES6从入门到精通:前言

ES6简介 ES6&#xff08;ECMAScript 2015&#xff09;是JavaScript语言的重大更新&#xff0c;引入了许多新特性&#xff0c;包括语法糖、新数据类型、模块化支持等&#xff0c;显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var&#xf…...

DockerHub与私有镜像仓库在容器化中的应用与管理

哈喽&#xff0c;大家好&#xff0c;我是左手python&#xff01; Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库&#xff0c;用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...

Golang dig框架与GraphQL的完美结合

将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用&#xff0c;可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器&#xff0c;能够帮助开发者更好地管理复杂的依赖关系&#xff0c;而 GraphQL 则是一种用于 API 的查询语言&#xff0c;能够提…...

【算法训练营Day07】字符串part1

文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接&#xff1a;344. 反转字符串 双指针法&#xff0c;两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...

微信小程序云开发平台MySQL的连接方式

注&#xff1a;微信小程序云开发平台指的是腾讯云开发 先给结论&#xff1a;微信小程序云开发平台的MySQL&#xff0c;无法通过获取数据库连接信息的方式进行连接&#xff0c;连接只能通过云开发的SDK连接&#xff0c;具体要参考官方文档&#xff1a; 为什么&#xff1f; 因为…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配

目录 一、C 内存的基本概念​ 1.1 内存的物理与逻辑结构​ 1.2 C 程序的内存区域划分​ 二、栈内存分配​ 2.1 栈内存的特点​ 2.2 栈内存分配示例​ 三、堆内存分配​ 3.1 new和delete操作符​ 4.2 内存泄漏与悬空指针问题​ 4.3 new和delete的重载​ 四、智能指针…...

探索Selenium:自动化测试的神奇钥匙

目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...

python爬虫——气象数据爬取

一、导入库与全局配置 python 运行 import json import datetime import time import requests from sqlalchemy import create_engine import csv import pandas as pd作用&#xff1a; 引入数据解析、网络请求、时间处理、数据库操作等所需库。requests&#xff1a;发送 …...

适应性Java用于现代 API:REST、GraphQL 和事件驱动

在快速发展的软件开发领域&#xff0c;REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名&#xff0c;不断适应这些现代范式的需求。随着不断发展的生态系统&#xff0c;Java 在现代 API 方…...