- Wiegand.DK - https://www.wiegand.dk/wordpress -

What to do about enums in Hibernate/JPA/JPA2?

What to do about enums in Hibernate/JPA/JPA2?

To ensure the integrity in data model and domain model enumerations are a necessity!

This is my statement and let me try to explain why I think it is important and what my solution to the problem is.

In the data model even that it is not read by the domain model it is a good idea to keep the data normalized. When this is said I must in the same sentence state than I am not into deep normalizing where data in the end is more obscure than useable – meaning not accessible. The most common example is that you have an entry table and for each entry you want a status or state. This could be a transaction table with a status that states if the transaction is new or processed. Instead of just adding the status as an integer [1, 2] or string [new, processed] is it good to have the relationship as a foreign key to a status table with to entries – [1, New] and [2, Processed]. With this construction you ensure the data integrity on the database level but this is NOT a good way to handle the data in the domain model.

In the domain model the data is not only used to ensure the structure but to decide how to handle the data. For every state/status there will be some code that will act according to the current status and process the data correctly. The state will in the domain model be a static list where only the defined states will matter. All other states will properly be handled as errors or just get the code to fail in obscure ways. A good way to handle this in the domain model is to implement the state as an enumeration.

The magic is now somehow to join the better of the two worlds – data model vs. domain model.

The data in the database is “dynamic” but is good to keep the structure and to ensure the integrity. But is doesn’t make sense to have to load the “static” data into the domain model. The normal CRUD operations do not apply to the state table and the best way to represent the constants is to group them as an enumeration. This suggests not using JPA to handle this mapping directly.

But how can JPA indirectly load the data model into the domain model? The way that Hibernate works is ether to map behaviour to an attribute or a method. If the behaviour is mapped to the attribute you can freely control what the getter and setter should do. This means that you can do your mapping in the getter and setter or rather the translation between an id and the enumeration.

The strait forward getter and setter based on the transaction example:

@Column(name=”TRANSACTION_STATUS_ID”) 
private int transactionStatusId;

public int getTransactionStatusId() {
  return this.transactionStatusId;
}

public void setTransactionStatusId(int transactionStatusId) {
  this.transactionStatusId = transactionStatusId;
}

And now with the mapping to the enumerator:

@Column(name=”TRANSACTION_STATUS_ID”) 
private int transactionStatusId;

public TransactionStatus getTransactionStatus () {
  return TransactionStatus.parse(this.transactionStatusId);
}

public void setTransactionStatus(TransactionStatus transactionStatus) {
  this.transactionStatusId = transactionStatus.getId();
}

The interesting part is now the two functions implemented in the enumerator – parse and getId.

public enum TransactionStatus {
  NEW(1),
  PROCESSED(2);

  TransactionStatus(int id) {
    this.id = id;
  }

  private int id;

  public int getId() {
    return this.id;
  }

  public static TransactionStatus parse(int id) {
    for (TransactionStatus transactionStatusTemp: TransactionStatus.values()) {
      if (transactionStatusTemp.getId()==id) {
        return transactionStatusTemp = transactionStatus;
      }
    }
    return null; // Default;
  }
}

In this example no default status is set and the parse function will return null if no match on id is found. If no match is found it would in some cases be smart to throw a run time exception to avoid a null pointer exception later. Another thing to ease debugging is to log if no match is found.

The problem with this solution is that you must by hand maintain the database and the code separately. The database can of cause come out of sync with the code nut you can help on this part with use of exceptions and logging – there are not that many fail scenarios.

I have used this approach with success on two projects until now and it has been easy to maintain over time.

Updated: 15. January 2014
I am still using the same approach today and this is definitely the way to handle enums in JPA/JPA2.  It is easy to understand, easy to implement and most of all easy to maintain.

Updated: 31. July 2015
For some time a new approach for handling enums have been possible if you are aloud to upgrate to JPA 2.1. In JPA 2.1 attribute converters have been introduced. A nice thing that solves the issue with the translation of the enum in the getter and setter. You still need the static parse function at the enum.

A converter for the TransactionStatus above will look like this:

public class TransactionStatusConverter implements AttributeConverter<TransactionStatus, Integer> {

  @Override
  public Integer convertToDatabaseColumn(TransactionStatus attribute) {
    return attribute.getId();
  }

  @Override
  public TransactionStatus convertToEntityAttribute(Integer dbData) {
    return TransactionStatus.parse(dbData);
  }

}

Then you can do the entity mapping like this:

@Convert(converter = TransactionStatusConverter.class)
@Column(name=”TRANSACTION_STATUS_ID”) 
private TransactionStatus transactionStatus;

public TransactionStatus getTransactionStatus () {
  return transactionStatus;
}

public void setTransactionStatus(TransactionStatus transactionStatus) {
  this.transactionStatus = transactionStatus;
}

A neat and tight solution – use it.

And remember …
Don’t use EnumType.ORDINAL (can’t delete or reorder) or EnumType.STRING (can’t do renaming) with the Enumerated annotation. You will break the refactoring process and it will give you pain in the long run.