Mapping Java Records to JSON columns using Hibernate
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, I’m going to explain how we can map Java Records to JSON columns when using Hibernate.
Because Hibernate ORM does not offer built-in support for JSON, we are going to use the Hypersistence Utils library, which allows you to map entity attributes to JSON column types, no matter if you are using Oracle, SQL Server, PostgreSQL, or MySQL.
Java Records
As I explained in this article, Java Records are a great way to build structured data types.
In our case, we want to encapsulate the book info in the following Book
data structure:
To create the above Book
structure, we can use the following Java Record definition:
public record BookRecord ( String title, String author, String publisher, Long priceInCents, URL url ) {}
How to serialize Java Records to JSON using Jackson
As I explained in this article, Java Records cannot be used as JPA or Hibernate entities, but you can use them as basic attributes:
Because Hibernate ORM does not have built-in support for Java Records, we need to provide a custom Hibernate Type to map the Java Records to the proper database column type, and one option is to persist the Java Record in a JSON column.
To make sure that the Java Records is properly marshaled to a JSON object and unmarshaled back to a Java Record, we need to change the BookRecord
, like this:
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) public record BookRecord ( String title, String author, String publisher, Long priceInCents, URL url ) implements Serializable { @JsonCreator public BookRecord( @JsonProperty("title") String title, @JsonProperty("author") String author, @JsonProperty("publisher") String publisher, @JsonProperty("priceInCents") String priceInCents, @JsonProperty("url") String url) { this( title, author, publisher, longValue(priceInCents), urlValue(url) ); } }
The following changes are required to be done in order to properly transform the Java Record to a JSON object using Jackson:
- the
@JsonAutoDetect
annotation allows Jackson to access the private fields of the Java Record when serializing or deserializing it from its String representation that’s sent to the database - the
Serializable
interface is needed because, according to the JPA specification, every entity attribute must be serializable using the Java serialization mechanism. - the
@JsonCreator
annotation was added to mark that the extra constructor can be called by Jackson to pass the Strong-based JSON attribute in order to initialize the Java Record fields.
How to map Java Records to JSON columns using Hibernate
The Hypersistence Utils framework has support for Oracle, SQL Server, PostgreSQL, and MySQL, so depending on the database engine and the associated JSON column type, you can use one of the following options.
For Oracle, SQL Server, MySQL, PostgreSQL, and H2, you can use the
JsonType
from the Hypersistence Utils project to map Java Records to JSON columns.
In case you’re using MySQL, then you can use the dedicated JSON
column type to store the properties
attributes, while for PostgreSQL, you need to use the jsonb
column type.
So, the Book
entity would be mapped as follows.
For Hibernate 6, the mapping will look as follows:
@Entity(name = "Book") @Table(name = "book") public class Book { @Id @GeneratedValue private Long id; @NaturalId private String ISBN; @Type(JsonType.class) @Column(columnDefinition = "JSON") private BookRecord properties; //Getters and setters omitted for brevity }
And for Hibernate 5, like this:
@Entity(name = "Book") @Table(name = "book") @TypeDef(typeClass = JsonType.class, defaultForType = BookRecord.class) public class Book { @Id @GeneratedValue private Long id; @NaturalId private String isbn; @Column(columnDefinition = "JSON") private BookRecord properties; //Getters and setters omitted for brevity }
Persisting Java Records to JSON columns using Hibernate
When persisting the following Book
entity:
entityManager.persist( new Book() .setIsbn("978-9730228236") .setProperties( new BookRecord( "High-Performance Java Persistence", "Vlad Mihalcea", "Amazon", 4499L, null ) ) );
Hibernate generates the following INSERT statement:
INSERT INTO book ( isbn, properties, id ) VALUES ( '978-9730228236', { "title":"High-Performance Java Persistence", "author":"Vlad Mihalcea", "publisher":"Amazon", "priceInCents":4499, "url":null }, 1 )
Fetching Java Records to JSON columns using Hibernate
When fetching the Book
entity, we can see that we get the expected BookRecord
:
Book book = entityManager .unwrap(Session.class) .bySimpleNaturalId(Book.class) .load("978-9730228236"); BookRecord bookRecord = book.getProperties(); assertEquals( "High-Performance Java Persistence", bookRecord.title() ); assertEquals( "Vlad Mihalcea", bookRecord.author() );
Updating Java Records
Java Records are immutable, so when we want to change the properties
attribute, we have to create a new BookRecord
instance, like this:
book.setProperties( new BookRecord( bookRecord.title(), bookRecord.author(), bookRecord.publisher(), bookRecord.priceInCents(), urlValue("https://www.amazon.com/dp/973022823X/") ) );
When the Persistence Context is flushed, Hibernate generates the following UPDATE statement:
UPDATE book SET properties = { "title":"High-Performance Java Persistence", "author":"Vlad Mihalcea", "publisher":"Amazon", "priceInCents":4499, "url":"https://www.amazon.com/dp/973022823X/" } WHERE id = 1
Cool, right?
If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
Conclusion
As you can see, Java Records are very convenient to use when you want to structure your data using immutable objects that are compact to write and flexible to customize. By adding the Jackson annotations and the extra constructor, we were able to serialize the Java Record to a JSON object when persisting the Book
entity to the database and deserialize the JSON column type back to its Java Record representation.
If you want to map an entity attribute as a Java Record and persist it to a JSON column, then the Hypersistence Utils project will surely allow you to achieve your goal.
