Java Map to JSON mapping with JPA and Hibernate

Imagine having a tool that can automatically detect if you are using JPA and Hibernate properly. Hypersistence Optimizer is that tool!

Introduction

In this article, we are going to see how we can store Java Map entity attributes in JSON columns when using JPA, Hibernate, and the Hibernate Types project.

While you can also persist Java Map entity attributes in PostgreSQL HStore columns, a JSON column type is a much more common option, especially since it works with other relational databases, like Oracle, SQL Server, or MySQL.

As you already have seen, the Hibernate Types project allows you to map a JSON column type to a wide variety of JPA entity attributes, like POJOs, JsonNode, collections or String Java object types:

This article shows that you can also map JSON column types to Java Map entity attributes when using JPA and Hibernate.

Maven dependency for JSON mapping with JPA and Hibernate

The first thing we need to do is add the Hibernate Types dependency from the Maven Central repository. For instance, if you are using Maven, then you need to add the following dependency into your project pom.xml configuration file:

<dependency>
    <groupId>com.vladmihalcea</groupId>
    <artifactId>hibernate-types-52</artifactId>
    <version>${hibernate-types.version}</version>
</dependency>

For older Hibernate ORM versions, you can use the hibernate-types-5, hibernate-types-43, or hibernate-types-4 dependencies. The Hibernate Types project documentation provides more details about which dependency you should use based on the Hibernate ORM version used by your project.

Domain Model

Let’s assume we have the following book table in our relational database:

PostgreSQL HStore Book database table

And, we want to map it to a Book entity whose properties attribute is of the Map<String, String> type:

Java Map to JSON entity property mapping with JPA and Hibernate

Mapping the Java Map entity property to a JSON column using JPA and Hibernate

The Book JPA entity is mapped as follows:

@Entity(name = "Book")
@Table(name = "book")
@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
public class Book {

    @Id
    @GeneratedValue
    private Long id;

    @NaturalId
    @Column(length = 15)
    private String isbn;

    @Type(type = "jsonb")
    @Column(columnDefinition = "jsonb")
    private Map<String, String> properties = new HashMap<>();

    public String getIsbn() {
        return isbn;
    }

    public Book setIsbn(String isbn) {
        this.isbn = isbn;
        return this;
    }

    public Map<String, String> getProperties() {
        return properties;
    }

    public Book setProperties(Map<String, String> properties) {
        this.properties = properties;
        return this;
    }

    public Book addProperty(String key, String value) {
        properties.put(key, value);
        return this;
    }
}

The @TypeDef annotation is used to register the JsonBinaryType, which handles JSON column types when using Oracle or PostgreSQL. For SQL Server or MySQL, we would need to use the JsonStringType instead.

The isbn property uses the @NaturalId annotation, which allows us to fetch the Book entity by its ISBN number without knowing its numerical identifier.

The properties attribute is of the type Map<String, String>, so it uses the @Type annotation to reference the jsonb type we have registered previously via the @TypeDef annotation.

The getters, setters, as well as the addProperty utility method use the Fluent-style API to simplify the way we build Book entity instances.

Testing Time

When persisting the following Book entity:

entityManager.persist(
    new Book()
        .setIsbn("978-9730228236")
        .addProperty("title", "High-Performance Java Persistence")
        .addProperty("author", "Vlad Mihalcea")
        .addProperty("publisher", "Amazon")
        .addProperty("price", "$44.95")
);

Hibernate generates the following SQL INSERT statement:

INSERT INTO book (
    isbn, 
    properties, 
    id
) 
VALUES (
    '978-9730228236', 
    {
       "author":"Vlad Mihalcea",
       "price":"$44.95",
       "publisher":"Amazon",
       "title":"High-Performance Java Persistence"
    },  
    1
)

And, when fetching the Bookentity, we can see that the properties entity attribute is properly fetched from the database:

Book book = entityManager.unwrap(Session.class)
    .bySimpleNaturalId(Book.class)
    .load("978-9730228236");

Map<String, String> bookProperties = book.getProperties();

assertEquals(
    "High-Performance Java Persistence",
    bookProperties.get("title")
);

assertEquals(
    "Vlad Mihalcea",
    bookProperties.get("author")
);

Awesome, right?

Online Workshops

If you enjoyed this article, I bet you are going to love my upcoming Online Workshops!

Conclusion

As you can see, mapping a Java Map JPA entity property to a JSON column type is very easy when using the Hibernate Types project.

Hibernate Types offers support for many non-standard column types, not just JSON. You can use it to map ARRAY, Inet, HStore, PostgreSQL-specific Enums, nullable Character columns, or use the enhanced ResultTransformer instances.

If you are using JPA and Hibernate, then you should definitely use the Hibernate Types project 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.