多线程如何实现事务回滚?一招帮你搞定!

七、回滚机制——事务回滚 在 Spring Boot 和 Spring Cloud 中实现事务回滚,可以根据系统的复杂性选择合适的事务管理策略。对于单库事务,使用 Spring 的注解即可实现事务管理和回滚。对于分布式事务,可以选择使用两阶段提交(2PC)、三阶段提交(3PC)、TCC 模式或 Sagas 模式等方法来保证事务的一致性和完整性。 阅读详情

点击上方“芋道源码”,选择“设为星标”

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 10:33 更新文章,每天掉亿点点头发...

源码精品专栏

 

来源:blog.youkuaiyun.com/u010978399/

article/details/117771620

a70a9ab70a7b4a08ebd3b8461f94cfbc.jpeg


特别说明CountDownLatch

CountDownLatch是一个类springboot自带的类,可以直接用 ,变量AtomicBoolean 也是可以直接使用

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

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro

  • 视频教程:https://doc.iocoder.cn/video/

CountDownLatch的用法

CountDownLatch典型用法:

1、某一线程在开始运行前等待n个线程执行完毕。 将CountDownLatch的计数器初始化为new CountDownLatch(n),每当一个任务线程执行完毕,就将计数器减1 countdownLatch.countDown(),当计数器的值变为0时,在CountDownLatch上await()的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。

2、实现多个线程开始执行任务的最大并行性。 注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的CountDownLatch(1),将其计算器初始化为1,多个线程在开始执行任务前首先countdownlatch.await(),当主线程调用countDown()时,计数器变为0,多个线程同时被唤醒。

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

  • 项目地址:https://github.com/YunaiV/yudao-cloud

  • 视频教程:https://doc.iocoder.cn/video/

CountDownLatch(num) 简单说明

new 一个 CountDownLatch(num) 对象

建立对象的时候 num 代表的是需要等待 num 个线程

// 建立对象的时候 num 代表的是需要等待 num 个线程
//主线程
CountDownLatch mainThreadLatch = new CountDownLatch(num);
//子线程
CountDownLatch rollBackLatch  = new CountDownLatch(1);

主线程:mainThreadLatch.await() 和mainThreadLatch.countDown()

新建对象

CountDownLatch mainThreadLatch = new CountDownLatch(num);

卡住主线程,让其等待子线程,代码mainThreadLatch.await(),放在主线程里

mainThreadLatch.await();
c10ad38b5753a9a0b52683e0dfe41c23.png

代码mainThreadLatch.countDown(),放在子线程里,每一个子线程运行一到这个代码,意味着CountDownLatch(num),里面的num-1(自动减一)

mainThreadLatch.countDown();

CountDownLatch(num)里面的num减到0,也就是CountDownLatch(0),被卡住的主线程mainThreadLatch.await(),就会往下执行

59efedaa860c81b526452c5da6154b26.png

子线程:rollBackLatch.await() 和rollBackLatch.countDown()

新建对象,特别注意:子线程这个num就是1(关于只能为1的解答在后面)

CountDownLatch rollBackLatch  = new CountDownLatch(1);

卡住子线程,阻止每一个子线程的事务提交和回滚

rollBackLatch.await();
81b20802c37e351993d67f085783b948.png

代码rollBackLatch.countDown();放在主线程里,而且是放在主线程的等待代码mainThreadLatch.await();后面。

rollBackLatch.countDown();
31ec83be68917ba375a340f02e381d23.png

为什么所有的子线程会在一瞬间就被所有都释放了?

536ad3905962fece41bb6b0c38a7dc60.png

事务的回滚是怎么结合进去的?

假设总共20个子线程,那么其中一个线程报错了怎么实现所有线程回滚。

引入变量

AtomicBoolean rollbackFlag = new AtomicBoolean(false)

和字面意思是一样的:根据 rollbackFlag 的true或者false 判断子线程里面,是否回滚。

首先我们确定的一点:rollbackFlag 是所有的子线程都用着这一个判断

a1a812388606babd62595a3387bad791.png 3b4ff73283ecf90dd87129022cf3dd93.png

