
本文详解 postgresql 中通过 jdbc 参数化查询调用 to_date 函数的正确实践,重点排查 preparedstatement 为 null 的常见误解,并提供防 sql 注入、类型安全、时区健壮的完整实现方案。
本文详解 postgresql 中通过 jdbc 参数化查询调用 to_date 函数的正确实践,重点排查 preparedstatement 为 null 的常见误解,并提供防 sql 注入、类型安全、时区健壮的完整实现方案。
在 PostgreSQL 中,TO_DATE() 是一个字符串解析函数,它不接受参数化占位符(?)作为其格式模板或输入字符串的直接参数——但关键在于:你完全不需要让 TO_DATE 接收参数。真正的参数化应作用于 待转换的时间字符串本身,而日期格式 'YYYYMMDDHH24MISS' 是固定常量,必须硬编码在 SQL 中(它不是用户输入,不构成注入风险)。
你遇到的 NullPointerException 并非源于 prepareStatement() 返回 null(JDBC 规范严格禁止此行为),而是典型调试误区。PreparedStatement pstmt 在 try-with-resources 中声明后绝不会为 null;若发生 NPE,必然是后续对 pstmt 的某次调用(如 setString()、executeQuery())中,某个传入参数为 null 所致。例如:
// ❌ 错误:SchDprtTs1 为 null 时,setString(1, null) 不报错,但若后续逻辑依赖该值可能引发 NPE pstmt.setString(1, SchDprtTs1); // 若 SchDprtTs1 == null,此处不抛异常 pstmt.setString(2, SchDprtTs1); // 注意:这里第二个参数也用了 SchDprtTs1,而非 SchDprtTs2!原文代码存在严重笔误
✅ 正确做法如下:
1. 修正逻辑错误与空值校验
String finalSQL = """
SELECT DISTINCT USER_ID, COUNT(USER_ID) AS USER_ID_CNT
FROM s.sample
WHERE SCH_DPRT_GMT_TS >= TO_DATE(?, 'YYYYMMDDHH24MISS')
AND SCH_DPRT_GMT_TS <= TO_DATE(?, 'YYYYMMDDHH24MISS')
AND USER_ID IS NOT NULL
GROUP BY USER_ID
ORDER BY USER_ID
""";
try (Connection connection = agroalDataSource.getConnection();
PreparedStatement pstmt = connection.prepareStatement(finalSQL)) {
// ✅ 严格校验并赋值:使用 SchDprtTs1 和 SchDprtTs2(非重复)
if (StringUtils.isNotBlank(SchDprtTs1) && StringUtils.isNotBlank(SchDprtTs2)) {
pstmt.setString(1, SchDprtTs1); // 如 "20240101083000"
pstmt.setString(2, SchDprtTs2); // 如 "20240101174500"
} else {
throw new IllegalArgumentException("Both date-time strings must be non-blank");
}
try (ResultSet rs = pstmt.executeQuery()) {
// 处理结果...
}
}2. 进阶建议:避免字符串解析,优先使用 TIMESTAMP 参数(更安全)
PostgreSQL 原生支持 java.time.LocalDateTime 或 java.time.Instant(需 JDBC 4.2+ 驱动)。推荐将时间字符串在 Java 层解析为 LocalDateTime,再通过 setObject() 传入:
// ✅ 推荐:类型安全、无格式硬编码、自动时区处理
LocalDateTime start = LocalDateTime.parse(SchDprtTs1, DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
LocalDateTime end = LocalDateTime.parse(SchDprtTs2, DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
pstmt.setObject(1, start); // 自动映射为 TIMESTAMP
pstmt.setObject(2, end);对应 SQL 可简化为(无需 TO_DATE):
WHERE SCH_DPRT_GMT_TS BETWEEN ? AND ?
3. 关键注意事项
-
TO_DATE()的格式串'YYYYMMDDHH24MISS'必须是 SQL 字面量,不可参数化(否则语法错误),但它不是用户输入,无注入风险; -
永远检查 stacktrace 的 NPE 具体行号和变量,
pstmt本身不可能为null; - 使用
StringUtils.isNotBlank()而非!= null,因空字符串""传入TO_DATE会报invalid input syntax; - 生产环境务必开启 JDBC 日志(如
logLevel=2),验证实际执行的 SQL 和参数绑定。
遵循以上实践,即可安全、高效地在 PostgreSQL 中实现参数化时间范围查询,彻底规避 SQL 注入与运行时异常风险。

















