Wednesday, 18 February 2015

Mapping Enumerated Column Values with Hibernate Annotations

If you use JPA to map table data to your entity classes using annotations then you may have come across difficulty when you want to map a Java Enum to a column. JPA offers two ways to map an enum using the EnumType enum. You have choice of mapping your enum as either ORDINAL or STRING.

With an ORDINAL type, the value persisted in the column will the ordinal integer of the enumerated value. With the STRING type, the value of enum.name() will be persisted. This can be somewhat limiting to a developer faced with an enum type which holds other declared fields. You may be working with a legacy database and the values in the column don't reflect the ordinal values or names of the enumerated type you want to use to constrain the data. In this case the JPA EnumType enum is no good to you and you may well end up having to use an unconstrained String or Integer value in the entity class. However, if your underlying JPA implementation is Hibernate then you can make use of the UserType and ParameterizedType interface in the Hibernate core package. Using these will tie your system to Hibernate, not directly as I will show, but you will need the Hibernate core as a dependency in your build.

Let's start by looking at an example situation with a database table with a column you want to map to an enum. My example uses an enum which reflects a country, this might not be something you'd use an enum for in real life, you'd probably want to read country information from a table of its own as it is something which can change. The column contains the actual country name as a VARCHAR but I want to constrain that in my code with the enum. The enum uses the two character code defined in ISO 3166-1. The enum looks like this:
The fields are defined as the country name, ISO 3166-1 3 char code, the numeric ISO 3166-1 code and a boolean flag used for something else. The database column I'm going to map this enum to hold the country name, e.g. Afghanistan, and I want to ensure I tightly constrain that to this enum when annotating the value in my entity class.

I'll start by implementing the ParameterizedType and UserType Hibernate interfaces in an abstract utility class which will allow me to specify the enum field in a concrete sub type. Here's what that class looks like:

The important function here is the setParameterValues method. this is called when the annotation we will implement is loaded and sets the type of Enum which will be instantiated when JPA maps column data. In our Country example the type will be the Country enum class itself. This abstract class is generic and can be implemented for each type of Enum class we wish to map. We now create a sub type for our Country enum which looks like this:

In my concrete sub type I state the Oracle data type that the mapper should expect, in this case our country names column is a VARCHAR. I then implement the two abstract methods which perform the get and set of the enum object based on the values in the column or the value of the enum. My Country enum has a utlity method which returns the enum for a given country name and the nullSafeGet function utilizes this. In the nullSafeSet function I simply pass the country name from the enum into the prepared statement.

Now we can use this class in our annotation in the entity like so:

And that's it. My DAO object which reads/writes the entity instances will do the rest and populate the country field with a valid Country enum. If it can't find an enum with a country name field which matches it'll just set a null. You might want to add some error handling around that depending on your use case. The getCountryByName function could throw an exception if the given name doesn't match any enum country name values. You'd then just catch that in the nullSafeGet implementation and throw one of the exceptions on that method signature.

1 comment:

  1. Top 777 Casino - Find Casinos Near you in New Jersey
    Casinos Near You in New 보령 출장샵 Jersey. Mapyro, the place 아산 출장마사지 where you'll 의정부 출장샵 find 부산광역 출장마사지 casinos that offer real money gambling in 안양 출장샵 New Jersey.

    ReplyDelete