✅ 一、核心概念

概念 说明
ISqlInjector MyBatis-Plus 扩展点,用于在 启动阶段 把自定义 SQL 方法注入到 每个 Mapper 接口MappedStatement 中,从而像 selectById 一样直接调用。
AbstractMethod 自定义 SQL 的模板类——重写 injectMappedStatement 来生成 SQL。
DefaultSqlInjector MP 默认实现;自定义时只需继承它并追加新方法。
使用场景 真批量插入、逻辑删除增强、多表 join、数据权限、租户过滤等。

✅ 二、操作步骤(5 步即可跑通)

✅ 步骤1:新建自定义方法类

public class InsertBatchSomeColumn extends AbstractMethod {

    @Override
    public MappedStatement injectMappedStatement(
            Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {

        // 1. SQL 模板
        String sql = "<script>INSERT INTO %s (%s) VALUES " +
                     "<foreach collection=\"list\" item=\"item\" separator=\",\">" +
                     "(<foreach collection=\"columns\" item=\"col\" separator=\",\">" +
                     "#{item.${col.property}}" +
                     "</foreach>)" +
                     "</foreach>" +
                     "</script>";

        // 2. 动态列
        List<TableFieldInfo> columns = tableInfo.getFieldList()
                                               .stream()
                                               .filter(f -> !f.isLogicDelete())
                                               .collect(Collectors.toList());
        String columnNames = columns.stream()
                                    .map(TableFieldInfo::getColumn)
                                    .collect(Collectors.joining(","));
        sql = String.format(sql, tableInfo.getTableName(), columnNames);

        // 3. 构建 SqlSource
        SqlSource sqlSource = languageDriver.createSqlSource(
                configuration, sql, modelClass);

        // 4. 返回 MappedStatement(注意 id 与方法名一致)
        return addInsertMappedStatement(mapperClass, modelClass,
                "insertBatchSomeColumn", sqlSource, new NoKeyGenerator(), null, null);
    }
}

✅ 步骤2:自定义 SQL 注入器

@Component
public class CustomSqlInjector extends DefaultSqlInjector {

    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
        // 保留父类所有方法
        List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);
        // 追加自定义方法
        methodList.add(new InsertBatchSomeColumn());
        return methodList;
    }
}

✅ 步骤3:注册注入器(Spring Boot 场景)

@Configuration
public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        return new MybatisPlusInterceptor(); // 已有其他插件可继续添加
    }

    // 注入器已在 @Component 中自动注册,无需额外配置
}

✅ 步骤4:扩展 BaseMapper

public interface MyBaseMapper<T> extends BaseMapper<T> {
    /**
     * 真批量插入
     */
    int insertBatchSomeColumn(@Param("list") List<T> list);
}

✅ 步骤5:业务 Mapper 继承并使用

public interface UserMapper extends MyBaseMapper<User> { }

// 使用示例
List<User> users = buildUsers(20000);
int rows = userMapper.insertBatchSomeColumn(users);

✅ 三、常见错误与解决方案

错误 原因 解决
Invalid bound statement (not found) 方法名与 MappedStatement id 不一致 保证 injectMappedStatementaddInsertMappedStatement 的 id 与 Mapper 方法名完全一致
MybatisPlusException: SQL injection failure SQL 拼写错误或列不匹配 打印 sql 变量,用日志确认
打包后 Lambda 失效 低版本 MyBatis-Plus 与 Spring Boot 分离打包导致 升级到 3.5.x 或避免 Lambda 方式写 SQL
数据量过大,SQL 报错 MySQL 单条 SQL 长度限制 分片批处理,每批 1000 条

✅ 四、注意事项

类别 说明
事务 大批量插入必须加 @Transactional
主键策略 自增主键需设置 @TableId(type = IdType.AUTO),雪花 ID 无需额外处理
字段过滤 使用 tableInfo.getFieldList().filter(...) 排除逻辑删除、乐观锁字段
分页批处理 一次性 > 10 万时,用 ListUtils.partition(list, 1000) 分批提交
SQL 大小 MySQL 默认最大 16M;可通过 max_allowed_packet 参数调整

✅ 五、使用技巧 & 高级玩法

技巧 代码示例
多租户字段自动注入 AbstractMethod 中读取 TenantLineInnerInterceptor 的租户 ID,拼接 SQL
动态表名 使用 tableInfo.getTableName() + 租户后缀:String table = tableInfo.getTableName() + "_" + tenantId;
逻辑删除过滤 重写 InsertBatchSomeColumn,排除 tableInfo.isLogicDelete() 字段
性能监控 结合 p6spyMybatisPlusSqlLog 打印耗时

✅ 六、最佳实践与性能优化

实践 说明
rewriteBatchedStatements=true JDBC URL 必加,MySQL 驱动将多条 insert 合并成一条多值 insert
分批大小 1000 条/批;SSD 高并发环境可 2000~5000
并行插入 CompletableFuture + 分片,多个线程独立事务,加速 3~5 倍
索引优化 批量插入前关闭二级索引 → 插入后重建,可再提升 30% 性能
日志级别 生产环境关闭 SQL 日志,开发环境开启

✅ 七、一句话总结

自定义 SQL 注入器 = 在启动阶段“写死”通用 SQL,让全局 Mapper 都拥有“真批量”能力;只需继承 DefaultSqlInjector、实现 AbstractMethod,一分钟即可在业务中调用高性能批量插入,彻底告别“伪批量”。