MyBatisPlus你会扩展吗?不,你不会

346 阅读2分钟

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

扩展BaseMapper

  • 自定义自己的通用方法可以实现接口 ISqlInjector 也可以继承抽象类 AbstractSqlInjector 注入通用方法 SQL 语句 然后继承 BaseMapper 添加自定义方法,全局配置 sqlInjector 注入 MP 会自动将类所有方法注入到 mybatis 容器中。

  • 创建通用方法的实现类(InsertIgnore、InsertIgnoreBatch、Replace、updateAllById、insertBatchSomeColumn)

  • InsertIgnore 插入数据实现类,如果中已经存在相同的记录,则忽略当前新数据

  • 批量插入数据,如果中已经存在相同的记录,则忽略当前新数据

  • Replace替换数据,如果中已经存在相同的记录,则覆盖旧数据

通过扩展批量解决效率问题

背景

  • 由于项目中需要大批量将数据插入数据库,直接使用mybatis-plus中的批量插入方法,结果发现效率奇低无比,线上批量插入一千条数据居然花销八九秒的时间。而我们的目标是想要单次插入一万条数据,这样的效率完全无法接受。

  • 继续接着上插入的源码研究,发现底层在SqlHeper类中有个executeBatch的方法有点异常。该方法显示 sqlSession.flushStatements()的调用居然是循环的。也就是说sql层面实际上是一堆insert语句再sqlSession中循环flush,而不是一个大insert一次flush操作完。这就是效率低的本质原因。

##解决方案 因为上述原因,考虑自己写一个批量插入的sql语句,这是最简单的。

但我们此处不采取此方法,而是直接重写sql注入。以下DefaultSqlInjector为mybatis-plus默认的sql注入实现类,继承的是AbstractSqlInjector类。该默认实现类中添加的Insert、Delete、DeleteByMap等等,实际上就是对应Mapper中所调用的各种方法。

public class InsertBatchMethod extends AbstractMethod {
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        final String sql = "<script>insert into %s %s values %s</script>";
        final String fieldSql = prepareFieldSql(tableInfo);
        final String valueSql = prepareValuesSql(tableInfo);
        final 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();
    }
}