主线程类Entry

package org.apache.dolphinscheduler.api.utils;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.dolphinscheduler.api.controller.WorkThread;
import org.apache.dolphinscheduler.common.enums.DbType;
import org.springframework.web.bind.annotation.*;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;


@RestController
@RequestMapping("importDatabase")
public class Entry {

    /**
     * @param dbid 数据库的id
     * @param tablename 表名
     * @param sftpFileName 文件名称
     * @param head 是否有头文件
     * @param splitSign 分隔符
     * @param type 数据库类型
     */
    private static String SFTP_HOST = "192.168.1.92";
    private static int SFTP_PORT = 22;
    private static String SFTP_USERNAME = "root";
    private static String SFTP_PASSWORD = "rootroot";
    private static String SFTP_BASEPATH = "/opt/testSFTP/";
    @PostMapping("/thread")
    @ResponseBody
    public static JSONObject importDatabase(@RequestParam("dbid") int dbid
            ,@RequestParam("tablename") String tablename
            ,@RequestParam("sftpFileName") String sftpFileName
            ,@RequestParam("head") String head
            ,@RequestParam("splitSign") String splitSign
            ,@RequestParam("type") DbType type
            ,@RequestParam("heads") String heads
            ,@RequestParam("scolumns") String scolumns
            ,@RequestParam("tcolumns") String tcolumns ) throws Exception {
        JSONObject obForRetrun = new JSONObject();

        try {

            JSONArray jsonArray = JSONArray.parseArray(tcolumns);
            JSONArray scolumnArray = JSONArray.parseArray(scolumns);
            JSONArray headsArray = JSONArray.parseArray(heads);
            List<Integer> listInteger = getRrightDataNum(headsArray,scolumnArray);
            JSONArray bodys = SFTPUtils.getSftpContent(SFTP_HOST,SFTP_PORT,SFTP_USERNAME,SFTP_PASSWORD,SFTP_BASEPATH,sftpFileName,head,splitSign);
            int total  = bodys.size();
            int num = 20; //一个批次的数据有多少
            int count = total/num;//周期
            int lastNum =total- count*num;//余数

            List<Thread> list = new ArrayList<Thread>();
            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss:SS");
            TimeZone t = sdf.getTimeZone();
            t.setRawOffset(0);
            sdf.setTimeZone(t);
            Long startTime=System.currentTimeMillis();


            int countForCountDownLatch = 0;
            if(lastNum==0){//整除
                countForCountDownLatch= count;
            }else{
                countForCountDownLatch= count + 1;
            }
            //子线程
            CountDownLatch rollBackLatch  = new CountDownLatch(1);
            //主线程
            CountDownLatch mainThreadLatch = new CountDownLatch(countForCountDownLatch);

            AtomicBoolean rollbackFlag = new AtomicBoolean(false);
            StringBuffer message = new StringBuffer();
            message.append("报错信息:");

            //子线程
            for(int i=0;i<count;i++) {//这里的count代表有几个线程
                Thread g = new Thread(new WorkThread(i,num,tablename,jsonArray,dbid,type,bodys,listInteger,mainThreadLatch,rollBackLatch,rollbackFlag,message ));
                g.start();
                list.add(g);
            }

            if(lastNum!=0){//有小数的情况下
                Thread g = new Thread(new WorkThread(0,lastNum,tablename,jsonArray,dbid,type,bodys,listInteger,mainThreadLatch,rollBackLatch,rollbackFlag,message ));
                g.start();
                list.add(g);
            }

//            for(Thread thread:list){
//                System.out.println(thread.getState());
//                thread.join();//是等待这个线程结束;
//            }

            mainThreadLatch.await();
            //所有等待的子线程全部放开
            rollBackLatch.countDown();

            //是主线程等待子线程的终止。也就是说主线程的代码块中,如果碰到了t.join()方法,此时主线程需要等待(阻塞),等待子线程结束了(Waits for this thread to die.),才能继续执行t.join()之后的代码块。


            Long endTime=System.currentTimeMillis();
            System.out.println("总共用时: "+sdf.format(new Date(endTime-startTime)));

            if(rollbackFlag.get()){
                obForRetrun.put("code",500);
                obForRetrun.put("msg",message);
            }else{
                obForRetrun.put("code",200);
                obForRetrun.put("msg","提交成功!");
            }
            obForRetrun.put("data",null);
        }catch (InterruptedException e){
            e.printStackTrace();
            obForRetrun.put("code",500);
            obForRetrun.put("msg",e.getMessage());
            obForRetrun.put("data",null);
        }
        return obForRetrun;

    }

