一、SQL 注入器是什么?
在上文《MybatisPlus 构造器 wrapper 的使用与原理》中,我们讲解过 MybatisPlus 的核心运行流程:
- 获取语法模板 SQL
- 代入表及字段信息
- 代入条件构造器逻辑
其中第一步获取 SQL 语法模板是整个执行流程的基础。MybatisPlus 内置了大量通用 SQL 模板,能够满足绝大多数常规业务需求。但在大型项目、复杂业务场景下,内置的原生模板数量有限,无法覆盖所有特殊语法场景。
如果仅少量 SQL 需要特殊语法,我们可以手动编写 XML 文件解决;但如果大量业务 SQL 需要复用同一套特殊模板,逐个手写 XML 会极度冗余、低效、不利于统一维护。
而MybatisPlus SQL 注入器就是为解决该问题诞生的核心拓展机制:允许开发者自定义全局 SQL 模板、注入到 MybatisPlus 原生流程中,全局复用、无需重复编写 SQL,大幅简化开发、统一代码规范、提升执行性能。
二、批量插入的性能问题
2.1 saveBatch 的运行原理
日常开发中,我们通常使用 MybatisPlus 自带的saveBatch方法实现数据批量插入。
深入源码可以发现:原生 saveBatch 并不是真正意义上的批量插入。
其底层本质是for 循环逐条插入,默认每 1000 条数据执行一次flush刷新提交。
虽然该方法复用了同一个sqlSession,减少了部分链接创建开销,比逐条单独提交性能更高;
但循环单条插入依然存在大量 SQL 交互、数据库频繁解析执行语句的问题,大批量数据插入时性能瓶颈极其明显,无法满足大数据量导入、同步、初始化等业务场景。
2.2 理想的批量插入形式
数据库原生支持拼接多值的批量插入语法,一次请求写入多条数据,极大减少数据库交互次数,是性能最优的插入方式。
MySQL 标准批量插入语法
INSERT INTO your_table_name (column1, column2, column3) VALUES (value1a, value2a, value3a), (value1b, value2b, value3b), (value1c, value2c, value3c);Oracle 标准批量插入语法
INSERT ALL INTO table_name (column1, column2, ...) VALUES (value1a, value2a, ...) INTO table_name (column1, column2, ...) VALUES (value1b, value2b, ...) INTO table_name (column1, column2, ...) VALUES (value1c, value2c, ...) SELECT * FROM dual;想要极致提升批量插入性能,就需要通过SQL 注入器,将数据库原生批量插入语法封装为 MybatisPlus 全局通用方法。
三、构造批量插入的 SQL 注入器
3.1 自定义 Oracle 批量插入方法
MybatisPlus 已内置 MySQL 高性能批量插入方法InsertBatchSomeColumn,无需自定义;
但未适配 Oracle 数据库,因此我们需要手动编写 Oracle 批量插入拓展方法,继承AbstractMethod实现 SQL 注入。
public class InsertBatchSomeColumnOracle extends AbstractMethod { // Oracle 批量插入SQL模板 private static String ORACLE_INSERT_SQL_MODE = "<script>\nINSERT ALL\n %s \n SELECT 1 FROM DUAL</script>"; // 字段过滤断言 @Setter @Accessors(chain = true) private Predicate<TableFieldInfo> predicate; public InsertBatchSomeColumnOracle() { super("insertBatchSomeColumnOracle"); } @Override public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { KeyGenerator keyGenerator = new NoKeyGenerator(); List<TableFieldInfo> fieldList = tableInfo.getFieldList(); // 拼接插入字段 String insertSqlColumn = tableInfo.getKeyInsertSqlColumn(true, false) + this.filterTableFieldInfo(fieldList, predicate, TableFieldInfo::getInsertSqlColumn, EMPTY); String columnScript = LEFT_BRACKET + insertSqlColumn.substring(0, insertSqlColumn.length() - 1) + RIGHT_BRACKET; // 拼接字段属性值 String insertSqlProperty = tableInfo.getKeyInsertSqlProperty(true, ENTITY_DOT, false) + this.filterTableFieldInfo(fieldList, predicate, i -> i.getInsertSqlProperty(ENTITY_DOT), EMPTY); insertSqlProperty = LEFT_BRACKET + insertSqlProperty.substring(0, insertSqlProperty.length() - 1) + RIGHT_BRACKET; // 组装单条插入语句 String oracleModelInsertSql = "INTO " + tableInfo.getTableName() + columnScript + " VALUES " + insertSqlProperty; // 遍历集合批量拼接 String valuesScript = SqlScriptUtils.convertForeach(oracleModelInsertSql, "list", null, ENTITY, " "); String keyProperty = null; String keyColumn = null; // 主键逻辑处理(自增/序列主键) if (tableInfo.havePK()) { if (tableInfo.getIdType() == IdType.AUTO) { keyGenerator = new Jdbc3KeyGenerator(); keyProperty = tableInfo.getKeyProperty(); keyColumn = tableInfo.getKeyColumn(); } else { if (null != tableInfo.getKeySequence()) { TableInfoHelper.genKeyGenerator("insertBatchSomeColumnOracle", tableInfo, builderAssistant); keyProperty = tableInfo.getKeyProperty(); keyColumn = tableInfo.getKeyColumn(); } } } // 最终拼接完整SQL String sql = String.format(ORACLE_INSERT_SQL_MODE, valuesScript); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); return this.addInsertMappedStatement(mapperClass, modelClass, "insertBatchSomeColumnOracle", sqlSource, keyGenerator, keyProperty, keyColumn); } }3.2 自定义 SQL 注入器,注册拓展方法
继承DefaultSqlInjector,将 MySQL、Oracle 两种高性能批量插入方法注册到 MybatisPlus 全局容器中,实现全局生效。
public class CustomerSqlInjector extends DefaultSqlInjector { @Override public List<AbstractMethod> getMethodList(Class<?> mapperclass, TableInfo tableInfo) { List<AbstractMethod> methodList = super.getMethodList(mapperclass, tableInfo); // 注入MySQL高性能批量插入方法 methodList.add(new InsertBatchSomeColumn()); // 注入Oracle高性能批量插入方法 methodList.add(new InsertBatchSomeColumnOracle()); return methodList; } }3.3 自定义通用 Mapper 接口(对外调用入口)
创建通用父 Mapper,封装拓展的批量插入方法,所有业务 Mapper 继承该接口即可直接使用。
public interface CustomerMapper<T> extends BaseMapper<T> { // MySQL高性能批量插入 void insertBatchSomeColumn(@Param("list") List<T> list); // Oracle高性能批量插入 void insertBatchSomeColumnOracle(@Param("list") List<T> list); }可按需拓展 Service 层接口,适配业务层调用:
public interface CustomerService<T> extends IService<T> { void insertBatchSomeColumn(List<T> list); void insertBatchSomeColumnOracle(List<T> list); }3.4 启用自定义注入器
- 业务 Mapper 继承自定义父接口
@Mapper public interface CsdnUserInfoMapper extends CustomerMapper<CsdnUserInfo> { }- yml 配置文件指定自定义 SQL 注入器
mybatis-plus: global-config: sql-injector: com.example.MyLogicSqlInjector四、总结
本文详细剖析了MybatisPlus 原生批量插入的性能缺陷,深入讲解了SQL 注入器的核心原理,并完整实现了适配 MySQL、Oracle 的高性能全局批量插入方案。
- 原生
saveBatch底层为循环单条插入,大批量数据场景性能较差,无法满足高性能业务需求; - SQL 注入器是 MybatisPlus 强大的拓展机制,支持自定义全局 SQL 模板,无需重复编写 XML,代码复用性、可维护性极高;
- 通过自定义注入方法,适配不同数据库原生批量插入语法,一次请求批量写入多条数据,大幅减少数据库 IO 交互,数据批量插入性能提升数倍;
- 该方案全局生效,所有业务 Mapper 只需继承通用接口即可直接使用,适配所有业务场景,是企业级项目标准的批量插入优化方案。
掌握 SQL 注入器 的使用,不仅可以解决批量插入性能问题,还可以自定义查询、更新、删除等通用 SQL 模板,极大提升开发效率,是深入掌握 MybatisPlus 高级特性、进行项目性能优化的核心技能。
以上就是今天的分享,觉得有帮助,记得点赞+收藏,以上所说到的漏洞扫描工具都整理成压缩包了,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】
同时还为准备学习网络安全(黑客)或者正在学习,整理了一套360独家内部资料,包含以下内容,如有需要也可文末获取:
①网络安全学习路线
②20份渗透测试电子书
③安全攻防357页笔记
④50份安全攻防面试指南
⑤安全红队渗透工具包
⑥网络安全必备书籍
⑦100个漏洞实战案例
⑧安全大厂内部视频资源
⑨历年CTF夺旗赛题解析
一、网络安全(黑客)学习路线
网络安全(黑客)学习路线,形成网络安全领域所有的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
二、网络安全教程视频
我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。
三、网络安全CTF实战案例
光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这里带来的是CTF&SRC资料&HW资料,毕竟实战是检验真理的唯一标准嘛~
四、网络安全面试题
最后,我们所有的作为都是为就业服务的,所以关键的临门一脚就是咱们的面试题内容,所以面试题板块是咱们不可或缺的部分,这里我给大家准备的就是我在面试期间准备的资料。
网安其实不难,难的是坚持和相信自己,我的经验是既然已经选定网安你就要相信它,相信它能成为你日后进阶的高效渠道,这样自己才会更有信念去学习,才能在碰到困难的时候坚持下去。
机会属于有准备的人,这是一个实力的时代。人和人之间的差距不在于智商,而在于如何利用业余时间,只要你想学习,什么时候开始都不晚,不要担心这担心那,你只需努力,剩下的交给时间!
这份完整版的网络安全学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】