在SpringBoot中使用Java8新时间类型

Java 8日期/时间( Date/Time)API是开发人员最受追捧的变化之一,其简洁、清晰以及线程安全等特性使得其推出后就备受Java开发者欢迎。

Java 8日期/时间API是JSR-310的实现,它的实现目标是克服旧的日期时间实现中所有的缺陷。但是Spring中的很多组件标准(如JPA2.1)的制定时间更早,因此未兼容到Java 8的新时间类型(LocalDate, LocalTime, LocalDateTime),所以直接使用这些类型作为时间属性时,会出现转换失败等异常。

但Spring各组件在随后的更新中均添加了对Java 8新时间类型的支持,但是需要进行手动配置。使用这些相关的支持,可以让我们更愉快地使用LocalDate等类型作为我们对象默认的时间属性了。

如何将LocalDate和LocalDateTime通过JPA持久化

当我们在使用JPA进行持久层操作时,如果实体(entity)的某个属性为Java 8的新时间类型时,它将无法正确地被持久化到数据库。JPA会将其映射为一个BLOB字段而不是DATE or TIMESTAMP,而这并不是我们所希望的。

使用AttributeConverter支持新时间类型的映射转换

在JPA标准中,DATE字段使用java.sql.Date进行映射,而TIMESTAMP字段则是java.sql.Timestampjava.util.Date则可以通过@Temporal注解指定映射的时间类型。因此,如果要让JPA兼容新的时间类型,则需要将新时间类型转换为JPA支持的时间类型。

使用Spring官方提供的Converter

在Sping Data的公共模块中,很早便加入了JSR-310的支持,如果要使用该Converter,只需要在你的应用启动类中添加以下配置:

@EntityScan(
  basePackageClasses = { Application.class, Jsr310JpaConverters.class}
)
@SpringBootApplication
class Application { … }

该配置将保证你的应用目录以及JSR-310的Converter将被扫描到,并且支持使用新的时间类型的entity被持久化。但需要注意的是,该Converter只是将新时间类型转换为了传统的Date,而且仅支持未带时区信息的时间类型。

使用自定义的Converter

由于Spring-Data-JPA默认使用的HibernateJPA2.1标准进行的实现。而在JPA2.1中,增加了Attribute Converter功能,因此,可以通过自定义Attribute Converter来实现新时间类型的转换。

例如:

@Converter(autoApply = true)
public class LocalDateAttributeConverter implements AttributeConverter<LocalDate, Date> {
    
    @Override
    public Date convertToDatabaseColumn(LocalDate locDate) {
        return (locDate == null ? null : Date.valueOf(locDate));
    }
    
    @Override
    public LocalDate convertToEntityAttribute(Date sqlDate) {
        return (sqlDate == null ? null : sqlDate.toLocalDate());
    }
    
}

首先需要实现AttributeConverter<LocalDate, Date>接口,包括它的两个转换方法:convertToDatabaseColumnconvertToEntityAttribute,它们两个定义了从实体类型(LocalDate)转换成数据库字段类型(Date)以及与之相反方法。这里的写法非常简单,因为java.sql.Date已经有现成的通过LocalDate进行转换的方法。

然后需要将该属性转换器添加 @Converter 注解并设置autoApply属性为true,该设置将保证该转换器会自动应用到LocalDate类型的属性上。

最后确认该Converter可以被bean扫描器扫描到,这样我就完成了一个自定义的属性转换器。同理,我们可以创建一个LocalDateTime的转换器:

@Converter(autoApply = true)
public class LocalDateTimeAttributeConverter implements AttributeConverter<LocalDateTime, Timestamp> {
    
    @Override
    public Timestamp convertToDatabaseColumn(LocalDateTime locDateTime) {
        return (locDateTime == null ? null : Timestamp.valueOf(locDateTime));
    }

    @Override
    public LocalDateTime convertToEntityAttribute(Timestamp sqlTimestamp) {
        return (sqlTimestamp == null ? null : sqlTimestamp.toLocalDateTime());
    }
}

