如何将 TIMESTAMP 列映射到 ZonedDateTime JPA 实体属性?

Posted

技术标签:

【中文标题】如何将 TIMESTAMP 列映射到 ZonedDateTime JPA 实体属性?【英文标题】:How to map a TIMESTAMP column to a ZonedDateTime JPA entity property? 【发布时间】:2019-11-01 00:43:34 【问题描述】:

我正在使用 Spring data jpama​​riadb 最新版本,以及 MariaDB 10.3.16

+--- org.springframework.boot:spring-boot-starter-data-jpa -> 2.1.5.RELEASE
...
|    +--- org.springframework.boot:spring-boot-starter-jdbc:2.1.5.RELEASE
...
|    +--- org.hibernate:hibernate-core:5.3.10.Final

这是我的实体:

@Entity
@Data
@Table
@NoArgsConstructor
@AllArgsConstructor
public class Note 
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Integer id;

    @Column
    private String gsn;

    @Column
    @Enumerated(EnumType.STRING)
    private NoteType type;

    @Column
    private String text;

    @Column
    private ZonedDateTime scheduleDt;

    @Column
    @CreationTimestamp
    private Instant createDt;

    @Column
    @UpdateTimestamp
    private ZonedDateTime updateDt;

当我持久化我的实体时,Hibernate 会尝试将 ZonedDateTime 成员保存为 DATETIME 列。但我想使用 TIMESTAMP 列而不是 DATETIME 列。

这是创建 DDL,我从日志中看到的。

create table `note` (`id` integer not null, `create_dt` datetime,
    `gsn` varchar(255), `schedule_dt` datetime, `text` varchar(255),
    `type` varchar(255), `update_dt` datetime, primary key (`id`)) 
  engine=MyISAM

这里create_dtschedule_dtupdate_dt 被创建为datetime 列类型,这不是我想要的。 (我也不喜欢 MyISAM)。

我该如何解决?


由于注释无法表达ddl而添加。

当我使用 columnDefinition 属性时,生成的 ddl 是 ...

create table `note` (`id` integer not null, `create_dt` datetime,
    `gsn` varchar(255), `schedule_dt` datetime, `text` varchar(255),
    `type` varchar(255), `update_dt` `TIMESTAMP`, primary key (`id`)) 

engine=MyISAM

TIMESTAMP 周围有不需要的“`”。

【问题讨论】:

不是很相关,但为什么要使用 ZonedDateTime 而不是 Instant 或 LocalDateTime?时间戳不会在任何地方存储存储在 ZonedDateTime 中的时区,因此您将无法使用原始时区取回 ZonedDateTime。 @JB 是的,你是对的。时区不可恢复。无论如何 Instant,LocalDateTime 也不起作用。生成的 DDL 使用 DATETIME 列类型。 @galex:您的内联代码格式更改很好,但软件名称和版本并没有通过将它们加粗来提高可读性。一般来说这是没有必要的。 好的,谢谢你的建议,我是***的新手 与问题无关,但永远不要将 lombok 的 Data 或 EqualsAndHashcode 用于实体。您可以检查反编译的文件:lombok 不理解实体。实体不是价值对象。 Lombok 对 equals 和 hascode 的实现既不正确,又是潜在的性能杀手。检查例如@vlad-mihalcea blog vladmihalcea.com/… 以获得正确的实施。 【参考方案1】:

JPA 2.2 支持映射 Java 8 日期/时间 API,但仅适用于以下类型:

java.time.LocalDate java.time.LocalTime java.time.LocalDateTime java.time.OffsetTime java.time.OffsetDateTime

不过,Hibernate 也支持ZonedDateTime,就像这样。

保存ZonedDateTime时,以下Timestamp将被发送到PreparedStatement

Timestamp.from( zonedDateTime.toInstant() )

并且,当从数据库中读取它时,ResultSet 将包含一个Timestamp,它将被转换为一个ZonedDateTime,如下所示:

ts.toInstant().atZone( ZoneId.systemDefault() )

注意ZoneId 没有存储在数据库中,所以基本上,如果Timestamp 转换不适合您,您最好使用LocalDateTime

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

@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 ZonedDateTime subscribedOn;

    //Getters and setters omitted for brevity

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

当持久化UserAccount:

UserAccount user = new UserAccount()
    .setId(1L)
    .setFirstName("Vlad")
    .setLastName("Mihalcea")
    .setSubscribedOn(
        LocalDateTime.of(
            2020, 5, 1,
            12, 30, 0
        ).atZone(ZoneId.systemDefault())
    );

entityManager.persist(user);

Hibernate 生成正确的 SQL INSERT 语句:

INSERT INTO user_account (
    first_name, 
    last_name, 
    subscribed_on, 
    id
) 
VALUES (
    'Vlad', 
    'Mihalcea', 
    '2020-05-01 12:30:00.0', 
    1
)

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

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

assertEquals(
    LocalDateTime.of(
        2020, 5, 1,
        12, 30, 0
    ).atZone(ZoneId.systemDefault()),
    userAccount.getSubscribedOn()
);

【讨论】:

JDBC 没有定义对ZonedDateTime 的支持,这是一个非标准扩展,JPA 2.2 也没有定义对它的支持(检查您自己的博文和您从该博文链接的 JPA 2.2 更改日志) . 我更新了答案。基本上,Hibernate 支持ZonedDateTime,我解释了它是如何做到的,即使 JDBC 不提供对这种类型的本机支持。【参考方案2】:

您可以使用@Column 注释定义列类型:

@Column(columnDefinition="TIMESTAMP")  
@UpdateTimestamp
private ZonedDateTime updateDt;

【讨论】:

这会产生错误。 DDL 是... 创建表 note (id integer not null auto_increment, create_dt datetime(6), gsn varchar(255), schedule_dt TIMESTAMP, text varchar(255), type varchar(255), update_dt datetime(6), 主键 (id)) engine=InnoDB 引擎很可能不同:您的引擎是 InnoDB,但是使用了有问题的定义 MyISAM。 @SimonMartinelli 我在上面添加了 ddl,因为注释删除了 '`'

以上是关于如何将 TIMESTAMP 列映射到 ZonedDateTime JPA 实体属性?的主要内容,如果未能解决你的问题,请参考以下文章

映射列类型 Slick 3.1.1

如何将值从一列映射到另一列数据框? [复制]

如何在 google sheet api 中获取列标题及其名称的映射?

如何将每一列映射到pyspark数据框中的其他列?

如果列中存在数据,如何将列名映射到查询响应中?

在 spark 和 scala 中,如何将数据框转换或映射到特定列信息?