Sunday, January 6, 2013

JPA alternatives to Hibernate UserTypes


A couple years ago, I started a side project that was a great way to get more experience with Hibernate and some of its advanced features. I want to put my webapp out there for the world to use but since I don't expect it to make much money (if any,) I decided that Google App Engine (GAE) would be a great place to host it. I can host my app for free and if the site catches on, I can pay the fees and re-examine hosting options. However, to access their data storage from Java, you need to use either JDO or JPA. Since JPA is the newer of the two and was supposedly modelled after Hibernate, it was the obvious choice. I started porting my app to JPA using Hibernate as my implementation and I plan on migrating the whole app to GAE (and its JPA implementation, DataNucleus) at a later date.

My experience converting from Hibernate to JPA was fairly smooth but the biggest shortcoming was JPA's lack of a UserType equivalent. UserTypes allow developers to persist values that are of a type the framework doesn't handle natively. I was using them to handle enums and Joda Time objects. For example, suppose I have a table where I store user information. One piece of information I might want to store is a user's gender. In Java, I would represent that with an enumeration like this:

public enum Gender {
  MALE('M'),
  FEMALE('F');

  private Character code;
  private static final Map<Character,Gender> valuesByCode;
 
  static {
    valuesByCode = new HashMap<Character,Gender>();
    for(Gender gender : values()) {
      valuesByCode.put(gender.code, gender);
    }
  }

  private Gender(Character code) {
    this.code = code;
  }

  public static Gender lookupByCode(Character code) { 
    return valuesByCode.get(code); 
  }

  public Character getCode() {
    return code;
  }
}

The 'code' field stores the value I want to use in the database. Since I'm not relying on an enum's 'name' field (which is automatically determined by the value's name in code - MALE and FEMALE in this case,) my Java and SQL naming conventions don't need to match up and I don't have to worry about breaking my app with basic refactoring. In Hibernate, I would have written a UserType to handle getting an enum's 'code' and looking up a enum by its code. But in JPA, I had to find an alternative.

One approach I tried was to leverage JPA's @Pre/PostUpdate, @Pre/PostPersist and @PostLoad annotations to populate, update and clear a JPA-friendly field I added where needed.

private Gender gender;
private String genderCode;

@PrePersist
@PreUpdate
public void populatePersistanceFields() {
  genderCode = gender==null? null : gender.getCode();
}

@PostPersist
@PostUpdate
public void cleanupPersistanceFields() {
  genderCode = null;
}

@PostLoad
public void updateFromPersistanceFields() {
  gender = ConcentrationUnit.lookupByCode(genderCode);
  cleanupPersistanceFields();
}

When I first tested my code, I was happy to find that an entity could in fact be persisted with the value that had been populated by my methods. However, what I quickly realized is that my JPA implementation (Hibernate) wasn't recognizing changes made to existing entities. So that approach wouldn't work.

So what I settled on instead was to create an additional getter and setter for fields that need to be converted. So far, using Hibernate, this approach has worked well.

@Entity @Table(name="person")
@Access(AccessType.FIELD)
public class Person {
  @Transient
  private Gender gender;

  @Transient
  public Gender getGender() {
    return gender;
  }
  public void setGender(Gender gender) {
    this.gender = gender;
  }

  @Column(name="gender", nullable=false) @Access(AccessType.PROPERTY)
  protected Character getGenderCode() {
    return gender==null? null : gender.getCode();
  }
  protected void setGenderCode(Character genderCode) {
    gender = Gender.lookupByCode(genderCode);
  }
}

This example mixes FIELD and PROPERTY access, which isn't necessarily recommended, but I prefer field access and I'm considering this a special case. Hibernate allows it so unless DataNucleus doesn't, I'm going to stick with it to prevent having annotations scattered through my model classes.

It's not elegant by any means but it seems like an acceptable solution to a major shortcoming in JPA. I'm curious what other "pure JPA" (ie: not using implementation-specific features like Hibernate's @Type annotation) solutions people have found.