    /**
     * 文件里第几列被作为导出列
     * @param headsArray
     * @param scolumnArray
     * @return
     */
    public static List<Integer> getRrightDataNum(JSONArray headsArray, JSONArray scolumnArray){
        List<Integer> list = new ArrayList<Integer>();
        String arrayA [] = new String[headsArray.size()];
        for(int i=0;i<headsArray.size();i++){
            JSONObject ob  = (JSONObject)headsArray.get(i);
            arrayA[i] =String.valueOf(ob.get("title"));
        }

        String arrayB [] = new String[scolumnArray.size()];
        for(int i=0;i<scolumnArray.size();i++){
            JSONObject ob  = (JSONObject)scolumnArray.get(i);
            arrayB[i] =String.valueOf(ob.get("columnName"));
        }

        for(int i =0;i<arrayA.length;i++){
            for(int j=0;j<arrayB.length;j++){
                if(arrayA[i].equals(arrayB[j])){
                    list.add(i);
                    break;
                }
            }
        }

        return list;
    }
}

子线程类WorkThread

package org.apache.dolphinscheduler.api.controller;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.dolphinscheduler.api.service.DataSourceService;
import org.apache.dolphinscheduler.common.enums.DbType;
import org.apache.dolphinscheduler.dao.entity.DataSource;
import org.apache.dolphinscheduler.dao.mapper.DataSourceMapper;
import org.apache.dolphinscheduler.service.bean.SpringApplicationContext;
import org.springframework.transaction.PlatformTransactionManager;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;


/**
 * 多线程
 */
public class WorkThread implements Runnable{ //建立线程的两种方法 1 实现Runnable 接口 2 继承 Thread 类

    private DataSourceService dataSourceService;

    private DataSourceMapper dataSourceMapper;

    private Integer begin;
    private Integer end;
    private String tableName;
    private JSONArray columnArray;
    private Integer dbid;
    private DbType type;
    private JSONArray bodys;
    private  List<Integer> listInteger;
    private PlatformTransactionManager transactionManager;
    private CountDownLatch mainThreadLatch;
    private CountDownLatch rollBackLatch;
    private AtomicBoolean rollbackFlag;
    private StringBuffer message;



    /**
     * @param i
     * @param num
     * @param tableFrom
     * @param columnArrayFrom
     * @param dbidFrom
     * @param typeFrom
     */
    public WorkThread(int i, int num, String tableFrom, JSONArray columnArrayFrom, int dbidFrom
            , DbType typeFrom, JSONArray bodysFrom, List<Integer> listIntegerFrom
            ,CountDownLatch mainThreadLatch,CountDownLatch rollBackLatch,AtomicBoolean rollbackFlag
            ,StringBuffer messageFrom) {
        begin=i*num;
        end=begin+num;
        tableName = tableFrom;
        columnArray = columnArrayFrom;
        dbid = dbidFrom;
        type = typeFrom;
        bodys = bodysFrom;
        listInteger = listIntegerFrom;
        this.dataSourceMapper = SpringApplicationContext.getBean(DataSourceMapper.class);
        this.dataSourceService = SpringApplicationContext.getBean(DataSourceService.class);
        this.transactionManager = SpringApplicationContext.getBean(PlatformTransactionManager.class);
        this.mainThreadLatch = mainThreadLatch;
        this.rollBackLatch = rollBackLatch;
        this.rollbackFlag = rollbackFlag;
        this.message = messageFrom;
    }

