java 线程池如何开启mysql事务

要求:通过excel导入一批数据,通过线程池处理,统一成功或失败,失败的时候立即退出,未消费的数据全部作废
比如有一个学生管理系统,教导专员可以通过excel批量导入学生,学生表如下

字段类型
idint
namevarchar(2)

注意了,这里的name长度为2 ,此时班里来了一个王二狗,批量导入会失败,这时候需求是批量导入的学生只能一起成功或者失败,网上说通过手工开启线程池能实现事务统一提交和回滚,于是我这么写

  public void addStudentsV2(List<Student> students){
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
        for (Student student : students) {
            try {
                studentMapper.addStudent(student);
            } catch (Exception e) {
                dataSourceTransactionManager.rollback(transactionStatus);
                return;
            }
        }
        dataSourceTransactionManager.commit(transactionStatus);
    }

事实证明是可以的,因为springboot底层用的是threadlocal 控制事务,但是我要的是通过线程池批量处理,因为导入的数据量很大,于是我改成如下实现,发现压根没生效,求正确写法

 @Override
    public void addStudents(List<Student> students){
TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
        List<Future<Student>> futures=new ArrayList<>();
        for (Student student : students) {
            Future<Student> future = executorService.submit(() -> {
                studentMapper.addStudent(student);
                log.info("任务结束,{}",student);
                return student;
            });
            futures.add(future);
        }
        
        for (Future<Student> future : futures) {
            Student student = null;
            try {
                student = future.get();
            } catch (Exception e) {
                e.printStackTrace();
                dataSourceTransactionManager.rollback(transactionStatus);
                return;
            }
        }
        dataSourceTransactionManager.commit(transactionStatus);
    }

这个不能直接用@Transactional(rollbackFor=Exception.class) 解决吗?

JDBC连接以启用自动提交模式开始,其中每个 SQL 语句都隐式地与一个事务划界。
希望每个事务执行多个语句的用户必须关闭自动提交。
con.setAutoCommit(false);//其中con是活动连接
con.commit();//数据提交,如果没有执行,则没有任何数据提交
更改自动提交模式会触发当前事务的提交(如果一个处于活动状态)。

  • 线程池可以等所有线程执行成功的机制
  • 所有线程执行成功后再提交事务即可解决你的需求

代码改成如下试试:

    @Transactional(rollbackFor = Exception.class)
    public void addStudents(List<Student> students){
        List<Future<Student>> futures=new ArrayList<>();
        for (Student student : students) {
            Future<Student> future = executorService.submit(() -> {
                studentMapper.addStudent(student);
                log.info("任务结束,{}",student);
                return student;
            });
            futures.add(future);
        }
        
        for (Future<Student> future : futures) {
            Student student = null;
            try {
                student = future.get();
            } catch (InterruptedException  | ExecutionException e) {
                e.printStackTrace();
                return;
            }
        }
    }

我的理解是这样的,一个事务对应一个数据库连接,如果你想多线程使用同一事务,实际上就是多线程使用同一个数据库连接。
这样的话,数据的提交还是分先后的,多线程执行就没意义了。
不如换成单线程批量提交。

https://blog.csdn.net/weixin_43753747/article/details/90136854?spm=1005.2026.3001.5635&utm_medium=distribute.pc_relevant_ask_down.none-task-blog-2~default~OPENSEARCH~Rate-2.pc_feed_download_top3ask&depth_1-utm_source=distribute.pc_relevant_ask_down.none-task-blog-2~default~OPENSEARCH~Rate-2.pc_feed_download_top3ask

可以尝试通过mapper拿到connection,然后自己控制事务

如果想控制事务,你就要为每个任务生成一个TransactionStatus 而你的Future 接受的不应该是Student 而是TransactionStatus

可以参考下https://blog.csdn.net/babing18258840900/article/details/105120935?spm=1001.2014.3001.5501

线程池可以等所有线程执行成功的机制
所有线程执行成功后再提交事务
JDBC连接以启用自动提交模式开始,其中每个 SQL 语句都隐式地与一个事务划界。
希望每个事务执行多个语句的用户必须关闭自动提交。
con.setAutoCommit(false);//其中con是活动连接
con.commit();//数据提交,如果没有执行,则没有任何数据提交
更改自动提交模式会触发当前事务的提交(如果一个处于活动状态)。
为每个任务生成一个TransactionStatus

在方法前面添加@Transactional,注意只能在service类的方法添加,不要加的controller类