What’s new in JPA 2.2 – Java 8 Date and Time Types

Follow @vlad_mihalcea

Introduction

Now that theJPA 2.2 Review Ballot was approved, let’s start analyzing some of the new additions to the standard which have been supported by Hibernate for quite some time already.

In this article, we are going to see how Java 8 Date/Time API is supported and which types you need to use depending on your business case requirements.

Java 8 Date/Time support

TheJPA 2.2 change logsays that onlythe following types are going to be supported:

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

WhileLocalDateTimeis rather straightforward since it only captures a point in time, similar tojava.util.Date,OffsetDateTimeis more problematic because it only captures the offset, but not the time zone rules such as DST (Daylight Saving Time) or other rules defined by ZoneId and typically supported byZonedDateTime.

It’s also curious that the standard does not support thejava.time.Durationtype which can come in handy in many business use cases.

Domain Model

Considering we have following entities in our system:

TheEmployeeentity contains the following Java 8 Date Time attributes:

  • birthday
    attribute is a
    LocalDate
    since we are only interested in the Date part
  • updatedOn
    is a
    LocalDateTime
    since this attribute needs to store both Date and Time information
@Entity
(name =
"Employee"
)
public class Employee {
@Id
@GeneratedValue
private Long id;
@NaturalId
private String name;
private
LocalDate
birthday
;
@
Column
(name =
"updated_on"
)
private
LocalDateTime
updatedOn
;
//Getters are setters omitted for brevity
}

TheMeetingentity features the following Java 8 Date Time attributes:

  • startsAt
    is a
    ZoneDateTime
    which, even if not supported by JPA 2.2, is probably a more suitable version of
    OffsetDateTime
  • the
    duration
    attribute might not be supported by JPA 2.2, but we will use it since Hibernate supports all these types
@Entity
(name =
"Meeting"
)
public class Meeting {
@Id
private Long id;
@ManyToOne
(fetch = FetchType.LAZY)
@JoinColumn
(name =
"employee_id"
)
private Employee createdBy;
@Column
(name =
"starts_at"
)
private ZonedDateTime startsAt;
private
Duration
duration
;
//Getters are setters omitted for brevity
}

Testing time

Assuming we persist the following entities:

Employee employee =
new
Employee();
employee.setName(
"Vlad Mihalcea"
);
employee.setBirthday(
LocalDate.
of
(
1981
,
12
,
10
)
);
employee.setUpdatedOn(
LocalDateTime.
of
(
2015
,
12
,
1
,
8
,
0
,
0
)
);
entityManager.persist( employee );
Meeting meeting =
new
Meeting();
meeting.setId(
1
L );
meeting.setCreatedBy( employee );
meeting.setStartsAt(
ZonedDateTime.
of
(
2017
,
6
,
25
,
11
,
30
,
0
,
0
,
ZoneId.systemDefault()
)
);
meeting.setDuration(
Duration.
of
(
45
, ChronoUnit.MINUTES )
);
entityManager.persist( meeting );

Hibernate is going to generate the following SQL statements:

INSERT
INTO
Employee (
birthday,
name
,
updated_on,
id
)
VALUES
(
'1981-12-10'
,
'Vlad Mihalcea'
,
'2015-12-01 08:00:00.0'
,
1
)
INSERT
INTO
Meeting (
employee_id,
duration
,
starts_at,
id
)
VALUES
(
1
,
2700000000000
,
'2017-06-25 11:30:00.0'
,
1
)

BothLocalDateTimeandZonedDateTimeshare theorg.hibernate.type.descriptor.sql.TimestampTypeDescriptor, meaning that they are going to be persisted asjava.sql.Timestamp.

While forLocalDateTimeit makes no difference since, just likejava.sql.Timestamp, it only captures a time snapshot, theZonedDateTimeis going to lose the zone information.

When loading back our entities:

Employee employee = entityManager
.unwrap( Session.
class
)
.bySimpleNaturalId( Employee.
class
)
.load(
"Vlad Mihalcea"
);
assertEquals(
LocalDate.
of
(
1981
,
12
,
10
),
employee.getBirthday()
);
assertEquals(
LocalDateTime.
of
(
2015
,
12
,
1
,
8
,
0
,
0
),
employee.getUpdatedOn()
);
Meeting meeting = entityManager.find( Meeting.
class
,
1
L );
assertSame(
employee, meeting.getCreatedBy()
);
assertEquals(
ZonedDateTime.
of
(
2017
,
6
,
25
,
11
,
30
,
0
,
0
,
ZoneId.systemDefault()
),
meeting.getStartsAt()
);
assertEquals(
Duration.
of
(
45
, ChronoUnit.MINUTES ),
meeting.getDuration()
);

The original Java 8 Data Time objects match the ones being persisted.

If you enjoyed this article, I bet you are going to lovemy bookas well.

Conclusion

The reason whyZonedDateTimeequals the one we previously saved is because the undnerlyingTIMESTAMPvalue was transposed in the current System Time Zone.

Until all JDBC Drivers will supportTIMESTAMP WITH TIMEZONEat thejava.sql.Statementparaeter value binding level which is a requirement forHHH-11773, it’s doubtful that you really need to useZonedDateTimeorOffsetDateTime.

Currently, it’s much wiser to save allTIMESTAMPvalues inUTC, meaning thatLocalDateTimeis a much better fit for your JPA entities.

If you liked this article, you might want to subscribe tomy newsletter too.

Source: https://vladmihalcea.com/2017/06/26/whats-new-in-jpa-2-2-java-8-date-and-time-types/