Java 8日期/時間( Date/Time)API是開發(fā)人員最受追捧的變化之一,其簡潔、清晰以及線程安全等特性使得其推出后就備受Java開發(fā)者歡迎。
Java 8日期/時間API是JSR-310的實現(xiàn),它的實現(xiàn)目標(biāo)是克服舊的日期時間實現(xiàn)中所有的缺陷。但是Spring中的很多組件標(biāo)準(zhǔn)(如JPA2.1)的制定時間更早,因此未兼容到Java 8的新時間類型(LocalDate, LocalTime, LocalDateTime),所以直接使用這些類型作為時間屬性時,會出現(xiàn)轉(zhuǎn)換失敗等異常。
但Spring各組件在隨后的更新中均添加了對Java 8新時間類型的支持,但是需要進(jìn)行手動配置。使用這些相關(guān)的支持,可以讓我們更愉快地使用LocalDate等類型作為我們對象默認(rèn)的時間屬性了。
如何將LocalDate和LocalDateTime通過JPA持久化
當(dāng)我們在使用JPA進(jìn)行持久層操作時,如果實體(entity)的某個屬性為Java 8的新時間類型時,它將無法正確地被持久化到數(shù)據(jù)庫。JPA會將其映射為一個BLOB字段而不是DATE or TIMESTAMP,而這并不是我們所希望的。
使用AttributeConverter支持新時間類型的映射轉(zhuǎn)換
在JPA標(biāo)準(zhǔn)中,DATE字段使用java.sql.Date進(jìn)行映射,而TIMESTAMP字段則是java.sql.Timestamp。 java.util.Date則可以通過@Temporal注解指定映射的時間類型。因此,如果要讓JPA兼容新的時間類型,則需要將新時間類型轉(zhuǎn)換為JPA支持的時間類型。
使用Spring官方提供的Converter
在Sping Data的公共模塊中,很早便加入了JSR-310的支持,如果要使用該Converter,只需要在你的應(yīng)用啟動類中添加以下配置:
@EntityScan(
basePackageClasses = { Application.class, Jsr310JpaConverters.class}
)
@SpringBootApplication
class Application { … }
該配置將保證你的應(yīng)用目錄以及JSR-310的Converter將被掃描到,并且支持使用新的時間類型的entity被持久化。但需要注意的是,該Converter只是將新時間類型轉(zhuǎn)換為了傳統(tǒng)的Date,而且僅支持未帶時區(qū)信息的時間類型。
使用自定義的Converter
由于Spring-Data-JPA默認(rèn)使用的Hibernate對JPA2.1標(biāo)準(zhǔn)進(jìn)行的實現(xiàn)。而在JPA2.1中,增加了Attribute Converter功能,因此,可以通過自定義Attribute Converter來實現(xiàn)新時間類型的轉(zhuǎn)換。
例如:
@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());
}
}
首先需要實現(xiàn)AttributeConverter<LocalDate, Date>接口,包括它的兩個轉(zhuǎn)換方法:convertToDatabaseColumn 和 convertToEntityAttribute,它們兩個定義了從實體類型(LocalDate)轉(zhuǎn)換成數(shù)據(jù)庫字段類型(Date)以及與之相反方法。這里的寫法非常簡單,因為java.sql.Date已經(jīng)有現(xiàn)成的通過LocalDate進(jìn)行轉(zhuǎn)換的方法。
然后需要將該屬性轉(zhuǎn)換器添加 @Converter 注解并設(shè)置autoApply屬性為true,該設(shè)置將保證該轉(zhuǎn)換器會自動應(yīng)用到LocalDate類型的屬性上。
最后確認(rèn)該Converter可以被bean掃描器掃描到,這樣我就完成了一個自定義的屬性轉(zhuǎn)換器。同理,我們可以創(chuàng)建一個LocalDateTime的轉(zhuǎn)換器:
@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支持包映射關(guān)系對應(yīng)表
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參數(shù)自動封裝為LocalDate和LocalDateTime
在使用SpringMVC時,java.util.Date類型字段可以使用@DateTimeFormat注解將application/x-www-from-urlencoded類型的請求中的字符串進(jìn)行自動轉(zhuǎn)換。而Java 8中新的時間類型該如何支持呢?
在application/x-www-from-urlencoded(鍵值對)請求中自動轉(zhuǎn)換新時間類型
從Spring4.0開始,Spring的context模塊包中增加了Jsr310DateTimeFormatAnnotationFormatterFactory工廠類。該類是對@DateTimeFormat注解的JSR310標(biāo)準(zhǔn)擴(kuò)展支持。因此,在Spring4.0之后,可以直接使用@DateTimeFormat注解標(biāo)注LocalDate等新時間類型字段,從而實現(xiàn)時間格式字符串到新時間類型的自動轉(zhuǎn)換。
如:
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請求中支持新時間類型的轉(zhuǎn)換
如果請求的格式為application/json,則@DateTimeFormat注解將不再生效,取而代之的是Spring默認(rèn)使用Jackson作為json的序列化工具,因此需要增加Jackson對新時間類型的反序列化器(Deserializer)來支持新時間類型的轉(zhuǎn)換。
而Jackson官方已經(jīng)提供了對JSR310標(biāo)準(zhǔn)的支持包,只需在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之后,將默認(rèn)依賴
spring-boot-starter-json包,該依賴包括了jackson-datatype-jsr310在內(nèi)的3種json實用工具包,因此不需要再手動添加依賴
結(jié)語
Java 8的日期/時間API非常易于使用,理解了其設(shè)計語法會讓相似方法也變得非常好找。以上的這些說明就是為了方便更快地在實際應(yīng)用中將舊的時間類切換為新的時間類型,雖然會有些時間上的消耗,但我相信在你理解了新Api帶來的方便與好處后,就會知道這是值得的。