    public void run() {

        DataSource dataSource = dataSourceMapper.queryDataSourceByID(dbid);
        String cp = dataSource.getConnectionParams();
        Connection con=null;
            con =  dataSourceService.getConnection(type,cp);
        if(con!=null)
        {
            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss:SS");
            TimeZone t = sdf.getTimeZone();
            t.setRawOffset(0);
            sdf.setTimeZone(t);
            Long startTime = System.currentTimeMillis();
            try {
                con.setAutoCommit(false);

//---------------------------- 获取字段和类型
                String columnString = null;//活动的字段
                int intForType = 0;
                String type[] = new String[columnArray.size()];//类型集合
                for(int i=0;i<columnArray.size();i++){
                    JSONObject ob = (JSONObject)columnArray.get(i);
                    if(columnString==null){
                        columnString = String.valueOf(ob.get("name"));
                    }else{
                        columnString = columnString + ","+ String.valueOf(ob.get("name"));
                    }
                    type[intForType] = String.valueOf(ob.get("type"));
                    intForType = intForType + 1;
                }
                intForType = 0;

                //这一步是为了形成 insert into "+tableName+"(id,name,age) values (?,?,?);
                String dataString  = null;
                for(int i=0;i<columnArray.size();i++){
                    if(dataString==null){
                        dataString = "?";
                    }else{
                        dataString = dataString +","+"?";
                    }
                }

//--------------------------------

                StringBuffer sql = new StringBuffer();
                sql = sql.append("insert into "+tableName+"("+columnString+") values ("+dataString+")") ;
                PreparedStatement pst= (PreparedStatement)con.prepareStatement(sql.toString());
                for(int i=begin;i<end;i++) {
                    JSONObject ob = (JSONObject)bodys.get(i);
                    if(ob!=null){
                        String [] array = ob.get(i).toString().split("\\,");
                        String [] arrayFinal = getFinalData(listInteger,array);
                        for(int j=0;j<type.length;j++){
                            String typeString  = type[j].toLowerCase();
                            int z = j+1;
                            if("string".equals(typeString)||"varchar".equals(typeString)){
                                pst.setString(z,arrayFinal[j]);//这里的第一个参数 是指 替换第几个?
                            }else if("int".equals(typeString)||"bigint".equals(typeString)){
                                pst.setInt(z,Integer.valueOf(arrayFinal[j]));//这里的第一个参数 是指 替换第几个?
                            }else if("long".equals(typeString)){
                                pst.setLong(z,Long.valueOf(arrayFinal[j]));//这里的第一个参数 是指 替换第几个?
                            }else if("double".equals(typeString)){
                                pst.setDouble(z,Double.parseDouble(arrayFinal[j]));
                            }else if("date".equals(typeString)||"datetime".equals(typeString)){
                                pst.setDate(z, setDateback(arrayFinal[j]));
                            }else if("Timestamp".equals(typeString)){
                                pst.setTimestamp(z, setTimestampback(arrayFinal[j]));
                            }
                        }
                    }
                    pst.addBatch();
                }
                pst.executeBatch();

                mainThreadLatch.countDown();
                rollBackLatch.await();

                if(rollbackFlag.get()){
                    con.rollback();//表示回滚事务;
                }else{
                    con.commit();//事务提交
                }
                con.close();
            } catch (Exception e) {
                System.out.println(e.getMessage());
                message = message.append(e.getMessage());
                rollbackFlag.set(true);
                mainThreadLatch.countDown();
                try {
                    con.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            Long endTime = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName()+":startTime= "+sdf.format(new Date(startTime))+",endTime= "+sdf.format(new Date(endTime))
                    +" 用时:"+sdf.format(new Date(endTime - startTime)));

        }
    }


    public java.sql.Date setDateback(String dateString) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
        java.util.Date date = sdf.parse( "2015-5-6 10:30:00" );
        long lg = date.getTime();// 日期 转 时间戳
        return new java.sql.Date( lg );
    }

    public java.sql.Timestamp setTimestampback(String dateString) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
        java.util.Date date = sdf.parse( "2015-5-6 10:30:00" );
        long lg = date.getTime();// 日期 转 时间戳
        return new java.sql.Timestamp( lg );
    }

    public String [] getFinalData(List<Integer> listInteger,String[] array){
        String [] arrayFinal = new String [listInteger.size()];
        for(int i=0;i<listInteger.size();i++){
            int a = listInteger.get(i);
            arrayFinal[i] = array[a];
        }
        return arrayFinal;
    }
}

