What’s new in JPA 2.2 – Java 8 Date and Time Types
Introduction
Now that the JPA 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
The JPA 2.2 change log says that only the following types are going to be supported:
java.time.LocalDatejava.time.LocalTimejava.time.LocalDateTimejava.time.OffsetTimejava.time.OffsetDateTime
While
LocalDateTime is rather straightforward since it only captures a point in time, similar to java.util.Date, OffsetDateTime is 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 by ZonedDateTime.
It’s also curious that the standard does not support the
java.time.Duration type which can come in handy in many business use cases.Domain Model
Considering we have following entities in our system:
The
Employee entity contains the following Java 8 Date Time attributes:birthdayattribute is aLocalDatesince we are only interested in the Date partupdatedOnis aLocalDateTimesince this attribute needs to store both Date and Time information
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| @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} |
The
Meeting entity features the following Java 8 Date Time attributes:startsAtis aZoneDateTimewhich, even if not supported by JPA 2.2, is probably a more suitable version ofOffsetDateTime- the
durationattribute might not be supported by JPA 2.2, but we will use it since Hibernate supports all these types
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| @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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| 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( 1L );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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| 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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| 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, 1L );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
Conclusion
The reason why
ZonedDateTime equals the one we previously saved is because the underlying TIMESTAMP value was transposed in the current System Time Zone.
Until all JDBC Drivers will support
TIMESTAMP WITH TIMEZONE at the java.sql.Statement parameter value binding level which is a requirement for HHH-11773, it’s doubtful that you really need to use ZonedDateTime or OffsetDateTime.
Currently, it’s much wiser to save all
TIMESTAMP values in UTC, meaning that LocalDateTime is a much better fit for your JPA entities.
No comments:
Post a Comment