Introduction
This article uses the old 1.2.0 version of the hibernate-types project.
The API has greatly changed ever since, so you should read this article instead.
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>1.2.0</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?
If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
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
.
Author: Vlad Mihalcea
I work as a Hibernate Developer Advocate, I wrote the High-Performance Java Persistence book and video course series. I speak at conferences and I created several open-source projects, like FlexyPool, hibernate-types, and db-util.
João Reis Jr May 4, 2018
Thank you very much, I’m looking for a solution to persist a coordinates { latitude, longitude } array field and I think that I’ve found it. wd
João Reis Jr May 4, 2018
cant find TypeReferenceFactory 🙁
Vlad Mihalcea May 4, 2018 — Post Author
That’s because the API has changed in 2.0, and this is using the 1.x API.
Check out this article for an updated version which is even simpler than the old alternative with
TypeReferenceFactory
Victor July 24, 2018
Thank you ! This is very useful topic
vladmihalcea September 3, 2018
If you liked this article, I bet you are going to love my High-Performance Java Persistence book as well.