代码实际运用踩坑!!!!

617230a0311894d0aeb9a12d476a9421.png

还记得这里有个一批次处理多少数据么,我这边设置了20,实际到运用中的时候客户给了个20W的数据,我批次设置为20,那就有1W个子线程!!!!

这还不是最糟糕的,最糟糕的是每个子线程都会创建一个数据库连接,数据库直接被我搞炸了

b48185fddba0701f1e46397c2d62da20.png

所以这里需要把:

int num = 20; //一个批次的数据有多少

改成:

int num = 20000; //一个批次的数据有多少


欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢:

93b051022802a5b2e2f4c97a6e4b72ae.png

已在知识星球更新源码解析如下:

3075a908e83a0909aab8db95a0d35739.jpeg

abb917822088d883fd88dd6f565f7d7f.jpeg

1a9446bfa04e7a7ae097391ac8366628.jpeg

4774da8b031e74e0d6bf51cb3e30bd3a.jpeg

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)
多线程优化大量数据如何事务回滚 于是使用多线程去处理,可能出现的问题是,如果一个线程出现失败,那么应该返回响应处理失败,已插入就绪的线程任务需要回滚,但是每个线程执行的是自己的事务,那么该如何处理呢。4w单的EXECL表格,导入到订单表,校验数据后落库,响应速度超过2min,大约在8min左右,这是单线程的情况下,在使用批量插入后,仍超时2min。:spring中对数据库连接是放在threadLocal里面,多线程场景下,拿到的数据库连接是不一样的,即是属于不同事务。将大批量的数据,分为多个集合,每个线程处理一个。 阅读详情

相关推荐

SQLAlchemy Core实战指南:从连接池到元数据反射的工程化实践

SQLAlchemy 是 Python 生态中最成熟的数据库工具包,其核心价值在于将关系型数据库操作抽象为可编程、可测试、跨方言的 Python 表达式。它基于 SQL 表达式语言(Core)构建确定性查询,通过 Engine-Connection-Statement 三层模型实现连接管理与语句编译,显著提升数据访问的安全性与可观测性。在金融风控、电商中台等高可靠场景中,SQLAlchemy Core 比 ORM 更适合复杂查询、分页优化与多数据库适配。本文聚焦生产级落地细节,涵盖连接池调优、元数据反射校验

weixin_30566111的博客 436

多线程事务回滚方法

1.最近有一个大数据量插入的操作入库的业务场景,需要先做一些其他修改操作,然后在执行插入操作,由于插入数据可能会很多,用到多线程去拆分数据并行处理来提高响应时间,如果有一个线程执行失败,则全部回滚。2.在spring中可以使用@Transactional注解去控制事务,使出现异常时会进行回滚,在多线程中,这个注解则不会生效,如果主线程需要先执行一些修改数据库的操作,当子线程在进行处理出现异常时,主线程修改的数据则不会回滚,导致数据错误。

mry6的博客 2864

Delphi 12.3下UniDAC 10.3.0源码编译与集成实战

数据库访问组件是Delphi开发者构建企业应用的核心工具。UniDAC作为一款通用数据访问组件,通过统一API屏蔽Oracle、SQL Server、MySQL等数据库差异,显著降低多数据库项目维护成本。其源码版提供完整Pascal代码,允许开发者自定义连接逻辑、跟踪SQL执行细节,甚至优化底层数据缓存策略,满足深度集成与性能调优需求。在Delphi 12.3环境下,源码级编译可充分利用现代编译器优势,结合64位支持和高DPI适配,打造更高效的数据库开发环境。从源码包解压、依赖编译到IDE集成,系统阐述Un

weixin_33884611的博客 686

多线程实现事务回滚