其它方案

Hibernate5同样提供了对Java 8新时间类型的支持,可以通过pom文件中增加以下配置来添加依赖:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-java8</artifactId>
    <version>5.1.0.Final</version>
</dependency>

附:hibernate5 Java 8支持包映射关系对应表

Java type JDBC type
java.time.Duration BIGINT
java.time.Instant TIMESTAMP
java.time.LocalDateTime TIMESTAMP
java.time.LocalDate DATE
java.time.LocalTime TIME
java.time.OffsetDateTime TIMESTAMP
java.time.OffsetTime TIME
java.time.ZonedDateTime TIMESTAMP

SpringMVC如何将request参数自动封装为LocalDate和LocalDateTime

在使用SpringMVC时,java.util.Date类型字段可以使用@DateTimeFormat注解将application/x-www-from-urlencoded类型的请求中的字符串进行自动转换。而Java 8中新的时间类型该如何支持呢?

在application/x-www-from-urlencoded(键值对)请求中自动转换新时间类型

Spring4.0开始,Spring的context模块包中增加了Jsr310DateTimeFormatAnnotationFormatterFactory工厂类。该类是对@DateTimeFormat注解的JSR310标准扩展支持。因此,在Spring4.0之后,可以直接使用@DateTimeFormat注解标注LocalDate等新时间类型字段,从而实现时间格式字符串到新时间类型的自动转换。

如:

public class SimpleRequest {
    
    private Integer id;
    
    @DateTimeFormat(iso = ISO.DATE)
    private LocalDate startDate;
    
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime startTime;
}

在JSON请求中支持新时间类型的转换

如果请求的格式为application/json,则@DateTimeFormat注解将不再生效,取而代之的是Spring默认使用Jackson作为json的序列化工具,因此需要增加Jackson对新时间类型的反序列化器(Deserializer)来支持新时间类型的转换。

Jackson官方已经提供了对JSR310标准的支持包,只需在pom文件中添加以下配置引入依赖(版本自选):

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.9.3</version>
</dependency>

然后在需要反序列化(序列化)的对象字段上添加@JsonDeserialize(using = LocalDateDeserializer.class)@JsonSerialize(using = LocalDateSerializer.class))注解,Jackson便会使用该反序列化器将json字段反序列化成LocalDate类型。

如:

public class SimpleRequest {
    
    private Integer id;
    
    @JsonDeserialize(using = LocalDateDeserializer.class)
    @JsonSerialize(using = LocalDateSerializer.class)
    private LocalDate startDate;
    
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    private LocalDateTime startTime;
}

在Spring Boot 2.0之后,将默认依赖spring-boot-starter-json包,该依赖包括了jackson-datatype-jsr310在内的3种json实用工具包,因此不需要再手动添加依赖

结语

Java 8的日期/时间API非常易于使用,理解了其设计语法会让相似方法也变得非常好找。以上的这些说明就是为了方便更快地在实际应用中将旧的时间类切换为新的时间类型,虽然会有些时间上的消耗,但我相信在你理解了新Api带来的方便与好处后,就会知道这是值得的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,678评论 6 342
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,494评论 18 139
  • 和蒙哥马利写的另一本书《绿山墙的安妮》一样,里面充满了对景物的描写。各种平凡的景物,在作者笔下仿佛有了生命力一样...
    春春她说阅读 200评论 0 1
  • 你的美 光艳 璀璨 让人不忍直视 又流连忘返 多少暗夜 用泪水 洗尽自己的铅华 多少苦痛 用身体 一层一层的包裹 ...
    海静16阅读 103评论 0 0
  • 津渡(66)拒贿文|大尾巴狗 津渡目录上一章【都市】津渡(65)新气象 学校办公楼后面,常有两辆同样款式的帕萨特停...
    大尾巴狗阅读 491评论 4 5