[Technology Sharing] How to rollback a multi-threaded transaction?

[Technology Sharing] How to rollback a multi-threaded transaction?

A scene 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 process it in parallel 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.

Two code demo

  1. Thread pool configuration class
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(){
    
    }
}
  1. sqlSession tool class
@Component
public class SqlContext {
    
    
    @Resource
    private SqlSessionTemplate sqlSessionTemplate;

    public SqlSession getSqlSession(){
    
    
        SqlSessionFactory sqlSessionFactory = sqlSessionTemplate.getSqlSessionFactory();
        return sqlSessionFactory.openSession();
    }
}
  1. Split list tool class
/**
 * 平均拆分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;
}
  1. Multi-threaded use manual commit transaction to achieve transaction rollback
    operation failed
 @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>

insert image description here
insert image description here
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);
    }
}

insert image description here

Guess you like

Origin blog.csdn.net/hcyxsh/article/details/131706011