Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.0k views
in Technique[技术] by (71.8m points)

Java8 LocalDateTime 如何支持yyyy-MM-dd反序列化?

### 问题描述

想知道如何在以下场景中正确的使用LocalDateTime/LocalDate/LocalTime

项目前不久升级到Java8了, 新的业务模块都是采用Java8的语法或者API来实现, 比如Date这个对象, 在Java8中, 我们就用了新的LocalDate LocalDateTime LocalTime来替代.

今天在实现需求的时候, 需要修改一个RESTFul API接口, 新增了一个日期字段. 数据库中存储用的timestamp, 代码中用的是LocalDateTime, 格式为yyyy-MM-dd HH:mm:ss.

但是前台展示以及编辑的时候, 用的是yyyy年MM月dd日的格式.

LocalDateTime通过jackson转为yyyy年MM月dd日是没有问题的.
但是将前端传回的字符串, 再通过jackson反序列化为LocalDateTime对象时, jackson因为调用LocalDateTime的实现导致报错.

因为jackson最终会使用

public static LocalDateTime parse(CharSequence text, DateTimeFormatter formatter) {
    Objects.requireNonNull(formatter, "formatter");
 return formatter.parse(text, LocalDateTime::from);
}

这个方法, 其中LocalDateTime::from的实现为

public static LocalDateTime from(TemporalAccessor temporal) {
    if (temporal instanceof LocalDateTime) {
        return (LocalDateTime) temporal;
    } else if (temporal instanceof ZonedDateTime) {
        return ((ZonedDateTime) temporal).toLocalDateTime();
    } else if (temporal instanceof OffsetDateTime) {
        return ((OffsetDateTime) temporal).toLocalDateTime();
    }
    try {
        LocalDate date = LocalDate.from(temporal);
        // 这一行因没有时间戳传入,导致报错
        LocalTime time = LocalTime.from(temporal);
        return new LocalDateTime(date, time);
    } catch (DateTimeException ex) {
        throw new DateTimeException("Unable to obtain LocalDateTime from TemporalAccessor: " +
                temporal + " of type " + temporal.getClass().getName(), ex);
    }
}

因为没有时间, 这就导致在运行

 LocalTime time = LocalTime.from(temporal);

这行代码的时候, 程序就会报错, 最终Spring认定这个请求无效, 后台返回400

此外我也尝试使用jackson对LocalDate的实现, 当然这种骚操作也是不行的.

@JsonSerialize(using = LocalDateSerializer.class)
@JsonDeserialize(using = LocalDateDeserializer.class)
private LocalDateTime someDateTime;

### 你期待的结果是什么?实际看到的错误信息又是什么?

Date太久了, 使用LocalDateTime编程感觉有些许“水土不服”, 不知道各位有无方法能帮助一下我, 感谢!


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

其实吧,从你的问题描述来看,我觉得你对于LocalDate LocalDateTime LocalTime之间的区分以及Java8时间API的认识应该足够了,足够可以做一些简单使用的地步了,如果从我们旁观者角度来说,仅仅只是为了改你这个问题,其实之前回答的人都是可以的。

比如上官元恒提到的直接把请求对象中的字段类型由LocalDateTime改为LocalDate,这样肯定是可以的

@JsonFormat(pattern = "yyyy年MM月dd日")
LocalDate date;

因为上官元恒在意的是请求报错的问题

微凉提到的是LocalDate如何转换为LocalDateTime,当然本质上LocalDateLocalDateTime是不同,这种转换只是一家之言的转换方式,你可以根据自己的业务来做相应的修改或者就这样转也行

总归微凉想提到的是前端只有年月日,但是你数据库是有时分秒的,所以他在意的是前端到入库这个转换过程,相当于对上官元恒的补充

两个回答的结合,应该就可以解决你的问题

但是可以感觉得出来,题主应该很不愿意把LocalDateTime改为LocalDate

为啥呢?这样改代码改得多啊。因为由于数据库里是timestamp,所以多半实体对象中的类型就是LocalDateTime,因此如果按照这两个回答的方式修改,肯定要多加一层转换了。加在业务层中的转换其实有时候很烦。

要是不转换,直接用jackson全部搞定,岂不是美哉,其他代码都不用修改(当然都保不准请求对象就是实体对象都有可能,所以更不敢改,hhhhh)

这也是为啥题主宁愿不改类型,硬是要用把LocalDateDeserializer塞到LocalDateTime类型头上。

当然我说这些不是埋汰题主哈。。我主要分析一下你的心理,希望能给你一个符合你心理预期的答案。

言归正传,不知道题主说硬使用LocalDateDeserializer这样的骚操作还是不行,这个不行的原因题主有注意么?

我猜可能没注意,其实你使用LocalDateDeserializer这个反序列化操作是成功的,报错的根本原因是类型不匹配,也就是LocalDateDeserializer其实最终把String -> LocalDate,但是转换成功后的LocalDate值需要被放到请求对象中啊,这个放置的操作用的是setter方法,但由于类型不是LocalDateTime么,所以反射操作失败了呗,这是才是失败的原因
image.png

所以说LocalDateDeserializer是完全执行成功的,这种做法是可取的,只是呢跟我们后续的setter操作不匹配而已。嘿嘿~ 所以你应该明白我想做啥了吧

没错,咱们自己造一个Deserializer呗,让它返回类型为LocalDateTime不就好么,最后用反射的setter方法执行肯定也不会报错了啊

因为官方是有一个LocalDateTimeDeserializer,所以我们取名就叫CustomLocalDateTimeDeserializer,虽然咱们披着的名字里有LocalDateTime,实际咱们处理的勾当跟LocalDateDeserializer是一致的,只是根据LocalDateDeserializer的处理之后呢,我们再按照题主自己的业务去转化为LocalDateTime即可,我这里就简单采取微凉的方式,直接补齐00:00好了

最终CustomLocalDateTimeDeserializer的效果如下,代码我也放在一个github上吧,不然看起来很别扭(因为我加了很多注释说明,下面的代码去掉了注释)


public class CustomLocalDateTimeDeserializer extends LocalDateTimeDeserializer {
    private static final long serialVersionUID = 1L;
    private static final DateTimeFormatter DEFAULT_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
    private CustomLocalDateTimeDeserializer() {
        super(DEFAULT_FORMATTER);
    }
    public CustomLocalDateTimeDeserializer(DateTimeFormatter formatter) {
        super(formatter);
    }
   
     @Override
     protected LocalDateTimeDeserializer withDateFormat(DateTimeFormatter formatter) {
         return new CustomLocalDateTimeDeserializer(formatter);
     }
     @Override
     public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        LocalDate localDate = new LocalDateDeserializer(_formatter).deserialize(p, ctxt);
        LocalDateTime localDateTime = localDate.atStartOfDay();
        return localDateTime;
     }
}

最后你使用的时候,当然就简单啦,直接@JsonDeserialize即可

@JsonFormat(pattern = "yyyy年MM月dd日")
@JsonDeserialize(using = CustomLocalDateTimeDeserializer.class)
private LocalDateTime someDateTime;

以上差不多就是全部答案,所以,拜了个拜o(^▽^)┛


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...