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!


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 book store, and books are modeled via the Book entity:

@Entity(name = "Book")
@Table(name = "book")
    typeClass = JsonType.class, 
    defaultForType = JsonNode.class
public class Book {

    private Long id;

    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.

Hibernate Types

The first thing you need to do is to set up the following Maven dependency in your project pom.xml configuration file:


That’s it!

Testing time

When persisting the following Book entity:

Book book = new Book();
book.setIsbn( "978-9730228236" );
        "{" +
        "   \"title\": \"High-Performance Java Persistence\"," +
        "   \"author\": \"Vlad Mihalcea\"," +
        "   \"publisher\": \"Amazon\"," +
        "   \"price\": 44.99" +

entityManager.persist( book );

Hibernate generates the proper SQL INSERT statement:

    '{"title":"High-Performance Java Persistence","author":"Vlad Mihalcea","publisher":"Amazon","price":44.99}',  

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" ); "Book details: {}", book.getProperties() );

        "{" +
        "   \"title\": \"High-Performance Java Persistence\"," +
        "   \"author\": \"Vlad Mihalcea\"," +
        "   \"publisher\": \"Amazon\"," +
        "   \"price\": 44.99," +
        "   \"url\": \"\"" +

When running the test case above, Hibernate generates the following SQL statements:

SELECT AS id1_0_
FROM    book b
WHERE   b.isbn = '978-9730228236'

SELECT AS id1_0_0_ ,
        b.isbn AS isbn2_0_0_ , AS properti3_0_0_
FROM    book b

-- Book details: {"price":44.99,"title":"High-Performance Java Persistence","author":"Vlad Mihalcea","publisher":"Amazon"}

    properties = '{"title":"High-Performance Java Persistence","author":"Vlad Mihalcea","publisher":"Amazon","price":44.99,"url":""}' 
    id = 1

Cool, right?

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.


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.

Transactions and Concurrency Control eBook

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.