Java 8 Time API (JSR310), Hibernate and Spring-Data-JPA

Recently I spend some time porting one of my old applications to Java 8. Java 8 offers besides several nice enhancements with respect to the overall language expression (i.e., Lambda expressions or the Stream API) also a new Date-Time package released under the JSR 310. Since I’ve been working for quite some time with joda-time, I was really interested what Java 8 had to offer.

Calendar

First off all, although JSR310 is not a direct port of joda-time, it is very intuitive and migrating from joda-time to the new java.time.* package started without any unexpected impediments. I was able to port my resources and REST services with having any major problem, but when I finally reached the point to port my persistence layer, consisting of spring-data-jpa and Hibernate with an underlying MySQL engine I discovered that the transition is not as smooth as expected. I’ve implemented my entities back in the days using the  java.util.Date functionality, and never bothered touching this layer and adjust it in order to use joda-time or any other date library. Therefore I never ran into the issue of Date and Time serialization from hibernate to the underlying persistence store. Since I upgraded all my underlying dependencies to use the latest version, my expectation was that I could adjust my Entities using java.time.LocalDate and java.time.LocalDateTime without any further adjustments. Surprisingly – at least for me – this was not the case.

The following two sections give an overview on what I discovered and how I was able to overcome my implementation serialization issues.

Serialization on DDL Generation

The first observation I made was during the DDL generation of Hibernate. Although some of you may point out, that Hibernate’s DDL generation is by no means built to keep you production database schema up to date and there are better tools to manage your database schema changes, it is a great way to setup your test database. It is also a good indicator to see Hibernate’s default type mapping, i.e. how the object relational mapper (ORM) treats the LocalDate type.

Default Column Definiton: LocalDate to TINYBLOB

The first change I made was adjusting the existing orderDate and migrate it from java.util.Date to java.time.LocalDate. Since I wanted to reveal the mapping behavior, I did not specify a column definition in my @Column JPA annotation. After the change, my @Entity object contained an id and a date field.

In order to generate the DDL on application startup, I enabled the HibernateJpaVendorAdapter to generate the DDL. Surprisingly I found out that Hibernate treats the LocalDate field as binary object and translates it into a TINYBLOB. Having written quite a few SQL statements in my life, I can definitly confirm that date and time functions are quite common in order to extract meaningful information of your data. Since the default mapping ends up as a binary object, it becomes necessary to translate the binary object into a date object with every statement execution that requires data and time functions. Additionally, any index over a date may be wasted, since the binary-to-date translation may not operate directly on the index and will therefore not achieve the desired performance optimization.

Custom Column Definiton: LocalDate to DATETIME

Since the default mapping on DDL generation did not result in the desired DATETIME fields, it becomes necessary to add a column definition to the JPA entity. Specifying an annotation attribute columnDefintion = “DATETIME” will force Hibernate or an other ORM to use the specified database type.

The explicit definition of the column results in the the right DATETIME field in the database. However, it is worth to note that specifying columnDefinitions will bind the application code closer to the underlying RDBMS and may introduce therefore a bigger effort when migrating between different database systems. This statement is especially valid when using RDBMS specific data types in your columnDefinition section.

Data Serialization on Query Execution

Although we are now able to map the LocalDate object to the correct database type on DDL generation, this does not imply that the ORM system is already capable to serialize objects with the correct data type. To evaluate the statement execution, I’ve created a simple test case that attempts to insert an object to the database. The following snippet shows the Hibernate generated SQL statement with relevant fields.

When executing this statement, I ran into a DataIntegrityViolationException that finally pointed to an insert attempt of an incorrect DateTime object to the ORDER_DATE column. The behavior was somewhat expected, since the columnDefinition has no direct impact on the query or statement execution and does therefore not facilitate any mapping from LocalDate to Date.

In order to support JSR310 when using JPA and an underlying ORM, it is still necessary to convert from LocalDate to Date objects. If you require full control over your date conversion, you might want to consider writing your Spring @Converter yourself. Since I had less ambitious goals, I found a nice spring-data-jpa class called Jsr310JpaConverters that contained the mapping logic meeting my needs. to configure the converter, I simply added the conversion package to my entity manager package scan. The entity manager will pickup the converter class and execute the conversion back to java.util.Date so that any JSR310 DateTime object can be directly used in your @Entity.

Conclusion

The migration to Java 8 offers a lot more functionality and it is worth to consider an upgrade, without mentioning the official support and maintenance cycles of each version. However, when you consider to upgrade and adopt new features, make sure that the underlying dependencies support the features already. The spring-data-jpa and Hibernate example shows that certain components have a faster adoption rate whereas others may need more time to implement new features provided.

If you consider upgrading to Java 8, this article demonstrates some pitfalls in case you may want to apply the new DataTime features to your JPA entities. I also hope, that the article provides sufficient detail to make a decision if the new DateTime functionality adds actually a lot value to your entities, or if the conversion should be executed at a different application level. Personally, in my scenario, it was a good decision to migrate my entities, since I was able to apply the conversion class provided by spring-data.