Persistence top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we'll take a look at Hibernate Types. This library provides us with a few types that are not native in the core Hibernate ORM.

2. Dependencies

To enable Hibernate Types we'll just add the appropriate hibernate-types dependency:

<dependency>
    <groupId>com.vladmihalcea</groupId>
    <artifactId>hibernate-types-52</artifactId>
    <version>2.9.7</version>
</dependency>

This will work with Hibernate versions 5.4, 5.3, and 5.2.

In the event that the version of Hibernate is older, the artifactId value above will be different. For versions 5.1 and 5.0, we can use hibernate-types-51. Similarly, version 4.3 requires hibernate-types-43, and versions 4.2, and 4.1 require hibernate-types-4.

The examples in this tutorial require a database. Using Docker we've provided a database container. Therefore, we'll need a working copy of Docker.

So, to run and create our database we only need to execute:

$ ./create-database.sh

3. Supported Databases

We can use our types with Oracle, SQL Server, PostgreSQL, and MySQL databases. Therefore, the mapping of types in Java to database column types will vary depending on the database we use. In our case, we will use MySQL and map the JsonBinaryType to a JSON column type.

Documentation on the supported mappings can be found over on the Hibernate Types repository.

4. Data Model

The data model for this tutorial will allow us to store information about albums and songs. An album has cover art and one or more songs. A song has an artist and length. The cover art has two image URLs and a UPC code. Finally, an artist has a name, a country, and a musical genre.

In the past, we'd have created tables to represent all the data in our model. But, now that we have types available to us we can very easily store some of the data as JSON instead.

For this tutorial, we'll only create tables for the albums and the songs:

public class Album extends BaseEntity {
    @Type(type = "json")
    @Column(columnDefinition = "json")
    private CoverArt coverArt;

    @OneToMany(fetch = FetchType.EAGER)
    private List<Song> songs;

   // other class members
}
public class Song extends BaseEntity {

    private Long length = 0L;

    @Type(type = "json")
    @Column(columnDefinition = "json")
    private Artist artist;

    // other class members
}

Using the JsonStringType we'll represent the cover art and artists as JSON columns in those tables:

public class Artist implements Serializable {
 
    private String name;
    private String country;
    private String genre;

    // other class members
}
public class CoverArt implements Serializable {

    private String frontCoverArtUrl;
    private String backCoverArtUrl;
    private String upcCode;

    // other class members
}

It's important to note that the Artist and CoverArt classes are POJOs and not entities. Furthermore, they are members of our database entity classes, defined with the @Type(type = “json”) annotation.

4.1. Storing JSON Types

We defined our album and songs models to contain members the database will store as JSON. This is due to using the provided json type. In order to have that type available for us to use we must define it using a type definition:

@TypeDefs({
  @TypeDef(name = "json", typeClass = JsonStringType.class),
  @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
})
public class BaseEntity {
  // class members
}

The @Type for JsonStringType and JsonBinaryType makes the types json and jsonb available.

The latest MySQL versions support JSON as a column type. Consequently, JDBC processes any JSON read from or any object saved to a column with either of these types as a String. This means that to map to the column correctly we must use JsonStringType in our type definition.

4.2. Hibernate

Ultimately, our types will automatically translate to SQL using JDBC and Hibernate. So, now we can create a few song objects, an album object and persist them to the database.  Subsequently, Hibernate generates the following SQL statements:

insert into song (name, artist, length, id) values ('A Happy Song', '{"name":"Superstar","country":"England","genre":"Pop"}', 240, 3);
insert into song (name, artist, length, id) values ('A Sad Song', '{"name":"Superstar","country":"England","genre":"Pop"}', 120, 4);
insert into song (name, artist, length, id) values ('A New Song', '{"name":"Newcomer","country":"Jamaica","genre":"Reggae"}', 300, 6)
insert into album (name, cover_art, id) values ('Album 0', '{"frontCoverArtUrl":"http://fakeurl-0","backCoverArtUrl":"http://fakeurl-1","upcCode":"b2b9b193-ee04-4cdc-be8f-3a276769ab5b"}', 7)

As expected, our json type Java objects are all translated by Hibernate and stored as well-formed JSON in our database.

5. Storing Generic Types

Besides supporting JSON based columns, the library also adds a few generics types: YearMonth, Year, and Month from the java.time package.

Now, we can map these types that are not natively supported by Hibernate or JPA. Also, we now have the ability to store them as an Integer, String, or Date column.

For example, let's say we want to add the recorded date of a song to our song model and store it as an Integer in our database. We can use the YearMonthIntegerType in our Song entity class definition:

@TypeDef(
  typeClass = YearMonthIntegerType.class,
  defaultForType = YearMonth.class
)
public class Song extends BaseEntity {
    @Column(
      name = "recorded_on",
      columnDefinition = "mediumint"
    )
    private YearMonth recordedOn = YearMonth.now();

    // other class members  
}

Our recordedOn property value is translated to the typeClass we provided. As a result, a pre-defined converter will persist the value in our database as an Integer.

6. Other Utility Classes

Hibernate Types has a few helper classes that further improve the developer experience when using Hibernate.

The CamelCaseToSnakeCaseNamingStrategy maps camel-cased properties in our Java classes to snake-cased columns in our database.

The ClassImportIntegrator allows simple Java DTO class name values in JPA constructor parameters.

There are also the ListResultTransformer and the MapResultTransformer classes providing cleaner implementations of the result objects used by JPA. In addition, they support the use of lambdas and provide backward compatibility with older JPA versions.

7. Conclusion

In this tutorial, we introduced the Hibernate Types Java library and the new types it adds to Hibernate and JPA. We also looked at some of the utilities and generic types provided by the library.

The implementation of the examples and code snippets are available over on GitHub.

Persistence bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
guest
0 Comments
Inline Feedbacks
View all comments