多线程实现事务回滚特别说明CountDownLatchCountDownLatch的用法CountDownLatch(num) 简单说明主线程:mainThreadLatch.await() 和mainThreadLatch.countDown()子线程:rollBackLatch.await() 和rollBackLatch.countDown()**为什么所有的子线程会在一瞬间就被所有都释放了?**事务的回滚是怎么结合进去的?假设总共20个子线程,那么其中一个线程报错了怎么实现所有线程回滚。引入变量At

吃素的哈士奇de博客 1万+

基于图神经网络的社交网络虚假账号检测系统 (Graph-based Fake Account Detection System, GFADS)

FOLLOWSRETWEETSSHAREDCOMMENTSLIKESMENTIONSUseruser_idusernamejoin_datebioavatar_urllocationverifiedis_fakeLOAD CSV这是决定模型性能的关键环节,需结合和。XXedge_indexy新账号缺乏历史数据和丰富关系。全网图可能包含数亿节点和数十亿边,难以直接训练。社交网络是动态变化的,虚假账号策略也在不断进化。获取大规模、高质量的真实/虚假账号标注数据非常困难。

Deng945201314的博客 1481

SocraticLM: Exploring Socratic Personalized Teaching with Large Language Models

大型语言模型(LLM)被认为是推进智能教育的一项关键技术,因为它们展示了深入理解教学场景和为学生提供个性化指导的潜力。然而,当前个性化教学中基于LLM的应用主要遵循“问答”范式,学生被动地获得答案和解释。在本文中,我们提出了苏格拉底式教学模式(SocraticLM ),它实现了一种苏格拉底式的“发人深省”的教学范式,实现了真正的课堂教师的角色,让学生积极参与到真正掌握解决问题所需的思考过程中。

weixin_43961909的博客 2882

3.Linux基础开发⼯具【由浅入深-Linux】

在 Windows 环境中,我们习惯了双击 或 文件并一路点击“下一步”来安装软件。但在 Linux 的世界里,软件的分发和安装理念有着本质的不同。Linux 强调权限控制、模块化和依赖管理。为了帮您构建完整的 Linux 系统管理知识体系,我们可以将 Linux 下安装软件的方式归纳为五大主流阵营。这是 Linux 下最正统、也是日常使用频率最高的方式。它又细分为“在线仓库安装”和“本地包安装”两种场景。1. 在线仓库安装(如同手机自带的 App Store) 操作系统维护着一个庞大的软件云端仓库。通

插花弄玉的博客 725

告别迷茫!手把手教你用Altova XMLSpy编写EtherCAT从站ESI文件(附避坑指南)

本文详细介绍了如何使用Altova XMLSpy高效编写EtherCAT从站ESI文件,涵盖核心架构、通信通道配置、过程数据映射等关键步骤,并提供了常见问题排查与性能优化建议。通过实战案例和避坑指南,帮助工程师快速掌握EtherCAT从站开发技巧,提升工作效率。

weixin_42570484的博客 395

多线程事务怎么回滚?

多线程事务处理

wcj_java的专栏 1894

《LabVIEW ZYNQ实战指南》第6章>>案例解析:基于USB WiFi模块的ZYNQ无线通信部署与Linux RT开发

本文详细解析了如何为ZYNQ开发板部署USB WiFi模块,实现无线通信。通过LabVIEW Linux RT环境,利用封装好的驱动VI,开发者可轻松配置ZYNQ连接无线网络,摆脱网线束缚,实现程序无线部署与远程调试,为工业物联网应用奠定基础。

app77的博客 754

基于ESP32-WROOM的智能蓝牙电压电流监测与远程控制系统设计

本文介绍了基于ESP32-WROOM的智能蓝牙电压电流监测与远程控制系统设计。该系统集成了INA219传感器和OLED显示屏,通过蓝牙实现无线数据传输和远程控制,适用于智能家居能耗监控和工业设备管理,具有成本低、精度高、功耗优化等优点。

qsc901234的博客 203

事务的理解

事务是恢复和并发控制的基本单位。 事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。 原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。 一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。 隔离性(isolation)

努力和遗憾,哪个更让你痛苦? 606

【记录】游戏体验报告怎么写(自我成长篇)

