PreparedStatement能彻底杜绝SQL注入,因其预编译SQL模板并分离数据与代码,用户输入仅通过setXXX()以二进制参数通道传入,绝不拼入SQL文本;表名等非参数位置须白名单校验。

Java 中可以通过反射提取类中定义的静态 SQL 模板字符串(如 public static final String 字段),再结合 PreparedStatement 实现安全的参数绑定,避免 SQL 注入。关键在于:**不拼接 SQL,只提取模板,参数全部交由 PreparedStatement 处理**。
提取类中的静态 SQL 模板
假设你把 SQL 语句统一放在配置类中,例如:
public class SqlTemplates {
public static final String FIND_USER_BY_ID = "SELECT id, name, email FROM user WHERE id = ?";
public static final String SEARCH_USERS = "SELECT * FROM user WHERE name LIKE ? AND age > ?";
public static final String INSERT_USER = "INSERT INTO user (name, email) VALUES (?, ?)";
}
用反射获取这些字段值,只需筛选 public static final String 类型的字段:
- 调用
SqlTemplates.class.getDeclaredFields() - 遍历每个
Field,检查是否满足:field.getType() == String.class且修饰符包含public、static、final - 使用
field.get(null)安全读取静态字段值(无需实例)
将模板与 JDBC PreparedStatement 结合
拿到 SQL 字符串后,不能直接拼接参数,而是传给 Connection.prepareStatement(),再用 setXXX() 方法填充占位符:
- SQL 模板里所有动态值都必须用
?占位,不能出现在字符串拼接中 - 例如从
SqlTemplates.FIND_USER_BY_ID得到"SELECT ... WHERE id = ?",再执行:PreparedStatement ps = conn.prepareStatement(sqlTemplate);<br>ps.setInt(1, userId); // 类型严格匹配
- 多参数按顺序或命名(NamedParameterJdbcTemplate)设置,确保类型一致(如
setString、setInt、setTimestamp)
封装成可复用的工具方法
可以写一个通用加载器,把模板名映射到 SQL 字符串,供业务层调用:
public class SqlLoader {
private static final Map<string string> TEMPLATES = new HashMap();
<pre class="brush:php;toolbar:false;">static {
for (Field f : SqlTemplates.class.getDeclaredFields()) {
if (f.getType() == String.class
&& Modifier.isPublic(f.getModifiers())
&& Modifier.isStatic(f.getModifiers())
&& Modifier.isFinal(f.getModifiers())) {
try {
f.setAccessible(true);
TEMPLATES.put(f.getName(), (String) f.get(null));
} catch (IllegalAccessException ignored) {}
}
}
}
public static String get(String key) {
return TEMPLATES.get(key);
}
}
业务代码即可解耦使用:
String sql = SqlLoader.get("FIND_USER_BY_ID");
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, 123);
try (ResultSet rs = ps.executeQuery()) {
// 处理结果
}
}
为什么这样更安全可靠
这种做法规避了常见风险点:
- SQL 模板由开发人员在编译期定义,不可被用户输入影响
- 参数值完全脱离 SQL 语法结构,由 JDBC 驱动做类型转换和转义
- 即使模板中包含
LIKE或IN场景,也应通过setString+%value%或批处理方式处理,而非字符串拼接 - 反射仅用于读取常量,不执行方法、不构造对象、不访问私有逻辑,无额外攻击面
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











