What’s new in JPA 2.2 – Java 8 Date and Time Types
Imagine having a tool that can automatically detect JPA and Hibernate performance issues. Wouldn’t that be just awesome?
Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, or Play Framework.
So, enjoy spending your time on the things you love rather than fixing performance issues in your production system on a Saturday night!
Introduction
In this article, we are going to see how JPA 2.2 Date/Time works, 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.LocalDate
java.time.LocalTime
java.time.LocalDateTime
java.time.OffsetTime
java.time.OffsetDateTime
While LocalDateTime
is rather straightforward since it only captures a date and time reference, 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:
birthday
attribute is aLocalDate
since we are only interested in the Date partupdatedOn
is aLocalDateTime
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 }
For more details about the
@NaturalId
annotation, check out this article.
The Meeting
entity features the following Java 8 Date Time attributes:
startsAt
is aZoneDateTime
which, even if not supported by JPA 2.2, is probably a more suitable version ofOffsetDateTime
- 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( 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:
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 )
Both
LocalDateTime
andZonedDateTime
share theorg.hibernate.type.descriptor.sql.TimestampTypeDescriptor
, meaning that they are going to be persisted asjava.sql.Timestamp
.While
LocalDateTime
lacks any time zone info, theZonedDateTime
is going to lose the time zone information when being saved to the associated database column.
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, 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.
I'm running an online workshop on the 11th of October about High-Performance SQL.If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
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.
