0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

支付宝:多线程事务怎么回滚?

jf_ro2CN3Fa 来源:CSDN 2023-01-09 11:42 次阅读


背景介绍

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

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

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

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

公用的类和方法

/**
*平均拆分list方法.
*@paramsource
*@paramn
*@param
*@return
*/
publicstaticList>averageAssign(Listsource,intn){
List>result=newArrayList>();
intremaider=source.size()%n;
intnumber=source.size()/n;
intoffset=0;//偏移量
for(inti=0;ivalue=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);
}
returnresult;
}
/**线程池配置
*@versionV1.0
*/
publicclassExecutorConfig{
privatestaticintmaxPoolSize=Runtime.getRuntime().availableProcessors();
privatevolatilestaticExecutorServiceexecutorService;
publicstaticExecutorServicegetThreadPool(){
if(executorService==null){
synchronized(ExecutorConfig.class){
if(executorService==null){
executorService=newThreadPool();
}
}
}
returnexecutorService;
}

privatestaticExecutorServicenewThreadPool(){
intqueueSize=500;
intcorePool=Math.min(5,maxPoolSize);
returnnewThreadPoolExecutor(corePool,maxPoolSize,10000L,TimeUnit.MILLISECONDS,
newLinkedBlockingQueue<>(queueSize),newThreadPoolExecutor.AbortPolicy());
}
privateExecutorConfig(){}
}
/**获取sqlSession
*@author86182
*@versionV1.0
*/
@Component
publicclassSqlContext{
@Resource
privateSqlSessionTemplatesqlSessionTemplate;

publicSqlSessiongetSqlSession(){
SqlSessionFactorysqlSessionFactory=sqlSessionTemplate.getSqlSessionFactory();
returnsqlSessionFactory.openSession();
}
}

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud
  • 视频教程:https://doc.iocoder.cn/video/

示例事务不成功操作

