应使用 preparedstatement 防范 sql 注入,spring batch 中推荐直接使用 jdbcbatchitemwriter 并设置预编译 sql,避免手动拼接、动态表名、repository 隐式风险及 executioncontext 存储原始 sql。

用 PreparedStatement 替代字符串拼接 SQL
Spring Batch 本身不校验 SQL 安全性,真正起防护作用的是底层 JDBC 层。只要你在 JdbcBatchItemWriter 或自定义 ItemWriter 中使用了原生 Statement 并手动拼接 SQL,就等于把 SQL 注入大门敞开。
正确做法是:所有 SQL 必须走 PreparedStatement 路径。Spring Batch 的 JdbcBatchItemWriter 默认就基于它,只要你没绕过它去手写 Connection.createStatement()。
- ✅ 推荐:直接用
JdbcBatchItemWriter,设置setSql("INSERT INTO t (a,b) VALUES (?, ?)"),再配ItemPreparedStatementSetter - ❌ 危险:在
write()方法里自己 newConnection+createStatement()+ 字符串拼接 - ⚠️ 注意:
@Query(nativeQuery = true)在JpaRepository中若用了${}拼接(MyBatis 风格),同样不安全;必须用#{}或命名参数
避免在 ItemWriter 中执行动态 SQL 构造
有些业务场景会根据 item 字段值决定插入哪张表、用哪个字段名,于是写出类似 "INSERT INTO " + item.getTableName() + " (...)" 的逻辑——这属于典型的“元数据注入”,比普通 SQL 注入更难防御。
这类需求本质上违背了批处理的稳定性原则。Spring Batch 的设计预期是 schema 固定、SQL 可预编译。
- ✅ 可行方案:按不同表/逻辑拆成多个独立 Step,每个 Step 绑定固定 SQL
- ✅ 折中方案:用白名单校验
item.getTableName(),只允许"order_hist"、"user_log"等已知值,拒绝任意字符串 - ❌ 不要尝试用正则过滤单引号或分号——攻击面远不止这些字符
慎用 RepositoryItemWriter + saveAll() 的隐式 SQL 风险
RepositoryItemWriter 看似安全,因为它调用的是 JPA 的 saveAll(),但隐患藏在实体类定义和数据库方言里:
- 如果实体字段用了
@Formula或@Where注解,且表达式拼接了运行时值,可能触发 HQL 注入 - 若开启
spring.jpa.properties.hibernate.globally_quoted_identifiers=true,又在列名里传入恶意标识符(如"name; DROP TABLE users--"),部分方言可能解析失败或绕过 - JPA 的
@Query若含原生 SQL 且用了#{#entity.xxx}这类 SpEL 表达式,需确认xxx是可信来源(如枚举、配置项),而非用户输入
简单说:JPA 不是免检通道,它只是把 SQL 构造时机从 DAO 层移到了 ORM 映射层,责任没消失,只是转移了。
ExecutionContext 里别存原始 SQL 或参数快照
有人为调试方便,在 ItemWriter 写完后,把刚执行的 SQL 字符串和参数列表塞进 StepExecution.getExecutionContext().put("last_sql", sql)。这看似无害,但如果该上下文后续被日志组件 dump 出来,或暴露给监控 API,就等于把注入 payload 直接回显。
尤其当参数本身来自用户输入(如导入文件中的字段值),这个快照就成了攻击证据链的一环。
- ✅ 安全替代:只存摘要,比如
"insert into target_table (3 fields), count=1" - ✅ 更好做法:用 MDC 或 Sleuth 的 traceId 关联日志,查原始 SQL 去数据库审计日志里捞
- ❌ 别把
item.toString()或items.get(0).getRawSql()往 ExecutionContext 里扔
SQL 安全不是加一层过滤就能一劳永逸的事。它取决于你是否让每条语句都经过预编译路径、是否把 schema 和行为约束在启动时确定、以及是否意识到“可观察性”本身也可能成为攻击面。










