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. Hypersistence Optimizer is that tool!
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.
Kudos to @vlad_mihalcea, the Hibernate Types library saved me tons of work yesterday by automagically mapping the PostgreSQL jsonb column to a POJO. Brilliant work 💪
— Tomasz Knyziak (@TomaszKnyziak) September 18, 2019
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 book store, and books are modeled via the Book
entity:
@Entity(name = "Book") @Table(name = "book") @TypeDef( typeClass = JsonBinaryType.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 JsonBinaryType
to handle the Jackson-specific JsonNode
object type.
Hibernate Types
The first thing you need to do is to set up the following Maven dependency in your project pom.xml
configuration file:
<dependency> <groupId>com.vladmihalcea</groupId> <artifactId>hibernate-types-52</artifactId> <version>${hibernate-types.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 a previous article, but you benefit from schema-less structures as well.

Hello, is it possble to use your hibernate-types lib to map postgres jsonb to Map<String, String> field ? I am trying to figur out how to do so.
Yes, of course. Check out this article for more details.
But that article describes hstore data type not the jsonb. How does one could map Map<String,String> to jsonb? What should I specify instead of PostgreSQLHStoreType.class?
I haven’t tested that scenario with jsonb, so you will need to debug it and see what’s needed in order to work.