如何使用 JPA 持久化 LocalDate?

Posted

技术标签:

【中文标题】如何使用 JPA 持久化 LocalDate?【英文标题】:How to persist LocalDate with JPA? 【发布时间】:2019-07-17 08:32:10 【问题描述】:

我想将没有时间的日期存储到我的数据库中。所以,我选择使用LocalDate 类型。

如this article 中所述,我使用JPA 转换器将LocalDate 转换为Date

但是,当我想持久化我的实体(使用 POST 和 PUT 请求)时,我遇到了一些麻烦。

错误

2019-02-23 11:26:30.254  WARN 2720 --- [-auto-1-exec-10] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Expected array or string.; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Expected array or string.
 at [Source: (PushbackInputStream); line: 1, column: 104] (through reference chain: ...entity.MyObject["startdate"])]

org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class org.springframework.http.ResponseEntity]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.springframework.http.ResponseEntity` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (PushbackInputStream); line: 1, column: 2]

代码

转换器

package ...entity;

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.time.LocalDate;
import java.sql.Date;

@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());
    

实体

package ...entity;

import org.hibernate.annotations.ColumnDefault;

import javax.persistence.*;
import java.time.LocalDate;
import java.util.HashSet;
import java.util.Set;

@Entity
public class MyObject 

    @Id
    private String id;
    private LocalDate startdate;
    private LocalDate enddate;

    public MyObject() 

    public MyObject(LocalDate enddate) 
        this.startdate = LocalDate.now();
        this.enddate = enddate;
    

    ...

“主要”

private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
MyObject myobject = new MyObject(LocalDate.parse("2019-03-01", formatter));

感谢您的帮助。

编辑 1:MyObject 的打印

 HttpHeaders headers = new HttpHeaders();
 headers.setContentType(MediaType.APPLICATION_JSON);
 HttpEntity<String> entity = new HttpEntity<>(this.toJsonString(myObject), headers);
 System.out.println(entity.toString());

 // <"id":"ba6649e4-6e65-4f54-8f1a-f8fc7143b05a","startdate":"year":2019,"month":"FEBRUARY","dayOfMonth":23,"dayOfWeek":"SATURDAY","era":"CE","dayOfYear":54,"leapYear":false,"monthValue":2,"chronology":"id":"ISO","calendarType":"iso8601","enddate":"year":2019,"month":"MARCH","dayOfMonth":1,"dayOfWeek":"FRIDAY","era":"CE","dayOfYear":60,"leapYear":false,"monthValue":3,"chronology":"id":"ISO","calendarType":"iso8601",[Content-Type:"application/json"]>

【问题讨论】:

所以你在 JSON 中有一个错误,它不是 JPA。即使是 JPA,您也需要提及您使用的是哪个 JPA 提供程序,以及您认为该 LocalDate 没有得到正确处理的地方。你的调试说明了什么 @BillyFrost 我编辑了我的问题。我在JSON 中打印myObject。格式不是我的格式化程序中指定的格式... 这对隔离问题所在没有任何作用。您在 JPA 中的问题是没有将数据持久化到您的数据库中,或者没有从您的数据库中检索数据....还是只是将 Java 对象转换为 JSON ?????如果问题存在于持久性中,则定义用于持久化的 SQL 以及您认为该 SQL 存在问题的位置......并且“持久”不涉及 POST / PUT ...它涉及 JPA em.persist 所以,我不明白为什么sysout() 的格式不是yyyy-MM-dd。因为如果我这样做sysout(myObject.getStartdate()),结果是yyyy-MM-dd 格式的日期... 好的,那么与 JPA 无关。一个LocalDate.toString() 方法有它自己的由JAVADOCS 定义的输出格式,按照docs.oracle.com/javase/8/docs/api/java/time/… 与“问题”有什么关系? 【参考方案1】:

使用 JPA 2.2,您不再需要使用转换器,它添加了对以下 java.time 类型的映射的支持:

java.time.LocalDate
java.time.LocalTime
java.time.LocalDateTime
java.time.OffsetTime
java.time.OffsetDateTime
@Column(columnDefinition = "DATE")
private LocalDate date;
@Column(columnDefinition = "TIMESTAMP")
private LocalDateTime dateTime;
@Column(columnDefinition = "TIME")
private LocalTime localTime;

【讨论】:

如果我有可选的 LocalDate 并且我将其作为 null 发送,oracle 返回:预期日期为二进制,如果我使用 java.util.Date,则不会发生这种情况。如何用 jpa 解决?【参考方案2】:

JPA 2.2 支持LocalDate,因此不需要转换器。

Hibernate 从 5.3 版本开始也支持它。

查看this article了解更多详情。

【讨论】:

Hibernate 在映射 LocalDate 时使用什么?我需要将 LocalDate.MAX 的日期调整为,例如 31.12.9999。我正在使用 AttributeConverter 并且它可以工作,但是映射 LocalDate.MIN 时 java.sql.Date 很糟糕,因为我需要允许 01.01.0001 但我不能将 java.sql.Date 设置为低于该值的任何值。 【参考方案3】:

Hibernate 5 支持 java 8,因此您可以将其添加到您的 pom.xml:

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

这为您提供了开箱即用的 LocalDateLocalDateTime 映射。

【讨论】:

【参考方案4】:

JPA 2.2 增加了对映射 Java 8 日期/时间 API 的支持,例如 LocalDateLocalTimeLocalDateTimeOffsetDateTimeOffsetTime

所以,假设我们有以下实体:

@Entity(name = "UserAccount")
@Table(name = "user_account")
public class UserAccount 

    @Id
    private Long id;

    @Column(name = "first_name", length = 50)
    private String firstName;

    @Column(name = "last_name", length = 50)
    private String lastName;

    @Column(name = "subscribed_on")
    private LocalDate subscribedOn;

    //Getters and setters omitted for brevity

注意subscribedOn 属性是一个LocalDate Java 对象。

持久化UserAccount时:

UserAccount user = new UserAccount()
    .setId(1L)
    .setFirstName("Vlad")
    .setLastName("Mihalcea")
    .setSubscribedOn(
        LocalDate.of(
            2013, 9, 29
        )
    );

entityManager.persist(user);

Hibernate 生成正确的 SQL INSERT 语句:

INSERT INTO user_account (
    first_name, 
    last_name, 
    subscribed_on, 
    id
) 
VALUES (
    'Vlad', 
    'Mihalcea', 
    '2013-09-29', 
    1
)

在获取UserAccount 实体时,我们可以看到LocalDate 已从数据库中正确获取:

UserAccount userAccount = entityManager.find(
    UserAccount.class, 1L
);

assertEquals(
    LocalDate.of(
        2013, 9, 29
    ),
    userAccount.getSubscribedOn()
);

【讨论】:

【参考方案5】:

我认为您可以编写自己的转换器,请查看答案:Spring Data JPA - Conversion failed when converting date and/or time from character string

【讨论】:

以上是关于如何使用 JPA 持久化 LocalDate?的主要内容,如果未能解决你的问题,请参考以下文章

Spring jpa hibernate mysql LocalDate在坚持一天后关闭

如何将 LocalDate 作为 Date 类型持久保存到 Hibernate

如何使用 JPA 获取最后一个持久化实体的 ID

如何使用JPA持久化多维Java数组?

将 Spring Boot 与 JPA 一起使用时如何持久化

如何知道一个分离的 JPA 实体是不是已经被持久化?