Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to map a TIMESTAMP column to a ZonedDateTime JPA entity property?

I'm using Spring data jpa and mariadb latest version, and 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

This is my Entity:

@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;
}

When I persist my entity, Hibernate tries to save ZonedDateTime member as DATETIME column. But I want to use TIMESTAMP column instead of DATETIME column.

This is create DDL, what I see from log.

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

Here create_dt, schedule_dt, update_dt is created as datetime column type, what is not I wanted. (I don't like MyISAM, too).

How can I fix it?


Added because comment cannot express ddl.

When I use columnDefinition attribute, generated ddl is ...

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

There is unrequired '`' around TIMESTAMP.

like image 752
chaeyk Avatar asked Jun 18 '19 11:06

chaeyk


People also ask

Does JPA support ZonedDateTime?

These mappings include the offset of the local timezone to UTC. But JPA doesn't support ZonedDateTime which stores additional timezone information, like daylight saving time.

What is columnDefinition in @column annotation?

columnDefinition definition: The SQL fragment that is used when generating the DDL for the column. columnDefinition default: Generated SQL to create a column of the inferred type.

Does JPA support instant?

JPA supports LocalDate , LocalTime , LocalDateTime etc. but not Instant.


1 Answers

JPA 2.2 offers support for mapping Java 8 Date/Time API, but only for the following types:

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

However, Hibernate supports also ZonedDateTime, like this.

When saving the ZonedDateTime, the following Timestamp is going to be sent to the PreparedStatement:

Timestamp.from( zonedDateTime.toInstant() )

And, when reading it from the database, the ResultSet will contain a Timestamp that will be transformed to a ZonedDateTime, like this:

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

Note that the ZoneId is not stored in the database, so basically, you are probably better off using a LocalDateTime if this Timestamp conversion is not suitable for you.

So, let's assume we have the following entity:

@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
}

Notice that the subscribedOn attribute is a ZonedDateTime Java object.

When persisting the 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 generates the proper SQL INSERT statement:

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

When fetching the UserAccount entity, we can see that the ZonedDateTime is properly fetched from the database:

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

assertEquals(
    LocalDateTime.of(
        2020, 5, 1,
        12, 30, 0
    ).atZone(ZoneId.systemDefault()),
    userAccount.getSubscribedOn()
);
like image 137
Vlad Mihalcea Avatar answered Sep 19 '22 00:09

Vlad Mihalcea