/**
*测试多线程事务.
*@paramemployeeDOList
*/
@Override
@Transactional
publicvoidsaveThread(ListemployeeDOList){
try{
//先做删除操作,如果子线程出现异常,此操作不会回滚
this.getBaseMapper().delete(null);
//获取线程池
ExecutorServiceservice=ExecutorConfig.getThreadPool();
//拆分数据,拆分5份
List>lists=averageAssign(employeeDOList,5);
//执行的线程
Thread[]threadArray=newThread[lists.size()];
//监控子线程执行完毕,再执行主线程,要不然会导致主线程关闭,子线程也会随着关闭
CountDownLatchcountDownLatch=newCountDownLatch(lists.size());
AtomicBooleanatomicBoolean=newAtomicBoolean(true);
for(inti=0;iif(i==lists.size()-1){
atomicBoolean.set(false);
}
Listlist=lists.get(i);
threadArray[i]=newThread(()->{
try{
//最后一个线程抛出异常
if(!atomicBoolean.get()){
thrownewServiceException("001","出现异常");
}
//批量添加,mybatisPlus中自带的batch方法
this.saveBatch(list);
}finally{
countDownLatch.countDown();
}

});
}
for(inti=0;i//当子线程执行完毕时,主线程再往下执行
countDownLatch.await();
System.out.println("添加完毕");
}catch(Exceptione){
log.info("error",e);
thrownewServiceException("002","出现异常");
}finally{
connection.close();
}
}

数据库中存在一条数据:

07f6f516-8fc2-11ed-bfe3-dac502259ad0.png
//测试用例
@RunWith(SpringRunner.class)
@SpringBootTest(classes={ThreadTest01.class,MainApplication.class})
publicclassThreadTest01{

@Resource
privateEmployeeBOemployeeBO;

/**
*测试多线程事务.
*@throwsInterruptedException
*/
@Test
publicvoidMoreThreadTest2()throwsInterruptedException{
intsize=10;
ListemployeeDOList=newArrayList<>(size);
for(inti=0;inewEmployeeDO();
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(Exceptione){
e.printStackTrace();
}
}
}

测试结果:

08028656-8fc2-11ed-bfe3-dac502259ad0.png08120248-8fc2-11ed-bfe3-dac502259ad0.png

可以发现子线程组执行时,有一个线程执行失败,其他线程也会抛出异常,但是主线程中执行的删除操作,没有回滚,@Transactional注解没有生效。

使用sqlSession控制手动提交事务

@Resource
SqlContextsqlContext;
/**
*测试多线程事务.
*@paramemployeeDOList
*/
@Override
publicvoidsaveThread(ListemployeeDOList)throwsSQLException{
//获取数据库连接,获取会话(内部自有事务)
SqlSessionsqlSession=sqlContext.getSqlSession();
Connectionconnection=sqlSession.getConnection();
try{
//设置手动提交
connection.setAutoCommit(false);
//获取mapper
EmployeeMapperemployeeMapper=sqlSession.getMapper(EmployeeMapper.class);
//先做删除操作
employeeMapper.delete(null);
//获取执行器
ExecutorServiceservice=ExecutorConfig.getThreadPool();
List>callableList=newArrayList<>();
//拆分list
List>lists=averageAssign(employeeDOList,5);
AtomicBooleanatomicBoolean=newAtomicBoolean(true);
for(inti=0;iif(i==lists.size()-1){
atomicBoolean.set(false);
}
Listlist=lists.get(i);
//使用返回结果的callable去执行,
Callablecallable=()->{
//让最后一个线程抛出异常
if(!atomicBoolean.get()){
thrownewServiceException("001","出现异常");
}
returnemployeeMapper.saveBatch(list);
};
callableList.add(callable);
}
//执行子线程
List>futures=service.invokeAll(callableList);
for(Futurefuture:futures){
//如果有一个执行不成功,则全部回滚
if(future.get()<=0){
connection.rollback();
return;
}
}
connection.commit();
System.out.println("添加完毕");
}catch(Exceptione){
connection.rollback();
log.info("error",e);
thrownewServiceException("002","出现异常");
}finally{
connection.close();
}
}
//sql
"saveBatch"parameterType="List">
INSERTINTO
employee(employee_id,age,employee_name,birth_date,gender,id_number,creat_time,update_time,status)
values
="list"item="item"index="index"separator=",">
(
#{item.employeeId},
#{item.age},
#{item.employeeName},
#{item.birthDate},
#{item.gender},
#{item.idNumber},
#{item.creatTime},
#{item.updateTime},
#{item.status}
)


数据库中一条数据:

081f036c-8fc2-11ed-bfe3-dac502259ad0.png

测试结果:抛出异常,

0836bc00-8fc2-11ed-bfe3-dac502259ad0.png

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

0847e7d2-8fc2-11ed-bfe3-dac502259ad0.png

成功操作示例:

@Resource
SqlContextsqlContext;
/**
*测试多线程事务.
*@paramemployeeDOList
*/
@Override
publicvoidsaveThread(ListemployeeDOList)throwsSQLException{
//获取数据库连接,获取会话(内部自有事务)
SqlSessionsqlSession=sqlContext.getSqlSession();
Connectionconnection=sqlSession.getConnection();
try{
//设置手动提交
connection.setAutoCommit(false);
EmployeeMapperemployeeMapper=sqlSession.getMapper(EmployeeMapper.class);
//先做删除操作
employeeMapper.delete(null);
ExecutorServiceservice=ExecutorConfig.getThreadPool();
List>callableList=newArrayList<>();
List>lists=averageAssign(employeeDOList,5);
for(inti=0;ilist=lists.get(i);
Callablecallable=()->employeeMapper.saveBatch(list);
callableList.add(callable);
}
//执行子线程
List>futures=service.invokeAll(callableList);
for(Futurefuture:futures){
if(future.get()<=0){
connection.rollback();
return;
}
}
connection.commit();
System.out.println("添加完毕");
}catch(Exceptione){
connection.rollback();
log.info("error",e);
thrownewServiceException("002","出现异常");
//thrownewServiceException(ExceptionCodeEnum.EMPLOYEE_SAVE_OR_UPDATE_ERROR);
}
}

测试结果:

08585db0-8fc2-11ed-bfe3-dac502259ad0.png

数据库中数据:

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

0869dc84-8fc2-11ed-bfe3-dac502259ad0.png

审核编辑 :李倩


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 多线程
    +关注

    关注

    0

    文章

    277

    浏览量

    19919
  • spring
    +关注

    关注

    0

    文章

    338

    浏览量

    14307

原文标题:支付宝:多线程事务怎么回滚?说用 @Transactional 可以回去等通知了!

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    socket 多线程编程实现方法

    在现代网络编程中,多线程技术被广泛应用于提高服务器的并发处理能力。Socket编程是网络通信的基础,而将多线程技术应用于Socket编程,可以显著提升服务器的性能。 多线程编程的基本概念 多线
    的头像 发表于 11-12 14:16 187次阅读

    支付宝与华为终端达成战略合作

    近日,支付宝与华为终端正式宣布达成战略合作,共同致力于鸿蒙生态与碰一下生态的深度融合与发展。
    的头像 发表于 11-11 14:46 214次阅读

    eBay携手蚂蚁国际旗下Antom,支付宝成eBay新支付选项

    近日,eBay宣布与蚂蚁国际旗下的Antom达成合作,正式将支付宝纳入其支付选项,为中国内地买家提供更加便捷、安全的支付体验。 此次合作意味着,中国内地消费者在eBay平台上购物时,可直接使用
    的头像 发表于 11-11 13:56 232次阅读

    支付宝与华为终端达成战略合作,共同推动移动支付进入“碰时代”

    近日,支付宝与华为终端宣布了一项重要的战略合作。此次合作将围绕鸿蒙生态与支付宝的“碰一下”生态展开,旨在为用户提供更加便捷、高效的移动支付体验。 根据双方签署的战略合作协议,华为将通过软硬件的全面
    的头像 发表于 11-10 11:16 537次阅读

    Spring事务实现原理

    作者:京东零售 范锡军 1、引言 spring的spring-tx模块提供了对事务管理支持,使用spring事务可以让我们从复杂的事务处理中得到解脱,无需要去处理获得连接、关闭连接、事务
    的头像 发表于 11-08 10:10 708次阅读
    Spring<b class='flag-5'>事务</b>实现原理

    Python中多线程和多进程的区别

    Python作为一种高级编程语言,提供了多种并发编程的方式,其中多线程与多进程是最常见的两种方式之一。在本文中,我们将探讨Python中多线程与多进程的概念、区别以及如何使用线程池与进程池来提高并发执行效率。
    的头像 发表于 10-23 11:48 306次阅读
    Python中<b class='flag-5'>多线程</b>和多进程的区别

    ESP32会不会有多线程问题,需要加锁吗?

    ESP32会不会有多线程问题,需要加锁吗
    发表于 07-19 08:05

    复旦微电 Boost Tag 芯片助力支付宝NFC支付

    随着技术的进步,移动支付技术正快速更新迭代。近日,支付宝正式推出NFC支付功能,用户只需将手机“碰一碰”收银台NFC支付设备,即可轻松完成支付
    的头像 发表于 07-12 18:02 1680次阅读

    欢创播报 支付宝“碰一下”正式发布

    1 支付宝“碰一下”正式发布 近日,在支付宝开放日上,支付宝宣布升级条码支付体验,推出“支付宝碰一下”,用户无需展示付款码,解锁手机碰一下商
    的头像 发表于 07-11 11:32 848次阅读
    欢创播报  <b class='flag-5'>支付宝</b>“碰一下”正式发布

    多线程设计模式到对 CompletableFuture 的应用

    最近在开发 延保服务 频道页时,为了提高查询效率,使用到了多线程技术。为了对多线程方案设计有更加充分的了解,在业余时间读完了《图解 Java 多线程设计模式》这本书,觉得收获良多。本篇文章将介绍其中
    的头像 发表于 06-26 14:18 265次阅读
    从<b class='flag-5'>多线程</b>设计模式到对 CompletableFuture 的应用

    java实现多线程的几种方式

    Java实现多线程的几种方式 多线程是指程序中包含了两个或以上的线程,每个线程都可以并行执行不同的任务或操作。Java中的多线程可以提高程序
    的头像 发表于 03-14 16:55 567次阅读

    AT socket可以多线程调用吗?

    请问AT socket 可以多线程调用吗? 有互锁机制吗,还是要自己做互锁。
    发表于 03-01 08:22

    支付宝与华为联手推动鸿蒙原生应用开发 

    华为常务董事、终端 BG CEO、智能汽车解决方案 BU 董事长余承东指出,支付宝参与鸿蒙原生应用项目,对于鸿蒙系统及生态的发展具有重要意义。他强调,HarmonyOS 旨在打造跨设备、万物互联的生态环境,希望能吸引更多优秀企业共同构建
    的头像 发表于 12-11 15:43 3876次阅读

    redis多线程还能保证线程安全吗

    是单线程的,多个客户端请求会按序执行,每个请求使用一个线程完成,这样可以避免多线程之间的竞争条件和锁等带来的开销。但是,由于Redis是存储内存中的数据的,当多个客户端同时对同一个数据进行读写操作时,就会存在
    的头像 发表于 12-05 10:28 1680次阅读

    mfc多线程编程实例

    (图形用户界面)应用程序的开发。在这篇文章中,我们将重点介绍MFC中的多线程编程。 多线程编程在软件开发中非常重要,它可以实现程序的并发执行,提高程序的效率和响应速度。MFC提供了丰富的多线程支持,可以轻松地实现
    的头像 发表于 12-01 14:29 1416次阅读