How to map JSON collections using JPA and Hibernate
Introduction
The open-source hibernate-types
project allows you to map Java objects or Jackson JsonNode
as JPA entity properties.
Recently, thanks to our awesome contributors, we added support for type-safe collections to be persisted as JSON as well. 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", parameters = { @org.hibernate.annotations.Parameter( name = TypeReferenceFactory.FACTORY_CLASS, value = "com.vladmihalcea.hibernate.type.json.PostgreSQLGenericJsonBinaryTypeTest$AlternativeLocationsTypeReference" ) } ) @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 customs 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.
TypeReferenceFactory
To store the Location
object in a jsonb
PostgreSQL column, we just need to annotate the location
property with @Type(type = "jsonb")
.
However, for the alternativeLocations
collection, we need to provide the associated Jackson TypeReference
so that we can reconstruct the very same type-safe Java collection when reading the JSON object from the relational database.
For this purpose, we provide the fully-qualified class of the TypeReferenceFactory
implementation which looks as follows:
public static class AlternativeLocationsTypeReference implements TypeReferenceFactory { @Override public TypeReference<?> newTypeReference() { return new TypeReference<List<Location>>() {}; } }
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 the
alternativeLocations` 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?
Published on Java Code Geeks with permission by Attila Mihaly Balazs, partner at our JCG program. See the original article here: How to map JSON collections using JPA and Hibernate Opinions expressed by Java Code Geeks contributors are their own. |