Java Map to JSON mapping with JPA 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

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 Hypersistence Utils 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 Hypersistence Utils 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 Hypersistence Utils dependency from the Maven Central repository. For instance, if you are using Maven, then you need to add the following dependency to your project pom.xml configuration file:

<dependency>
    <groupId>io.hypersistence</groupId>
    <artifactId>hypersistence-utils-hibernate-60</artifactId>
    <version>${hypersistence-utils.version}</version>
</dependency>

For older Hibernate ORM versions, you can use the hypersistence-utils-hibernate-55 or hypersistence-utils-hibernate-52 dependencies.

The Hypersistence Utils 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.

For Hibernate 6, the mapping will look as follows:

@Entity(name = "Book")
@Table(name = "book")
public class Book {

    @Id
    @GeneratedValue
    private Long id;

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

    @Type(JsonType.class)
    @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;
    }
}

And for Hibernate 5, like this:

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

    @Id
    @GeneratedValue
    private Long id;

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

    @Type(type = "json")
    @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 JsonType, which handles JSON column types when using Oracle or PostgreSQL, SQL Server, or MySQL.

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 json 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?

I'm running an online workshop on the 20-21 and 23-24 of November about High-Performance Java Persistence.

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

Conclusion

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

Hypersistence Utils 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 Hypersistence Utils 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.