Cassandra JSON返回Timestamp解析为Java Instant失败,求最优解法
我之前也碰到过一模一样的问题,先帮你梳理下完整场景:
你在PostgreSQL里创建了这样的表:
CREATE TABLE test ( id int, time timestamp, PRIMARY KEY(id) );
通过JSON语法插入数据:
INSERT INTO test JSON '{"id":1,"time":"2018-01-12T15:06:02.753Z"}'
存储后查询表内数据,显示的time格式是带空格的:
id | time ----+-------------------------- 1 | 2018-01-12 15:06:02.753+0000
当你用SELECT JSON * from test where id=1;查询时,返回的JSON里time字段保持了这个带空格的格式:
{ "id":1, "time":"2018-01-12 15:06:02.753+0000" }
这时候用Java的Instant.parse()解析就会报错,因为这个格式不符合ISO 8601的标准规范(标准用T分隔日期和时间):
Exception in thread "main" java.time.format.DateTimeParseException: Text '2018-01-12 10:23:00.461Z' could not be parsed at index 10
不用手动替换空格,有两个更优雅的解决思路:
方案一:从数据库端输出标准ISO 8601格式(推荐)
PostgreSQL默认序列化timestamp到JSON时会用空格分隔日期和时间,我们可以在查询时主动指定time字段的输出格式,让它直接生成带T的标准格式:
SELECT json_build_object( 'id', id, 'time', to_char(time, 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"') ) FROM test WHERE id=1;
执行这个查询后,返回的JSON会变成:
{ "id":1, "time":"2018-01-12T15:06:02.753Z" }
此时直接调用Instant.parse(timeStr)就能正常解析,完全不需要Java端做额外处理。这是最优解——从数据源层面输出标准格式,从根源上避免格式兼容问题。
如果你的PostgreSQL版本是12及以上,还可以通过定义视图、或者用jsonb_set来统一处理timestamp的JSON序列化格式,避免每次查询都重复写格式化代码。
方案二:在Java端用自定义DateTimeFormatter适配格式
如果没办法修改数据库的查询语句,那可以在Java端创建一个适配该格式的DateTimeFormatter,不用手动替换字符串:
适配2018-01-12 15:06:02.753+0000格式
// 定义匹配数据库输出格式的DateTimeFormatter,指定时区为UTC DateTimeFormatter dbTimestampFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSX") .withZone(ZoneOffset.UTC); // 解析字符串到Instant String timeStr = "2018-01-12 15:06:02.753+0000"; Instant instant = Instant.from(dbTimestampFormatter.parse(timeStr));
同时兼容2018-01-12 10:23:00.461Z格式
如果你的数据同时存在+0000和Z两种结尾(比如报错信息里的格式),可以用DateTimeFormatterBuilder构建一个兼容的解析器:
DateTimeFormatter flexibleFormatter = new DateTimeFormatterBuilder() .appendPattern("yyyy-MM-dd HH:mm:ss.SSS") // 可选的时区偏移格式(比如+0000) .appendOptional(DateTimeFormatter.ofPattern("X")) // 可选的Z结尾 .appendOptional(DateTimeFormatter.ofPattern("'Z'")) .toFormatter() .withZone(ZoneOffset.UTC); // 两种格式都能正常解析 Instant instant1 = Instant.from(flexibleFormatter.parse("2018-01-12 15:06:02.753+0000")); Instant instant2 = Instant.from(flexibleFormatter.parse("2018-01-12 10:23:00.461Z"));
这个方法不需要改动数据库逻辑,直接在Java端适配格式,适合无法调整查询语句的场景。
内容的提问来源于stack exchange,提问作者user3133542

