
最近做项目整理了一下遇到的问题知识点。涵盖日期格式化、Oracle NLS、系统区域、JDBC驱动、MongoDB数据格式等技术。一、前言在Java后端开发中日期处理是高频且容易出错的环节。尤其是在混合使用MongoDB和Oracle的场景下日期字符串的解析、格式化以及与数据库的交互常常因为Locale和NLS参数的差异导致诡异错误。本文将从基础概念出发结合实际案例系统性地梳理日期格式化、Oracle隐式转换、系统区域设置等关键技术点。二、Java日期格式化核心要点2.1 DateTimeFormatter模式字母速查表字母 含义 示例2026-07-14y 年份 yy → 26yyyy → 2026M 月份 M → 7MM → 07MMM → JulMMMM → Julyd 日期 d → 14dd → 14E 星期 EEE → TueEEEE → Tuesdaya AM/PM a → AM 或 PM受Locale影响关键注意• MMM、MMMM、EEE、EEEE、a 等文本型字段必须配合Locale否则会使用JVM默认Locale中文环境可能输出“7月”、“周二”等。• 数字型字段M、MM、d、dd、yy、yyyy不受Locale影响。2.2 解析字符串到LocalDate的正确写法// ❌ 错误不指定Locale依赖默认中文环境会失败DateTimeFormatter badFmt DateTimeFormatter.ofPattern(“dd-MMM-yy”);LocalDate badDate LocalDate.parse(“14-Jul-26”, badFmt); // 抛出DateTimeParseException// ✅ 正确显式指定英文LocaleDateTimeFormatter goodFmt DateTimeFormatter.ofPattern(“dd-MMM-yy”, Locale.ENGLISH);LocalDate goodDate LocalDate.parse(“14-Jul-26”, goodFmt); // 成功2.3 LocalDate转字符串的几种方式LocalDate date LocalDate.of(2026, 7, 14);// 方式1toString() — 固定ISO格式String s1 date.toString(); // “2026-07-14”// 方式2format(DateTimeFormatter) — 自定义格式DateTimeFormatter fmt DateTimeFormatter.ofPattern(“dd-MMM-yy”, Locale.ENGLISH);String s2 date.format(fmt); // “14-Jul-26”// 方式3预定义常量String s3 date.format(DateTimeFormatter.ISO_LOCAL_DATE); // “2026-07-14”String s4 date.format(DateTimeFormatter.BASIC_ISO_DATE); // “20260714”特别说明 java.sql.Date.valueOf(LocalDate) 是将 LocalDate 转换为 java.sql.Date 对象的常用方法常用于JDBC传参。但它不直接返回字符串返回的是 java.sql.Date 对象。如果需要字符串仍需调用 toString() 或 format()。2.4 java.sql.Date 的本质*将 Java 8 的 LocalDate 转换为传统的 java.sql.Date 对象主要用于 JDBC 操作中向数据库传递日期参数。• 接收 LocalDate 的年、月、日。• 构造一个 java.sql.Date 实例其内部存储的毫秒时间戳对应于 JVM 默认时区下该日期的 00:00:00午夜。• 调用 toString() 时固定输出 yyyy-MM-dd 格式如 2026-07-14不包含时间部分。java.sql.Date 是 java.util.Date 的子类它内部存储的是一个 long 类型的毫秒时间戳从 1970-01-01 00:00:00 UTC 开始的毫秒数。但 java.sql.Date 做了特殊处理• 只保留日期部分它的 toString() 方法固定输出 yyyy-MM-dd 格式如 2026-07-14。• 时分秒清零在构造时会将时间部分强制设置为 00:00:00午夜时区为 JVM 默认时区。java.sql.Date.valueOf(LocalDate) 的行为:LocalDate localDate LocalDate.of(2026, 7, 14);java.sql.Date sqlDate java.sql.Date.valueOf(localDate);• sqlDate 内部存储的是 2026-07-14 00:00:00 对应的毫秒时间戳相对于 JVM 默认时区的午夜。• 调用 sqlDate.toString() 输出 “2026-07-14”。• 调用 sqlDate.getTime() 可以得到该毫秒数。与 PreparedStatement.setDate() 的关系:当通过 pstmt.setDate(index, sqlDate) 传入 Oracle 时JDBC 驱动会将这个 java.sql.Date 对象转换为 Oracle 的 DATE 类型只传递日期部分年月日不包含时分秒。Oracle 接收后存储的日期值就是 2026-07-14 00:00:00如果字段是 DATE 类型。与直接传 LocalDate 的区别:LocalDate localDate LocalDate.of(2026, 7, 14);java.sql.Date sqlDate java.sql.Date.valueOf(localDate);// 通过 PreparedStatement 传入 Oraclepstmt.setDate(1, sqlDate);// 等价于pstmt.setObject(1, localDate); // JDBC 4.2 也支持直接传 LocalDate这种方式下JDBC 驱动也会将 LocalDate 转换为 java.sql.Date行为与 valueOf 一致。两者等价推荐使用 setObject 更简洁。与 LocalDate.toString() 的区别:LocalDate date LocalDate.of(2026, 7, 14);// 方式1LocalDate.toString() 直接返回字符串String str1 date.toString(); // “2026-07-14”// 方式2通过 java.sql.Date.valueOf 转对象再转字符串java.sql.Date sqlDate java.sql.Date.valueOf(date);String str2 sqlDate.toString(); // “2026-07-14”两者最终字符串相同但方式1更轻量方式2主要用于 JDBC 传参场景。常见误区:• ❌ 误以为 Date.valueOf 返回字符串实际上返回的是 java.sql.Date 对象。• ❌ 误以为 java.sql.Date 包含时间它虽然内部有时间戳但 JDBC 驱动只会传递日期部分。• ❌ 误以为 valueOf 可以解析字符串java.sql.Date 还有一个 valueOf(String s) 方法已废弃用于解析 yyyy-MM-dd 格式的字符串但建议使用 LocalDate.parse 代替。特别说明• java.sql.Date 不是字符串它是一个日期对象内部是时间戳。• Date.valueOf(LocalDate) 返回的是 java.sql.Date 对象不是字符串。如果你需要字符串需要调用 toString() 或 SimpleDateFormat 格式化。• 与 java.util.Date 的区别java.sql.Date 的 toString() 固定输出 yyyy-MM-dd而 java.util.Date 的 toString() 输出类似 Tue Jul 14 00:00:00 CST 2026。时区敏感性时间部分基于 JVM 默认时区跨时区应用需谨慎建议统一使用 UTC 或明确指定时区表达式 返回类型 内部存储 toString() 输出java.sql.Date.valueOf(LocalDate.of(2026,7,14)) java.sql.Date 毫秒时间戳对应 2026-07-14 00:00:00 2026-07-14LocalDate.of(2026,7,14).toString() String 无 2026-07-14两者最终呈现的字符串都是 2026-07-14但一个是对象一个是字符串。在与 Oracle 交互时推荐直接传 LocalDate 或 java.sql.Date 对象避免字符串拼接和隐式转换问题。最佳实践// 推荐直接使用 LocalDate 通过 setObject 传参LocalDate date LocalDate.parse(“2026-07-14”);pstmt.setObject(1, date);// 兼容旧版 JDBC 驱动时使用 setDatejava.sql.Date sqlDate java.sql.Date.valueOf(date);pstmt.setDate(1, sqlDate);总结 java.sql.Date.valueOf(LocalDate) 是连接现代 LocalDate 与传统 JDBC setDate 方法的桥梁在需要兼容旧版驱动或已有代码习惯时很有用。新项目中推荐直接使用 setObject 传递 LocalDate。2.5 不同格式解析后的LocalDate是否相等只要日期相同无论输入格式如何解析后的LocalDate对象equals返回true。LocalDate d1 LocalDate.parse(“14-Jul-26”, DateTimeFormatter.ofPattern(“dd-MMM-yy”, Locale.ENGLISH));LocalDate d2 LocalDate.parse(“2026-07-14”);LocalDate d3 LocalDate.parse(“07/14/2026”, DateTimeFormatter.ofPattern(“MM/dd/yyyy”));System.out.println(d1.equals(d2)); // trueSystem.out.println(d1.equals(d3)); // true三、Oracle NLS参数详解3.1 查看当前会话的NLS_DATE_FORMATSELECT VALUE FROM NLS_SESSION_PARAMETERS WHERE PARAMETER ‘NLS_DATE_FORMAT’;– 或SELECT SYS_CONTEXT(‘USERENV’, ‘NLS_DATE_FORMAT’) FROM DUAL;3.2 查看所有NLS相关参数SELECT * FROM NLS_SESSION_PARAMETERS WHERE PARAMETER LIKE ‘NLS%’;重点关注NLS_DATE_FORMAT、NLS_DATE_LANGUAGE、NLS_LANG。3.3 修改会话的NLS_DATE_FORMAT临时ALTER SESSION SET NLS_DATE_FORMAT ‘DD-MON-RR’;ALTER SESSION SET NLS_DATE_LANGUAGE ‘AMERICAN’;3.4 查看数据库级别的默认NLSSELECT VALUE FROM NLS_DATABASE_PARAMETERS WHERE PARAMETER ‘NLS_DATE_FORMAT’;3.5 隐式转换的规则• Oracle使用当前会话的NLS_DATE_FORMAT作为模板将字符串隐式转换为DATE。• 如果字符串格式与模板不匹配抛出ORA-01861literal does not match format string或ORA-01843not a valid month。• 注意 ORA-01843有时是年份位数不匹配导致的因为解析器从左到右匹配在月份位置卡住时错误地报告月份无效。3.6 如何避免隐式转换最佳实践 在Java中解析为LocalDate通过PreparedStatement.setObject()传入日期对象。String mongoDate “14-Jul-26”;DateTimeFormatter fmt DateTimeFormatter.ofPattern(“dd-MMM-yy”, Locale.ENGLISH);LocalDate date LocalDate.parse(mongoDate, fmt);pstmt.setObject(1, date); // 或 pstmt.setDate(1, java.sql.Date.valueOf(date));次选 在SQL中使用TO_DATE显式指定格式TO_DATE(‘14-Jul-26’, ‘DD-MON-RR’, ‘NLS_DATE_LANGUAGEAMERICAN’)四、系统区域Locale对Oracle JDBC的影响4.1 影响机制• Oracle JDBC驱动在建立连接时会读取操作系统的区域格式Regional format。• 如果区域格式为英语如English (United States)驱动会自动设置会话的NLS_DATE_FORMAT为DD-MON-RR两位数年份。• 如果区域格式为非英语如中文驱动可能使用数据库默认的严格格式如DD-MON-YYYY导致两位数年份的字符串解析失败。4.2 如何设置Windows区域格式• 路径设置 → 时间和语言 → 语言和区域• 关键选项区域格式Regional format → 选择English (United States)或English (American Samoa)• 注意 修改后需重启IDE和数据库连接。4.3 验证方法修改区域后在Oracle中执行SELECT VALUE FROM NLS_SESSION_PARAMETERS WHERE PARAMETER ‘NLS_DATE_FORMAT’;期望输出DD-MON-RR4.4 如果无法修改系统区域可以在应用连接池配置中添加初始化SQLHikariCP示例spring.datasource.hikari.connection-init-sql: ALTER SESSION SET NLS_DATE_FORMAT‘DD-MON-RR’以下是针对“无法修改系统区域”场景基于 UCPUniversal Connection Pool 和 普通 JDBC 基础连接 的示例用于设置 Oracle 会话的 NLS_DATE_FORMAT。方案一使用 UCP 连接池推荐企业级UCP 是 Oracle 官方推荐的连接池支持通过 ConnectionInitializationCallback 或连接属性来初始化会话。通过 ConnectionInitializationCallback最灵活import oracle.ucp.jdbc.PoolDataSource;import oracle.ucp.jdbc.PoolDataSourceFactory;import oracle.ucp.jdbc.ConnectionInitializationCallback;import java.sql.Connection;import java.sql.SQLException;import java.sql.Statement;public class UcpWithNlsSetting {public static void main(String[] args) throws SQLException { PoolDataSource pds PoolDataSourceFactory.getPoolDataSource(); pds.setConnectionFactoryClassName(oracle.jdbc.pool.OracleDataSource); pds.setURL(jdbc:oracle:thin://host:1521/service); pds.setUser(user); pds.setPassword(password); pds.setInitialPoolSize(2); pds.setMaxPoolSize(10); // 设置连接初始化回调 pds.setConnectionInitializationCallback(new ConnectionInitializationCallback() { Override public void initialize(Connection connection) throws SQLException { try (Statement stmt connection.createStatement()) { stmt.execute(ALTER SESSION SET NLS_DATE_FORMAT DD-MON-RR); stmt.execute(ALTER SESSION SET NLS_DATE_LANGUAGE AMERICAN); } } }); // 获取连接并使用 try (Connection conn pds.getConnection()) { // 你的业务代码 System.out.println(Session NLS_DATE_FORMAT initialized.); } }}通过连接属性适用于简单场景某些 UCP 版本支持在 PoolDataSource 上直接设置连接属性但 NLS_DATE_FORMAT 不属于标准连接属性。更可靠的方式仍是使用 ConnectionInitializationCallback。方案二普通 JDBC 基础连接无连接池如果项目未使用连接池可以在每次获取连接后立即执行 ALTER SESSION。import java.sql.Connection;import java.sql.DriverManager;import java.sql.SQLException;import java.sql.Statement;public class BasicJdbcWithNls {public static Connection getConnection() throws SQLException { Connection conn DriverManager.getConnection( jdbc:oracle:thin://host:1521/service, user, password ); // 初始化会话 NLS 参数 try (Statement stmt conn.createStatement()) { stmt.execute(ALTER SESSION SET NLS_DATE_FORMAT DD-MON-RR); stmt.execute(ALTER SESSION SET NLS_DATE_LANGUAGE AMERICAN); } return conn; } public static void main(String[] args) throws SQLException { try (Connection conn getConnection()) { // 执行业务 SQL System.out.println(Connection established with custom NLS settings.); } }}方案三使用 Spring 的 DataSourceUtils 或 AOP 统一初始化如果项目使用 Spring 框架可以通过切面或 PostConstruct 方式统一设置。Componentpublic class NlsInitializer {Autowired private DataSource dataSource; EventListener(ApplicationReadyEvent.class) public void init() { try (Connection conn DataSourceUtils.getConnection(dataSource)) { try (Statement stmt conn.createStatement()) { stmt.execute(ALTER SESSION SET NLS_DATE_FORMAT DD-MON-RR); stmt.execute(ALTER SESSION SET NLS_DATE_LANGUAGE AMERICAN); } } catch (SQLException e) { throw new RuntimeException(Failed to initialize NLS settings, e); } }}方案四最彻底的方式——Java 代码中强类型传参推荐无论是否设置 NLS_DATE_FORMAT只要在 Java 中解析日期并直接传递 LocalDate 对象就能完全绕过隐式转换。String mongoDate “14-Jul-26”;DateTimeFormatter fmt DateTimeFormatter.ofPattern(“dd-MMM-yy”, Locale.ENGLISH);LocalDate date LocalDate.parse(mongoDate, fmt);try (PreparedStatement pstmt conn.prepareStatement(“UPDATE table_a SET date_col ? WHERE id ?”)) {pstmt.setObject(1, date); // 直接传 LocalDate 对象pstmt.setInt(2, 123);pstmt.executeUpdate();}优点• 不依赖任何 NLS 参数设置。• 不受系统区域、驱动版本、连接池配置影响。• 代码可移植性最强。总结建议场景 推荐方案无法修改系统区域但可以修改代码 使用 UCP 的 ConnectionInitializationCallback 或 普通 JDBC 的 ALTER SESSION希望彻底根除环境差异 Java 代码中强类型传参setObject 传 LocalDate已有 Spring 项目 使用 AOP 或事件监听器 统一初始化会话最佳实践 即使使用了 ALTER SESSION也建议同时在 Java 代码中采用强类型传参形成双重保障。五、JDBC驱动版本相关5.1 驱动版本对NLS的影响• 不同版本的JDBC驱动对客户端NLS_LANG的继承策略可能不同。• 较新版本如ojdbc11 23.x可能更严格地遵守标准导致隐式转换行为变化。• 验证方法 在同一环境下更换驱动版本测试如果行为不变则问题不在驱动。5.2 常用驱动版本版本 Maven坐标19.3 com.oracle.jdbc:ojdbc8:19.3.0.023.9 com.oracle.database.jdbc:ojdbc11:23.9.0.25.075.3 连接池初始化SQL配置HikariCPHikariConfig config new HikariConfig();config.setConnectionInitSql(“ALTER SESSION SET NLS_DATE_FORMAT‘DD-MON-RR’”);六、MongoDB数据格式相关6.1 查询MongoDB中日期字段的原始值db.collection.findOne({}, { curdate: 1 })观察curdate字段的字符串格式常见的有• “14-Jul-26”英文缩写月份• “2026-07-14”ISO标准格式6.2 如果数据格式不一致在Java代码中增加多格式解析逻辑String mongoDate doc.getString(“curdate”);LocalDate date;try {date LocalDate.parse(mongoDate, DateTimeFormatter.ofPattern(“dd-MMM-yy”, Locale.ENGLISH));} catch (DateTimeParseException e) {date LocalDate.parse(mongoDate, DateTimeFormatter.ofPattern(“yyyy-MM-dd”));}七、常见错误及排查步骤7.1 ORA-01843: not a valid month• 可能原因 月份缩写语言不匹配如中文环境解析英文缩写、年份位数不匹配两位数年份被四位数格式拒绝。• 排查步骤检查Java代码中是否显式指定了Locale.ENGLISH。检查Oracle会话的NLS_DATE_FORMAT和NLS_DATE_LANGUAGE。检查系统区域格式是否为英语。检查连接池是否有初始化SQL。7.2 ORA-01861: literal does not match format string• 可能原因 字符串整体格式与NLS_DATE_FORMAT不匹配如分隔符不同、缺少时间部分等。• 排查步骤打印实际传入的字符串检查是否有隐藏字符。确认NLS_DATE_FORMAT的模板与字符串结构一致。7.3 DateTimeParseException• 可能原因 字符串格式与DateTimeFormatter模式不匹配或Locale不正确。• 排查步骤确认模式字符串中的字母含义如MMM需要英文缩写。确认Locale参数是否与字符串语言一致。八、最佳实践总结所有涉及MMM、MMMM、EEE、EEEE的日期格式化必须显式指定Locale.ENGLISH或与数据源一致的语言。与Oracle交互时优先使用PreparedStatement.setObject()传入LocalDate或java.sql.Date彻底避免隐式转换。开发环境系统区域格式设置为英语美国使JDBC驱动自动配置宽松的NLS_DATE_FORMAT。连接池配置初始化SQL确保所有会话的NLS_DATE_FORMAT统一。MongoDB中的日期字符串建议统一存储为ISO格式yyyy-MM-dd减少解析复杂性。代码中避免拼接SQL字符串使用参数化查询。多格式兼容如果数据源格式不确定使用try-catch尝试多种解析器。ORA-01843错误排查实录系统区域、JDBC驱动与MongoDB数据源的协同一、问题背景在某项目中Java应用从MongoDB读取日期字符串如14-Jul-26然后通过JDBC直接拼接到SQL中更新Oracle的DATE字段。本地开发环境报错ORA-01843: not a valid month而线上环境IKP完全正常。同一套代码、同一个数据库为何本地报错而线上不报本文记录了完整的排查过程和解决方案。二、环境信息环境 操作系统区域格式 JDBC驱动版本 NLS_DATE_FORMAT会话 结果本地Windows 中文简体中国 ojdbc8 19.3 / ojdbc11 23.9 DD-MON-YYYY HH24:MI:SS ❌ 报错线上IKP Linux en_US.UTF-8 同本地 DD-MON-RR ✅ 成功三、Root Cause分析3.1 隐式转换机制Oracle在将字符串’14-Jul-26’转换为DATE时使用当前会话的NLS_DATE_FORMAT作为模板。本地会话的格式是DD-MON-YYYY HH24:MI:SS要求年份为四位数字且必须包含时分秒。而字符串只有两位年份且无时间部分导致解析失败抛出ORA-01843。3.2 系统区域对NLS_DATE_FORMAT的影响Oracle JDBC驱动在建立连接时会读取操作系统的区域格式。如果区域格式为英语如English (United States)驱动会自动设置会话的NLS_DATE_FORMAT为DD-MON-RR两位数年份。如果区域格式为非英语驱动可能使用数据库默认的严格格式。• 线上环境 系统区域为en_US.UTF-8驱动自动设置NLS_DATE_FORMAT‘DD-MON-RR’能匹配’14-Jul-26’。• 本地环境 系统区域为中文驱动未自动调整会话使用数据库默认的DD-MON-YYYY HH24:MI:SS无法匹配。3.3 为什么报错信息是“无效月份”Oracle的解析器从左到右匹配模板。当遇到DD-MON-后期望接下来是四位年份但读到26后无法继续解析器在月份位置产生歧义错误地报告月份无效。实际上根因是年份位数不匹配。四、排查过程4.1 第一步确认会话NLS参数在本地代码报错后立即在同一个连接中执行SELECT * FROM NLS_SESSION_PARAMETERS WHERE PARAMETER LIKE ‘NLS_DATE%’;结果NLS_DATE_FORMAT DD-MON-YYYY HH24:MI:SSNLS_DATE_LANGUAGE AMERICAN。4.2 第二步临时修改会话参数验证在同一个连接中执行ALTER SESSION SET NLS_DATE_FORMAT ‘DD-MON-RR’;然后重新执行更新语句不再报错。确认根因是NLS_DATE_FORMAT不匹配。4.3 第三步对比线上环境联系运维确认线上应用的连接池配置发现线上应用在启动时执行了ALTER SESSION SET NLS_DATE_FORMAT‘DD-MON-RR’通过连接池初始化SQL。而本地没有这个配置。4.4 第四步检查系统区域设置本地Windows系统区域格式为“中文简体中国”修改为“English (United States)”后重启IDE重新连接数据库NLS_DATE_FORMAT自动变为DD-MON-RR问题解决。五、解决方案5.1 方案一修改系统区域格式快速验证• Windows 设置 → 时间和语言 → 语言和区域 → 区域格式 → 选择“English (United States)”• Linux/macOS 设置LANGen_US.UTF-8环境变量5.2 方案二连接池初始化SQL推荐生产环境在HikariCP配置中添加spring.datasource.hikari.connection-init-sql: ALTER SESSION SET NLS_DATE_FORMAT‘DD-MON-RR’5.3 方案三Java代码中强类型传参最彻底String mongoDate “14-Jul-26”;DateTimeFormatter fmt DateTimeFormatter.ofPattern(“dd-MMM-yy”, Locale.ENGLISH);LocalDate date LocalDate.parse(mongoDate, fmt);pstmt.setObject(1, date); // 完全绕过隐式转换六、经验教训永远不要依赖Oracle的隐式转换尤其在不同语言环境下。系统区域设置是容易被忽略的环境差异源开发机应与线上保持一致。连接池初始化SQL是统一会话参数的有效手段。代码中显式指定Locale如Locale.ENGLISH是避免本地化问题的基本素养。七、附录相关命令速查目的 命令查看会话NLS_DATE_FORMAT SELECT VALUE FROM NLS_SESSION_PARAMETERS WHERE PARAMETER‘NLS_DATE_FORMAT’;临时修改会话格式 ALTER SESSION SET NLS_DATE_FORMAT‘DD-MON-RR’;查看数据库默认格式 SELECT VALUE FROM NLS_DATABASE_PARAMETERS WHERE PARAMETER‘NLS_DATE_FORMAT’;查看Windows区域格式 设置 → 时间和语言 → 语言和区域查看Linux区域设置 echo $LANG一、Java 日期格式化核心要点DateTimeFormatter 模式字母含义字母 含义 示例2026-07-14y 年份 yy → 26yyyy → 2026M 月份 M → 7MM → 07MMM → JulMMMM → Julyd 日期 d → 14dd → 14E 星期 EEE → TueEEEE → Tuesdaya AM/PM a → AM关键注意• MMM、MMMM、EEE、EEEE、a 等文本型字段必须配合 Locale否则会使用 JVM 默认 Locale中文环境可能输出“7月”、“周二”等。• 数字型字段M、MM、d、dd、yy、yyyy不受 Locale 影响。解析字符串到 LocalDate 的正确写法// ❌ 错误不指定 Locale依赖默认中文环境会失败DateTimeFormatter badFmt DateTimeFormatter.ofPattern(“dd-MMM-yy”);LocalDate badDate LocalDate.parse(“14-Jul-26”, badFmt); // 抛出异常// ✅ 正确显式指定英文 LocaleDateTimeFormatter goodFmt DateTimeFormatter.ofPattern(“dd-MMM-yy”, Locale.ENGLISH);LocalDate goodDate LocalDate.parse(“14-Jul-26”, goodFmt); // 成功LocalDate 转字符串的几种方式LocalDate date LocalDate.of(2026, 7, 14);// 方式1toString() — 固定 ISO 格式String s1 date.toString(); // “2026-07-14”// 方式2format(DateTimeFormatter) — 自定义格式DateTimeFormatter fmt DateTimeFormatter.ofPattern(“dd-MMM-yy”, Locale.ENGLISH);String s2 date.format(fmt); // “14-Jul-26”// 方式3预定义常量String s3 date.format(DateTimeFormatter.ISO_LOCAL_DATE); // “2026-07-14”String s4 date.format(DateTimeFormatter.BASIC_ISO_DATE); // “20260714”不同格式解析后的 LocalDate 是否相等只要日期相同无论输入格式如何解析后的 LocalDate 对象 equals 返回 true。LocalDate d1 LocalDate.parse(“14-Jul-26”, DateTimeFormatter.ofPattern(“dd-MMM-yy”, Locale.ENGLISH));LocalDate d2 LocalDate.parse(“2026-07-14”);LocalDate d3 LocalDate.parse(“07/14/2026”, DateTimeFormatter.ofPattern(“MM/dd/yyyy”));System.out.println(d1.equals(d2)); // trueSystem.out.println(d1.equals(d3)); // true二、Oracle NLS 参数相关查看当前会话的 NLS_DATE_FORMATSELECT VALUE FROM NLS_SESSION_PARAMETERS WHERE PARAMETER ‘NLS_DATE_FORMAT’;– 或SELECT SYS_CONTEXT(‘USERENV’, ‘NLS_DATE_FORMAT’) FROM DUAL;查看所有 NLS 相关参数SELECT * FROM NLS_SESSION_PARAMETERS WHERE PARAMETER LIKE ‘NLS%’;重点关注NLS_DATE_FORMAT、NLS_DATE_LANGUAGE、NLS_LANG。修改会话的 NLS_DATE_FORMAT临时ALTER SESSION SET NLS_DATE_FORMAT ‘DD-MON-RR’;ALTER SESSION SET NLS_DATE_LANGUAGE ‘AMERICAN’;查看数据库级别的默认 NLSSELECT VALUE FROM NLS_DATABASE_PARAMETERS WHERE PARAMETER ‘NLS_DATE_FORMAT’;查看实例级别的 NLS 参数SELECT VALUE FROM V$PARAMETER WHERE NAME ‘nls_date_format’;隐式转换的规则• Oracle 使用当前会话的 NLS_DATE_FORMAT 作为模板将字符串隐式转换为 DATE。• 如果字符串格式与模板不匹配抛出 ORA-01861数据格式不匹配或 ORA-01843无效月份。• 注意 ORA-01843 有时是年份位数不匹配导致的因为解析器从左到右匹配在月份位置卡住时错误地报告月份无效。如何避免隐式转换• 最佳实践 在 Java 中解析为 LocalDate通过 PreparedStatement.setObject() 传入日期对象。• 次选 在 SQL 中使用 TO_DATE 显式指定格式TO_DATE(‘14-Jul-26’, ‘DD-MON-RR’, ‘NLS_DATE_LANGUAGEAMERICAN’)三、系统区域Locale对 Oracle JDBC 的影响影响机制• Oracle JDBC 驱动在建立连接时会读取操作系统的区域格式Regional format。• 如果区域格式为英语如 English (United States)驱动会自动设置会话的 NLS_DATE_FORMAT 为 DD-MON-RR两位数年份。• 如果区域格式为非英语如中文驱动可能使用数据库默认的严格格式如 DD-MON-YYYY导致两位数年份的字符串解析失败。如何设置 Windows 区域格式• 路径设置 → 时间和语言 → 语言和区域• 关键选项区域格式Regional format → 选择 English (United States) 或 English (American Samoa)• 注意 修改后需重启 IDE 和数据库连接。验证方法修改区域后在 Oracle 中执行SELECT VALUE FROM NLS_SESSION_PARAMETERS WHERE PARAMETER ‘NLS_DATE_FORMAT’;期望输出DD-MON-RR如果无法修改系统区域可以在应用连接池配置中添加初始化 SQLHikariCP 示例spring.datasource.hikari.connection-init-sql: ALTER SESSION SET NLS_DATE_FORMAT‘DD-MON-RR’四、JDBC 驱动版本相关驱动版本对 NLS 的影响• 不同版本的 JDBC 驱动对客户端 NLS_LANG 的继承策略可能不同。• 较新版本如 ojdbc11 23.x可能更严格地遵守标准导致隐式转换行为变化。• 验证方法 在同一环境下更换驱动版本测试如果行为不变则问题不在驱动。常用驱动版本版本 Maven 坐标19.3 com.oracle.jdbc:ojdbc8:19.3.0.023.9 com.oracle.database.jdbc:ojdbc11:23.9.0.25.07连接池初始化 SQL 配置HikariCPHikariConfig config new HikariConfig();config.setConnectionInitSql(“ALTER SESSION SET NLS_DATE_FORMAT‘DD-MON-RR’”);五、MongoDB 数据格式相关查询 MongoDB 中日期字段的原始值db.collection.findOne({}, { curdate: 1 })观察 curdate 字段的字符串格式常见的有• “14-Jul-26”英文缩写月份• “2026-07-14”ISO 标准格式如果数据格式不一致在 Java 代码中增加多格式解析逻辑String mongoDate doc.getString(“curdate”);LocalDate date;try {date LocalDate.parse(mongoDate, DateTimeFormatter.ofPattern(“dd-MMM-yy”, Locale.ENGLISH));} catch (DateTimeParseException e) {date LocalDate.parse(mongoDate, DateTimeFormatter.ofPattern(“yyyy-MM-dd”));}六、常见错误及排查步骤ORA-01843: not a valid month• 可能原因 月份缩写语言不匹配如中文环境解析英文缩写、年份位数不匹配两位数年份被四位数格式拒绝。• 排查步骤检查 Java 代码中是否显式指定了 Locale.ENGLISH。检查 Oracle 会话的 NLS_DATE_FORMAT 和 NLS_DATE_LANGUAGE。检查系统区域格式是否为英语。检查连接池是否有初始化 SQL。ORA-01861: literal does not match format string• 可能原因 字符串整体格式与 NLS_DATE_FORMAT 不匹配如分隔符不同、缺少时间部分等。• 排查步骤打印实际传入的字符串检查是否有隐藏字符。确认 NLS_DATE_FORMAT 的模板与字符串结构一致。DateTimeParseException• 可能原因 字符串格式与 DateTimeFormatter 模式不匹配或 Locale 不正确。• 排查步骤确认模式字符串中的字母含义如 MMM 需要英文缩写。确认 Locale 参数是否与字符串语言一致。七、最佳实践总结所有涉及 MMM、MMMM、EEE、EEEE 的日期格式化必须显式指定 Locale.ENGLISH或与数据源一致的语言。与 Oracle 交互时优先使用 PreparedStatement.setObject() 传入 LocalDate 或 java.sql.Date彻底避免隐式转换。开发环境系统区域格式设置为英语美国使 JDBC 驱动自动配置宽松的 NLS_DATE_FORMAT。连接池配置初始化 SQL确保所有会话的 NLS_DATE_FORMAT 统一。MongoDB 中的日期字符串建议统一存储为 ISO 格式yyyy-MM-dd减少解析复杂性。代码中避免拼接 SQL 字符串使用参数化查询。多格式兼容如果数据源格式不确定使用 try-catch 尝试多种解析器。