Mybati-Plus自定义批量插入接口

337 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第29天,点击查看活动详情


哈喽,大家好,我是一条。

相信大家都用过 Mybati-Plus ,啊哈哈,属实是好用,写起来代码那叫一个丝滑。

List<ZoneReportPO> zoneReportPOList = zoneReportMapper.selectList(
                Wrappers.lambdaQuery(ZoneReportPO.class)
                        .eq(StringUtils.isNotBlank(reportTime), ZoneReportPO::getReportTime, reportTime));

配上函数式编程,美得很。

但是!它在批量插入这里有个坑:

@Service
public class CommerceUserService extends ServiceImpl<CommerceUserMapper, CommerceUser> {
    
    public boolean batchInsert(List<CommerceUser> list) {
        return super.saveBatch(list);
    }
}

这里有一个帮我们实现好的批量插入方法,看起来挺方便哈,可仔细一看,这货还是一条一条插入的。

return !CollectionUtils.isEmpty(list) && executeBatch(entityClass, log, (sqlSession) -> {
    int size = list.size();
    int i = 1;
​
    for(Iterator var6 = list.iterator(); var6.hasNext(); ++i) {
        E element = var6.next();
        consumer.accept(sqlSession, element);
        if (i % batchSize == 0 || i == size) {
            sqlSession.flushStatements();
        }
    }
​
});

感兴趣的同学可以看一下源码,同时有个 java8 的新函数式接口 BiConsumer,可以学习。

我们不跑题说正事,批量插入怎么搞?我插入1000条数据,访问1000次数据库显然是不行,还要考虑大事务。

没办法,自己写吧!开搞!

sql注入器

这里我们可以通过继承DefaultSqlInjector来注入我们自定义的sql方法。

这里实现了批量插入和修改方法。

public class CustomizedSqlInjector extends DefaultSqlInjector {
​
    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        //保留自带方法
        List<AbstractMethod> methodList = super.getMethodList(mapperClass);
        methodList.add(new InsertBatchMethod());
        methodList.add(new UpdateBatchMethod());
        return methodList;
    }
​
}

InsertBatchMethod

接下来就要把这个方法实现,我觉得哈,下面代码直接复制用就行,都是sql的拼接,没什么好研究的。

注意下返回的"insertBatch"这个id,和接口方法中的方法名要对应上。

@Slf4j
public class InsertBatchMethod extends AbstractMethod {
​
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        String sql = "<script>insert into %s %s values %s</script>";
        String fieldSql = prepareFieldSql(tableInfo);
        String valueSql = prepareValuesSql(tableInfo);
        String sqlResult = String.format(sql, tableInfo.getTableName(), fieldSql, valueSql);
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sqlResult, modelClass);
        return this.addInsertMappedStatement(mapperClass, modelClass, "insertBatch", sqlSource, new NoKeyGenerator(), null, null);
    }
​
    private String prepareFieldSql(TableInfo tableInfo) {
        StringBuilder fieldSql = new StringBuilder();
        fieldSql.append(tableInfo.getKeyColumn()).append(",");
        tableInfo.getFieldList().forEach(x -> fieldSql.append(x.getColumn()).append(","));
        fieldSql.delete(fieldSql.length() - 1, fieldSql.length());
        fieldSql.insert(0, "(");
        fieldSql.append(")");
        return fieldSql.toString();
    }
​
    private String prepareValuesSql(TableInfo tableInfo) {
        final StringBuilder valueSql = new StringBuilder();
        valueSql.append("<foreach collection="list" item="item" index="index" open="(" separator="),(" close=")">");
        valueSql.append("#{item.").append(tableInfo.getKeyProperty()).append("},");
        tableInfo.getFieldList().forEach(x -> valueSql.append("#{item.").append(x.getProperty()).append("},"));
        valueSql.delete(valueSql.length() - 1, valueSql.length());
        valueSql.append("</foreach>");
        return valueSql.toString();
    }
}

自定义Mapper

在自定义一个mapper,这样使用的时候就不再继承BaseMapper而是我们的RootMapper

public interface RootMapper<T> extends BaseMapper<T> {
​
    /**
     * 自定义批量插入
     */
    int insertBatch(@Param("list") List<T> list);
​
    /**
     * 自定义批量更新,条件为主键
     */
    int updateBatch(@Param("list") List<T> list);
​
}

内存溢出怎么办?

你以为这就完事大吉了?并不是,因为我们要把所有数据拼接一个sql,那字符串就会非常的长,内存溢出必不可少了,怎么解决呢?

对于特别大量的数据,我们还是要分多次的批量插入。

引入一个工具类

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>28.1-jre</version>
</dependency>

分组批量插入

List<List<TestBean>> splistList = Lists.partition(testBeans,200);
splistList.forEach(itemList->testMapper.insertBatch(itemList));
​

最后

其实这个问题困扰了我很久,如果遇到数据量大,又要实时插入,效率、性能、内存溢出、大事务。都是需要考虑的。

大家还是要要看需求选择合适的方案。