Monday, December 22, 2008

JPA + Enum

Frequently in JPA domain models, entities contain "type" objects (for instance: an Employee entity may contain Gender and EmployeeType members).

The simplest Java-centric pattern is to make an Enum of the type object (e.g.: Gender) and mark the member with the @Enumerated annotation.

This is optimal so long as the list of types is more or less static (as in the Employee.Gender). If the list is more dynamic (as in, perhaps, in the Employee.EmployeeType) and especially if the list must be user modifiable, the entity member can obviously not be an Enum.

On the other hand, the general benefits -- stricter type checking, handy case statement, etc -- of Enum-inity still hold.

For this purpose, I have been using a hybrid pattern. Basically, my type entities have a referance to an enum and the enum refers back to the entity. Both have an identifying Name property. There is a special Enum.NA for those Entities not reflected in the Enum.

The way this works is I define interfaces like:


public interface LookupType extends Serializable {
public String getName();
public void setName(String s);
}

public interface LookupTypeEntity<E extends Enum> extends LookupType {
public E getEnum();
}
public interface LookupTypeEnum<E extends LookupTypeEntity<?>> extends LookupType {
public E getEntity();
}


So for example an EmployeeType might look like


@Entity
...
public class EmployeeType implements LookupTypeEntity<EmployeeTypeEnum> {
...
public EmployeeTypeEnum getEnum() {
return LookupTypeHelper.getEnum(this, EmployeeTypeEnum.class);
}
}

public enum EmployeeTypeEnum implements LookupTypeEnum<EmployeeType> {
KING, LORD, SERF, NA
;
private EmployeeType _entity;
private synchronized void initEntity() {
if (_entity == null) {
_entity = LookupTypeHelper.getEntity(this, EmployeeType.class);
}
}
public String getName() {
return name();
}
public void setName(String s) {
throw new UnsupportedOperationException();
}
public EmployeeType getEntity() {
if (_entity == null) {
initEntity();
}
return _entity;
}
}


In the helper class, I provide methods for traversing between the entity and the enum.


public static <T extends LookupTypeEntity<?>> T getEntity(LookupTypeEnum<T> e, Class<T> clazz) {
T t = null;
if ( ! NA.equals(e.getName() )) {
//Dao is a simple class I wrote that wraps a JPA Entity Manager
Dao<T> dao = ...
boolean entityExists = false;
try {
t = dao.findByName(e.getName());
entityExists = t != null;
} catch (NoResultException x) {
}
if (! entityExists ) {
logger.log(Level.INFO, clazz.getSimpleName() + " \'" + e.getName() + "\' not found in database. Creating...");
try {
t = clazz.newInstance();
t.setName( e.getName() );
t = dao.create(t);
logger.log(Level.INFO, "Created: " + t);
} catch (Exception ignore) {
logger.log(Level.WARNING,"Trying to instantiate " + clazz, ignore);
}
}
}
return t;

}

public static <E extends Enum<E>> E getEnum(LookupTypeEntity<E> t, Class<E> clazz) {
E e = Enum.valueOf(clazz, t.getName());
if (e == null) {
e = Enum.valueOf(clazz, NA);
}
return e;
}

No comments:

Post a Comment