能,且必须用;preparedstatement批量插入仍通过预编译隔离sql结构与参数,所有值须经setxxx()绑定,严禁字符串拼接或${}替换,否则失效。

batch insert 用 PreparedStatement 还能防注入吗
能,而且必须用。批量插入不是例外场景,PreparedStatement.addBatch() 和 PreparedStatement.executeBatch() 仍走预编译路径,参数值不会拼进 SQL 字符串里。只要所有字段值都通过 setString()、setInt() 等方法传入,SQL 结构就固定不变,攻击者无法靠输入改变语句逻辑。
常见错误是误以为“批量 = 拼接”,于是手动拼出一长串 INSERT INTO t VALUES (?, ?), (?, ?), ... 再用 Statement 执行——这本质还是字符串拼接,照样中招。
MyBatis 批量插入时 #{} 和 ${} 的陷阱
MyBatis 的 <foreach></foreach> 标签常用于生成批量 SQL,但写法稍有不慎就会掉进坑里:
-
<foreach collection="list" item="item" open="(" separator="),(" close=")">#{item.id}, #{item.name}</foreach>—— 安全。每个#{}都转为独立占位符,最终生成的是带多个?的预编译语句 -
INSERT INTO user VALUES ${item.id}, ${item.name}—— 危险。${} 直接字符串替换,若item.name是'a'; DROP TABLE user--,就真执行了 - 用
${}拼表名或字段名(如动态表名)时,必须白名单校验,不能依赖用户输入
批量插入前对 List 元素做校验是否必要
不是“必要”,而是“多余且误导”。对每个 item.name 做正则过滤(比如删掉单引号、分号)看似加固,实则:① 干扰正常业务数据(如人名 O’Connor);② 绕过手段极多(Unicode 编码、宽字节、注释符 -- 或 /* */);③ 掩盖真正问题——没用参数化。
真正该做的是:确保传入 addBatch() 或 MyBatis #{} 的每一个值,都是原始业务对象字段,不经过任何“拼 SQL”环节。校验应放在业务层(如非空、长度、格式),而非 SQL 层。
Oracle/MySQL 批量插入的兼容性注意点
不同数据库对批量语句的语法容忍度不同,但这和防注入无关,只影响能否执行成功:
- MySQL 支持
INSERT INTO t VALUES (?, ?), (?, ?)单条语句多值,但 Oracle 不支持,必须用UNION ALL或循环多条INSERT - PostgreSQL 支持
INSERT ... VALUES ... ON CONFLICT,但ON CONFLICT后的字段名不能用#{},否则又退化为拼接 - 无论哪种,只要参数值始终走
setXxx()或#{},注入风险就不存在;语法报错是另一类问题
最容易被忽略的一点:批量操作常伴随事务控制,而事务本身不提供任何注入防护。哪怕开了 connection.setAutoCommit(false),只要底层执行的 SQL 是拼出来的,照样被注入。防注入的关键永远在“怎么构造那条 SQL”,不在“它被包在第几层事务里”。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











