How to store schema-less EAV (Entity-Attribute-Value) data using JSON and 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
One of my Twitter followers has recently asked me about a way of storing EAV (Entity-Attribute-Value) data using JPA and Hibernate, and, because this is a very good question, I decided to turn into a blog post.
In this article, you are going to see how you can use JSON types to store schema-less EAV data.
Domain Model
Let’s assume we have an online bookstore, and books are modeled via the Book
entity.
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 = "jsonb") private JsonNode properties; //Getters and setters omitted for brevity }
And for Hibernate 5, like this:
@Entity(name = "Book") @Table(name = "book") @TypeDef(typeClass = JsonType.class, defaultForType = JsonNode.class) public class Book { @Id @GeneratedValue private Long id; @NaturalId private String isbn; @Column(columnDefinition = "jsonb") private JsonNode properties; //Getters and setters omitted for brevity }
The Book
has a unique isbn
attribute, hence it’s mapped as a @NaturalId
.
For more details about the
@NaturalId
annotation, check out this article.
The properties
attribute is of type JsonNode
, which contains the EAV data structure.
Because Hibernate does not support JSON types natively, we need a JsonType
to handle the Jackson-specific JsonNode
object type.
Hypersistence Utils
The first thing you need to do is to set up the following Maven dependency in your project pom.xml
configuration file:
<dependency> <groupId>io.hypersistence</groupId> <artifactId>hypersistence-utils-hibernate-55</artifactId> <version>${hypersistence-utils.version}</version> </dependency>
That’s it!
Testing time
When persisting the following Book
entity:
Book book = new Book(); book.setIsbn( "978-9730228236" ); book.setProperties( JacksonUtil.toJsonNode( "{" + " \"title\": \"High-Performance Java Persistence\"," + " \"author\": \"Vlad Mihalcea\"," + " \"publisher\": \"Amazon\"," + " \"price\": 44.99" + "}" ) ); entityManager.persist( book );
Hibernate generates the proper SQL INSERT statement:
INSERT INTO book ( isbn, properties, id ) VALUES ( '978-9730228236', '{"title":"High-Performance Java Persistence","author":"Vlad Mihalcea","publisher":"Amazon","price":44.99}', 1 )
We can also fetch the Book
entity and even modify it, and Hibernate will take care of all the persistence logic:
Session session = entityManager.unwrap( Session.class ); Book book = session .bySimpleNaturalId( Book.class ) .load( "978-9730228236" ); LOGGER.info( "Book details: {}", book.getProperties() ); book.setProperties( JacksonUtil.toJsonNode( "{" + " \"title\": \"High-Performance Java Persistence\"," + " \"author\": \"Vlad Mihalcea\"," + " \"publisher\": \"Amazon\"," + " \"price\": 44.99," + " \"url\": \"https://www.amazon.com/High-Performance-Java-Persistence-Vlad-Mihalcea/dp/973022823X/\"" + "}" ) );
When running the test case above, Hibernate generates the following SQL statements:
SELECT b.id AS id1_0_ FROM book b WHERE b.isbn = '978-9730228236' SELECT b.id AS id1_0_0_ , b.isbn AS isbn2_0_0_ , b.properties AS properti3_0_0_ FROM book b WHERE b.id = 1 -- Book details: {"price":44.99,"title":"High-Performance Java Persistence","author":"Vlad Mihalcea","publisher":"Amazon"} UPDATE book SET properties = '{"title":"High-Performance Java Persistence","author":"Vlad Mihalcea","publisher":"Amazon","price":44.99,"url":"https://www.amazon.com/High-Performance-Java-Persistence-Vlad-Mihalcea/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
Hibernate types are great! Even if Hibernate comes with a great variety of types, even Java 1.8 Date/Time, you can easily map custom types like ARRAY or JSON.
For JSON, not only that you can map JSON structures as Java objects, as we saw in this article, but you benefit from schema-less structures as well.
