How to map a JSON collection using 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

The open-source hibernate-types project allows you to map Java objects or Jackson JsonNode as JPA or Hibernate entity properties, and, thanks to our awesome contributors, we have added support for storing type-safe JSON collections.

In this article, you are going to see how to achieve this goal.

Maven dependency

First of all, you need 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>

If you’re using older versions of Hibernate, check out the hibernate-types GitHub repository for more info about the matching dependency for your current Hibernate version.

Domain Model

Let’s assume we have the following Location Java object type.

public class Location implements Serializable {

    private String country;

    private String city;

    //Getters and setters omitted for brevity

    @Override
    public String toString() {
        return "Location{" +
                "country='" + country + ''' +
                ", city='" + city + ''' +
                '}';
    }
}

And, one Event entity:

@Entity(name = "Event")
@Table(name = "event")
public class Event extends BaseEntity {

    @Type(type = "jsonb")
    @Column(columnDefinition = "jsonb")
    private Location location;

    @Type(type = "jsonb")
    @Column(columnDefinition = "jsonb")
    private List<Location> alternativeLocations = new ArrayList<Location>();

    //Getters and setters omitted for brevity
}

The BaseEntity defines some basic properties (e.g. @Id, @Version) and several custom Hibernate types, among which, we are interested in the JsonBinaryType one.

@TypeDefs({
    @TypeDef(name = "string-array", typeClass = StringArrayType.class),
    @TypeDef(name = "int-array", typeClass = IntArrayType.class),
    @TypeDef(name = "json", typeClass = JsonStringType.class),
    @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class),
    @TypeDef(name = "jsonb-node", typeClass = JsonNodeBinaryType.class),
    @TypeDef(name = "json-node", typeClass = JsonNodeStringType.class),
})
@MappedSuperclass
public class BaseEntity {

    @Id
    private Long id;

    @Version
    private Integer version;

    //Getters and setters omitted for brevity
}

For more details about using @MappedSuperclass, check out this article.

To store both the Location object or the List<Location> in a jsonb PostgreSQL column, we just need to annotate the location property with @Type(type = "jsonb").

That’s it!

Testing time

When saving the following Event entity:

Location cluj = new Location();
cluj.setCountry("Romania");
cluj.setCity("Cluj-Napoca");

Location newYork = new Location();
newYork.setCountry("US");
newYork.setCity("New-York");

Location london = new Location();
london.setCountry("UK");
london.setCity("London");

Event event = new Event();
event.setId(1L);
event.setLocation(cluj);
event.setAlternativeLocations(
    Arrays.asList(newYork, london)
);

entityManager.persist(event);

Hibernate will generate the following SQL INSERT statement:

INSERT INTO event (
    version, 
    alternativeLocations, 
    location, 
    id
) 
VALUES (
    0, 
    [
        {"country":"US","city":"New-York"},
        {"country":"UK","city":"London"}
    ], 
    {"country":"Romania","city":"Cluj-Napoca"}, 
    1
)

Also, when retrieving back the Event entity, both the location and thealternativeLocations` properties are properly fetched:

Event event = entityManager.find(Event.class, eventId);

assertEquals(
    "Cluj-Napoca", 
    event.getLocation().getCity()
);

assertEquals(2, event.getAlternativeLocations().size());

assertEquals(
    "New-York", 
    event.getAlternativeLocations().get(0).getCity()
);
assertEquals(
    "London", 
    event.getAlternativeLocations().get(1).getCity()
);

Cool, right?

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

Seize the deal! 50% discount. Seize the deal! 50% discount.

Conclusion

The hibernate-types project supports more than JSON types. You can map PostgreSQL ARRAY types or PostgreSQL-specific Enums, nullable Character, or even provide your own immutable Hibernate custom Types.

FREE EBOOK

25 Comments on “How to map a JSON collection using JPA and Hibernate

  1. TelephoneNumberType is my own type similar to EmailAddressType.

    • I also have no idea what EmailAddressType is. Anyway, I could only helped you if you were using the Types provided by my hibernate-types project, which is also the subject of the article your are now commenting on.

  2. I’ve tried this, i’m currently using hibernate 5.3.9 with hibernate-types-52 2.5.0 and i’m getting ” org.postgresql.util.PSQLException: ERROR: malformed array literal: “[{“id”:”Mycah”,”name”:”Sven Goldner”}]””
    Anyone faced this before ? thanks

    • Try to replicate it and provide a fix for it via a Pull Request. I’ll review it once it’s done.

  3. Hi Vlad,
    Some questions.
    Is it possible to use @OneToMany instead of @Column for List alternativeLocations?
    Can I write HQL query for fields within the Locations? Say pull all events held in specific city?

    • The answer is no and no. Associations and entity queries are one thing while basic properties and DB-specific querying capabilities are another thing.

    • It’s a DTO that you might want to transfer between systems, hence it’s good practice to make it Serializable. But it’s not mandatory.

  4. Hi Vlad,

    It works great and thanks for starting this initiative.
    Is it possible to do away with hibernate specific annotations and use only JPA specific ones instead? Thanks!

    • No, that’s not possible. But why would you do that? Did you ever heard of anyone switching from Hibernate to some other JPA provider? I personally never heard of it.

      • I agree to some extent, but then why does JPA exists ? 🙂

      • For the same reason the SQL standard exists. However, that does not prevent you from using database-specific query features when dealing with performance issues.

  5. Hi vlad,

    I think there might be an issue when using this with envers. When i retrieve my entity using the entitymanager it maps the jsonb list to an ArrayList of the generic type. But when i retrieve my audit entity by using the AuditReader class (created from the same entitymanager), my jsonb list suddenly becomes an ArrayList.

    The only cause for this i can think of is that the name of the versions entity is the same as the name of the entity being audited, but it ends with _AUD.

    This issue occurs for met with hibernate 5.2 and version 2.3.5 of hibernate-types-52

    • The hibernate-types framework is designed for Hibernate ORM only. If it does not work with Envers, then a Jira issue has to be created for the Hibernate Envers project.

      • I looked into this a bit deeper, and this worked with version 1.2.0, where I was able to define a type reference factory class using the parameter “com.vladmihalcea.hibernate.type.json.TypeReferenceFactory.class”.

        For envers it seems that the “org.hibernate.type.ParameterType.xproperty” parameter is not passed to the JsonTypeDescriptor setParameterValues method. I was wondering if this was something that I could work around, or if this is a bug in envers that should be reported and fixed?

      • The TypeReferenceFactory was too complicated for generic usage, so version 2.x of the hibernate-types project simplified that via XProperty. You need to investigate it further and see why it does not work, and provide a Pull Request with a fix. Looking forward to your contribution.

  6. Using same above flow i created a project,

    Its giving me this error:
    “java.util.ArrayList cannot be cast to com.fasterxml.jackson.databind.JsonNode”

    for this column: private List alternativeLocations = new ArrayList()

    on save method call.

    Anything which i may be missing ? (or done wrong)

  7. Ahh yes, can’t post XML. Anyways, I added “org.hibernate.commons-annotations” as a module dependency to jboss-deployment-structure.xml

  8. Nevermind! I was able to solve the problem by changing my jboss-deployment-structure.xml to the following:

  9. What exactly did you change in your EAR pom.xml? I’m having this exact same problem with Wildfly.

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.