Handling of multi-threaded transaction rollback

background introduction

1. Recently, there is a business scenario where a large amount of data is inserted into the warehouse. It is necessary to do some other modification operations first, and then perform the insertion operation. Since there may be a lot of inserted data, multi-threading is used to split the data and parallel processing to improve Response time, if one thread fails to execute, all will be rolled back.

2. In spring, you can use the @Transactional annotation to control the transaction, so that it will be rolled back when an exception occurs. In multi-threading, this annotation will not take effect. If the main thread needs to perform some operations to modify the database first, when the child thread When an exception occurs during processing, the data modified by the main thread will not be rolled back, resulting in data errors.
3. The following uses a simple example to demonstrate multi-threaded transactions.

public classes and methods

/**
     * 平均拆分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
 * @version V1.0
 */
@Component
public class SqlContext {
    
    
    @Resource
    private SqlSessionTemplate sqlSessionTemplate;

    public SqlSession getSqlSession(){
    
    
        SqlSessionFactory sqlSessionFactory = sqlSessionTemplate.getSqlSessionFactory();
        return sqlSessionFactory.openSession();
    }
}

Example transaction unsuccessful operation

  /**
     * 测试多线程事务.
     * @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();
         }
    }

There is a piece of data in the database:
insert image description here

//测试用例
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {
    
     ThreadTest01.class, MainApplication.class})
public class ThreadTest01 {
    
    

    @Resource
    private EmployeeBO employeeBO;

    /**
     *   测试多线程事务.
     * @throws InterruptedException
     */
    @Test
    public  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();
        }
    }
}

Test results:
insert image description here
insert image description here
It can be found that when the sub-thread group is executed, one thread fails to execute, and other threads also throw exceptions, but the delete operation executed in the main thread is not rolled back, and the Transactional annotation does not take effect.

Use sqlSession control to manually commit transactions

 @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);
            //获取mapper
            EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
            //先做删除操作
            employeeMapper.delete(null);
            //获取执行器
            ExecutorService service = ExecutorConfig.getThreadPool();
            List<Callable<Integer>> callableList  = new ArrayList<>();
            //拆分list
            List<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 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>

A piece of data in the database:
insert image description here
test result: an exception is thrown,
insert image description here
the data of the delete operation is rolled back, and the data in the database still exists, indicating that the transaction is successful.
insert image description here

Example of successful operation:

 @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);
        }
    }

Test Results:
blog.csdnimg.cn/20210608165840483.png)

Data in the database:
the deleted ones are deleted, the added ones are successfully added, and the test is successful.
insert image description here

Reposted from: https://blog.csdn.net/weixin_43225491/article/details/117705686

Guess you like

Origin blog.csdn.net/qq_43842093/article/details/132644350