2019年3月更新: 这个倒是不用推翻,我个人虽然写的东西还是有一些偏差的,但是“表哥”说的倒是没毛病的,这篇主要是为了记录用,具体的干货也有,都在图片里,请自行提取 =====18年初原答案 作为一个游戏策划新人,游戏分析可能抓不到点子上,试着总结记录一下游戏体验报告分析的成长(因为水平不同,视野可能也不一样,也许之后几年回头看这些东西都是小儿科或者是错误的,所以只写出当下的想...

eat_human的博客 6万+

多线程事务怎么回滚

1,最近有一个大数据量插入的操作入库的业务场景,需要先做一些其他修改操作,然后在执行插入操作,由于插入数据可能会很多,用到多线程去拆分数据并行处理来提高响应时间,如果有一个线程执行失败,则全部回滚。2,在spring中可以使用@Transactional注解去控制事务,使出现异常时会进行回滚,在多线程中,这个注解则不会生效,如果主线程需要先执行一些修改数据库的操作,当子线程在进行处理出现异常时,主线程修改的数据则不会回滚,导致数据错误。3,下面用一个简单示例演示多线程事务。

qq_22343483的博客 1399

MAC-电脑中的user文件夹在哪?

MAC-电脑中的user文件夹在哪?

兴趣是最好的老师 1万+

ximalaya 新xm-sign逆向思路

本文主要介绍目标网站新xm-sign值的逆向思路。仅代表个人理解,如有其他建议可在评论区沟通。声明仅仅记录一下自己的学习方法,不作为其他参考、更不作为商业用途。如有侵犯请联系本人删除。

weixin_66580433的博客 2017

SpringBoot事务注解@Transactional

1. 事务说明 在Spring中,事务有两种实现方式,分别是编程式事务管理和声明式事务管理两种方式。编程式事务管理: 编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。声明式事务管理: 建立在AOP之上的。其本质是对方法前后进行拦截,然后在...

rogerxue12345的博客 1334

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

事务的回滚五种方法

前段时间对Spring的事务配置做了比较深入的研究,在此之间对Spring的事务配置虽说也配置过,但是一直没有一个清楚的认识。通过这次的学习发觉Spring的事务配置只要把思路理清,还是比较好掌握的。     总结如下:     Spring配置文件中关于事务配置总是由三个组成部分,分别是DataSource、TransactionManager和代理机制这三部分,无论哪种配置方式,一般变

pengxiaodong19862的专栏 1万+

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

3种常见的事务回滚

3种常见的事务回滚

m0_66918076的博客 3437

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

Spring Boot 中的事务回滚规则是什么, 如何使用

事务是一组数据库操作,这些操作被视为一个单一的工作单元,要么全部都成功完成,要么全部都失败回滚。在应用程序中,事务通常用于确保数据的完整性和一致性。例如,在一个银行应用程序中,当一个用户从一个账户转移资金到另一个账户时,这个操作必须是一个事务,以确保不会发生资金丢失或重复转移的情况。事务回滚是指当事务中的一部分操作失败时,整个事务被撤销,所有操作都被回滚到事务开始之前的状态。这样可以避免数据的不一致性和损失。在 Spring Boot 中,事务回滚可以通过一些规则来控制。

stormjun的博客 2265

SpringBoot 多线程事务回滚,一个注解轻松搞定~

本文是使用了两个注解共同作用实现的,主线程当做协调者,各子线程作为参与者/*** 多线程事务注解: 主事务*///子线程数量/*** 多线程事务注解: 子事务*/两个注解都是用在方法上的,须配合一起使用@MainTransaction注解用在调用方,其参数为必填,参数值为本方法中调用的方法开启的线程数,如:在这个方法中调用的方法中有2个方法用@Async注解开启了子线程,则参数为,另外如果未使用注解,则直接已无多线程事务执行(不影响方法的单线程事务)

weixin_45738731的博客 3164
上一篇: 谈一谈大厂都怎么防止重复下单?
下一篇: 新来个技术总监,给公司项目引入了全新的业务架构,堪称最佳实践!
公众号-芋道源码
博客等级 码龄10年 1627粉丝 377原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值