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

    文章

    275

    浏览量

    19863
  • spring
    +关注

    关注

    0

    文章

    335

    浏览量

    14268

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

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

收藏 人收藏

    评论

    相关推荐

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

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

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

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

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

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

    java实现多线程的几种方式

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

    AT socket可以多线程调用吗?

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

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

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

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

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

    mfc多线程编程实例

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

    多线程如何保证数据的同步

    多线程编程是一种并发编程的方法,意味着程序中同时运行多个线程,每个线程可独立执行不同的任务,共享同一份数据。由于多线程并发执行的特点,会引发数据同步的问题,即保证多个
    的头像 发表于 11-17 14:22 951次阅读

    多线程同步的几种方法

    多线程同步是指在多个线程并发执行的情况下,为了保证线程执行的正确性和一致性,需要采用特定的方法来协调线程之间的执行顺序和共享资源的访问。下面将介绍几种常见的
    的头像 发表于 11-17 14:16 1002次阅读

    Linux系统上多线程和多进程的运行效率

    关于多进程和多线程,教科书上最经典的一句话是“进程是资源分配的最小单位,线程是CPU调度的最小单位”,这句话应付考试基本上够了,但如果在工作中遇到类似的选择问题,那就没有这么简单了,选的不好,会让你
    的头像 发表于 11-10 10:54 1133次阅读
    Linux系统上<b class='flag-5'>多线程</b>和多进程的运行效率

    关于Python多进程和多线程详解

    进程(process)和线程(thread)是操作系统的基本概念,但是它们比较抽象,不容易掌握。关于多进程和多线程,教科书上最经典的一句话是“进程是资源分配的最小单位,线程是CPU调度的最小单位”。
    的头像 发表于 11-06 14:46 722次阅读
    关于Python多进程和<b class='flag-5'>多线程</b>详解

    多线程idm下载软件

    多线程idm下载软件
    发表于 10-23 09:23 0次下载

    华为回应“花瓣支付”更名 花瓣支付不对标微信支付支付宝

    2亿,在2014年取得了支付牌照,最初隶属于中兴软件,后续股权发生转让。2021年3月,华为通过收购迅联智支付100%股份,获得移动支付牌照。 那么花瓣支付会给微信
    的头像 发表于 10-17 16:08 892次阅读

    MDK可以支持多线程编译吗?

    怎么才能打开多线程编译
    发表于 10-11 07:23