如何使用JPA持久化LocalDate?

30
我想将日期存储到我的数据库中,而不包含时间。因此,我选择使用LocalDate类型。
正如在这篇文章中提到的:如何使用JPA 2.1持久化LocalDate和LocalDateTime,我使用了一个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时出现了问题。你的调试信息显示了什么? - user3973283
@BillyFrost 我修改了我的问题。我以 JSON 格式打印 myObject。该格式不符合我的格式化要求... - Royce
这并没有隔离问题所在的位置。你的问题是JPA没有将数据持久化到数据库中,还是没有从数据库中检索到数据....或者仅仅是将Java对象转换为JSON?如果问题出现在持久性方面,那么请定义用于持久化的SQL以及您认为该SQL存在的问题所在...而“持久化”不涉及POST / PUT...它涉及JPA em.persist。 - user3973283
所以,我不明白的是为什么在 sysout() 中的格式不是 yyyy-MM-dd。因为如果我执行 sysout(myObject.getStartdate()),结果将是一个带有 yyyy-MM-dd 格式的日期... - Royce
1
好的,那么这与JPA无关。LocalDate.toString()方法有其自己的输出格式,由JAVADOCS定义,如https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html#toString-- 所述。这与“问题”有什么关系? - user3973283
@BillyFrost 谢谢。我会看一下的。 - Royce
5个回答

31

使用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解决它? - user666
Optional类是一个包装器,用于可能为空的值。您不应该使用它来包装实体字段,但如果必须这样做,可以实现AttributeConverter<Optional<LocalDate>, LocalDate>接口,将可选字段映射到实际值。 - Michael Mesfin
1
@user666 你解决那个错误了吗?(期望的日期……)? - Kash.

19

Hibernate在映射LocalDate时使用什么?如果遇到LocalDate.MAX,我需要调整日期为31.12.9999等。我正在使用AttributeConverter <LocalDate,Date>,它可以工作,但是当映射LocalDate.MIN时,java.sql.Date非常糟糕,因为我需要允许01.01.0001,但是我无法将java.sql.Date设置为比这更低的任何值。 - lostiniceland

5

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属性是一个Java中的LocalDate对象。

在持久化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()
);

4
Hibernate 5支持Java 8,因此您可以在pom.xml中添加以下内容:
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-java8</artifactId>
    <version>5.1.0.Final</version>
</dependency>

这为您提供了LocalDateLocalDateTime的映射,无需额外设置。

2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接