PreparedStatement 只允许对参数值(如 WHERE 条件值、INSERT 字段值、UPDATE SET 值)使用 ? 占位;表名、列名、操作符等 SQL 结构部分必须拼接,但须经白名单或正则严格校验。

不能在 PreparedStatement 中动态拼接 SQL 语句的结构部分(比如列名、表名、WHERE 子句的字段名或操作符),因为 PreparedStatement 的预编译机制只允许对 参数值 使用 ? 占位符,不允许对 SQL 语法结构做占位。
哪些部分可以安全用 ? 占位
只有真正的数据值(即字面量)才能用 ? 替代:
- WHERE 条件中的具体值:如
WHERE name = ? AND age > ? - INSERT 的字段值:如
INSERT INTO user(name, age) VALUES (?, ?) - UPDATE 的 SET 值:如
UPDATE user SET status = ? WHERE id = ?
哪些部分不能用 ? 占位,必须拼接(需严格校验)
以下内容无法用 PreparedStatement 参数化,必须在 Java 侧拼接进 SQL 字符串,但必须确保来源可信、无注入风险:
- 表名(如
SELECT * FROM ?❌ 不合法)→ 改为"SELECT * FROM " + tableName,且 tableName 必须来自白名单或正则校验(如^[a-zA-Z_][a-zA-Z0-9_]*$) - 列名(如
ORDER BY ?❌ 不合法)→ 拼接前检查是否在允许字段列表中,如Set.of("id", "name", "created_time").contains(sortField) - 动态条件字段与操作符(如
WHERE ? = ?❌ 错误)→ 正确做法是按业务逻辑分支拼接:"WHERE status = ?" + (includeAge ? " AND age > ?" : "")
推荐的安全拼接模式:构建器 + 白名单校验
避免字符串拼接裸奔,用结构化方式控制风险:
立即学习“Java免费学习笔记(深入)”;
- 用 List 存储动态条件 SQL 片段和对应参数,最后组合成完整 SQL 和参数数组
- 所有用户可控的结构字段(如排序字段、查询表、过滤字段)必须查白名单或通过正则验证
- 示例片段:
List<String> whereClauses = new ArrayList<>(); List<Object> params = new ArrayList<>(); <p>if (status != null && STATUS_WHITELIST.contains(status)) { whereClauses.add("status = ?"); params.add(status); } if (minAge != null && minAge > 0) { whereClauses.add("age > ?"); params.add(minAge); }</p><p>String sql = "SELECT * FROM user"; if (!whereClauses.isEmpty()) { sql += " WHERE " + String.join(" AND ", whereClauses); }</p><p>PreparedStatement ps = conn.prepareStatement(sql); for (int i = 0; i < params.size(); i++) { ps.setObject(i + 1, params.get(i)); }</p>
绝对不要做的操作
- 直接拼接用户输入的任意字符串到 SQL 中(如
"WHERE " + request.getParameter("clause")) - 用反射或表达式引擎动态生成列名/表名而不校验
- 把 ? 当作“万能占位符”尝试用于非值位置(JDBC 驱动会抛 SQLException)


















