MyBatis批量插入与事务隔离

PunkLu 2020年03月13日 170次浏览
MyBatis批量插入与事务隔离

需求

最近在工作中有个需求是解析XML文件中的数据并把解析出来的数据批量全部插入数据库中,但是使用MyBatis的foreach标签进行遍历插入消耗时间太多。查阅资料之后发现可以使用MyBatis提供的MyBatisBatchItemWriter类实现快速批量插入。

实现

首先封装工具类

public class MyBatisBatchFactory implements ApplicationContextAware {

    public static final MyBatisBatchFactory getInstance() {
        return MyBatisBatchFactory.SingletonHolder.INSTANCE;
    }

    private MyBatisBatchFactory() {
    }

    private static class SingletonHolder {
        private static final MyBatisBatchFactory INSTANCE = new MyBatisBatchFactory();

        private SingletonHolder() {
        }
    }

    private static ApplicationContext applicationContext;

    private SqlSessionFactory sessionFactory;

    public <T> MyBatisBatchItemWriter<T> writer(String statementId) {
        MyBatisBatchItemWriter<T> myBatisBatchItemWriter = new MyBatisBatchItemWriter();
        myBatisBatchItemWriter.setSqlSessionFactory(this.sessionFactory());
        myBatisBatchItemWriter.setStatementId(statementId);
        return myBatisBatchItemWriter;
    }

    private SqlSessionFactory sessionFactory() {
        if (this.sessionFactory != null) {
            return this.sessionFactory;
        } else {
            this.sessionFactory = (SqlSessionFactory) applicationContext.getBean("sqlSessionFactory");
            return this.sessionFactory;
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

然后再封装一个公用的批量插入调用类:

@Service
public class BatchService<T> {

    @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
    public void batchInsert(List<T> list,String mapperMethod){
        // 获取批量更新的操作流
        MyBatisBatchItemWriter<T> writer = MyBatisBatchFactory.getInstance().writer(mapperMethod);
        // 关闭流对数据库是否更新的校验
        writer.write(list);
    }
}

其中list即为要批量插入的数据列表,mapperMethod参数为插入语句的MyBatis的DAO接口的方法名,也就是说还是需要写一个INSERT语句。只是在调用的时候不再直接调用,而是通过MyBatis提供的MyBatisBatchItemWriter批量插入类操作。

需要注意的是:

这里在批量插入方法上加上了以下注解:

@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)

这是为了避免在调用这个批量插入方法的代码之前还有其他对数据库的操作。以免引起因为普通的MyBatis数据库操作的ExecutorType和这个批量插入的ExecutorType不同而导致出现以下错误:

MyBatisBatchItemWriter Cannot change the ExecutorType when there is an existing transaction

MyBatis执行器类型

MyBatis的执行器有三种类型:

  1. ExecutorType.SIMPLE

    这个类型不做特殊的事情,它只为每个语句创建一个PreparedStatement。

  2. ExecutorType.REUSE

    这种类型将重复使用PreparedStatements。

  3. ExecutorType.BATCH

    这个类型批量更新,性能更优,但batch模式也有自己的问题,比如在Insert操作时,在事务没有提交之前,是没有办法获取到自增的id,这在某型情形下是不符合业务要求的,而且假如有一条sql语句报错,则整个事务回滚,虽然这条sql语句不是太重要。注意:在同一事务中batch模式和simple模式之间无法转换。

为了保证不会因为同一个事务中同时出现不同的ExecutorType而报错,所以需要在调用批量插入方法的时候新开一个事务,同时为了保证批量插入出现错误时可以回滚数据,需要指定rollBackFor = Exception.class保证碰到异常时将批量插入的数据回滚。