The way I've been working with these types of tables is to use a UserType. Unfortunately, there's no pretty solution for this out of the box that is provided with the Hibernate distribution. Check out the wiki, in particular:
http://hibernate.org/265.html
You'll really need something along these lines:
Baseclass For Lookup Values:
Code:
public abstract class Lookup
{
private int id;
private String desc;
private static final Map<Class<? extends Lookup>, Map<Integer,Lookup>> instanceIdMap = new HashMap<...>;
private static final Map<Class<? extends Lookup>, Map<String,Lookup>> instanceDescMap = new HashMap<...>;
protected static void addInstance(Class<? extends Lookup> clazz, Lookup instance)
{
if(instanceIdMap.get(clazz) == null) {
instanceIdMap.put(clazz, new HashMap<...>());
instanceDescMap.put(clazz, new HashMap<...>());
}
instanceIdMap.get(clazz).put(instance.getId(), instance);
instanceDescMap.get(clazz).put(instanceId.getDesc(), instance);
}
protected Lookup() { }
protected Lookup(int id, String desc) {
this.id = id; this.desc = desc;
addInstance(this.getClass(), this);
}
public static Lookup getInstance(Class<? extends Lookup> clazz, int id)
{
if(instanceIdMap.get(clazz) != null) {
return instanceIdMap.get(clazz).get(id);
}
else {
return null;
}
}
public static Lookup getInstance(Class<? extends Lookup> clazz, String desc)
{
if(instanceDescMap.get(clazz) != null) {
return instanceDescMap.get(clazz).get(desc);
}
else {
return null;
}
}
public static void saveOrUpdate(Class<? extends Lookup> clazz, Lookup updated)
{
Lookup instance = getInstance(clazz, updated.id);
if(instance != null) {
instance.desc = updated.desc;
}
else {
addInstance(clazz, updated);
}
}
public static void saveOrUpdate(Class<? extends Lookup> clazz, int id, String description)
{
try {
Lookup symbol = clazz.newInstance();
saveOrUpdate(clazz, symbol);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getDesc()
{
return description;
}
public void setDesc(String desc)
{
this.desc = desc;
}
public boolean equals(Object obj)
{
if(obj == null) {
return false;
}
if(!(this.getClass().isAssignableFrom(obj.getClass()))) {
return false;
}
return this.id == ((Lookup)obj).id;
}
public int hashCode()
{
return new HashCodeBuilder(17, 37).append(this.id).toHashCode();
}
}
UserType for Hibernate integration:
Code:
public class LookupUserType
implements UserType, ParameterizedType
{
private Class<? extends Lookup> lookupClass;
private String lookupClassName;
public void setParameterValues(Properties properties)
{
String lookupClassName = (String) properties.get("lookupClassName");
if(lookupClassName != null && lookupClassName.length() != 0) {
try {
lookupClass = Class.forName(lookupClassName).asSubclass(Lookup.class);
}
catch(ClassNotFoundException ex) {
throw new HibernateException(MessageFormat.format(
"Class {0} specified as lookupClassName could not be found.",
lookupClassName));
}
catch(ClassCastException ex) {
throw new HibernateException(MessageFormat.format(
"Class {0} specified as lookupClassName is not a proper subclass of {1}.",
lookupClassName, Lookup.class.getName()
));
}
}
else {
throw new HibernateException(
"Type LookupUserType is parameterized. You must specify the " +
"lookupClassName property and it must reference a class that " +
"extends Lookup.");
}
}
/** {@inheritDoc} */
public int[] sqlTypes()
{
return new int[] { Types.SMALLINT };
}
public Class returnedClass()
{
return Lookup.class;
}
public boolean equals(Object object, Object object1)
throws HibernateException
{
return object.equals(object1);
}
public int hashCode(Object object)
throws HibernateException
{
return object.hashCode();
}
public Object nullSafeGet(ResultSet resultSet, String[] strings, Object object)
throws HibernateException, SQLException
{
short id = resultSet.getShort(strings[0]);
if(Lookup.getInstance(lookupClass, id) == null) {
Lookup.saveOrUpdate(lookupClass,
id, "", "");
}
return Lookup.getInstance(lookupClass, id);
}
public void nullSafeSet(PreparedStatement ps, Object obj, int i)
throws HibernateException, SQLException
{
ps.setInt(i, obj == null ? 0 : ((Lookup)obj).getId());
}
public Object deepCopy(Object object)
throws HibernateException
{
return object;
}
public boolean isMutable()
{
return false;
}
public Serializable disassemble(Object object)
throws HibernateException
{
return (Lookup) object;
}
public Object assemble(Serializable serializable, Object object)
throws HibernateException
{
Lookup lookup = (Lookup) serializable;
return Lookup.getInstance(lookupClass, lookup.getId());
}
public Object replace(Object object, Object object1, Object object2)
throws HibernateException
{
return object;
}
public String getlookupClassName()
{
return lookupClassName;
}
public void setlookupClassName(String lookupClassName)
{
this.lookupClassName = lookupClassName;
}
}
Example of Lookup subclass:
Code:
@Entity
@Table(name="widget_status")
public class WidgetStatus
extends Symbol
{
public static final RequestStatus PENDING =
new RequestStatus(1, "pending", "Pending");
public static final RequestStatus PROCESSED =
new RequestStatus(200, "processed", "Processed");
public static final RequestStatus FAILED =
new RequestStatus(300, "failed", "Failed");
public RequestStatus()
{
super();
}
public RequestStatus(int id, String name, String description)
{
super(id, name, description);
}
public static RequestStatus getInstance(String statusName)
{
return (RequestStatus) RequestStatus
.getInstance(RequestStatus.class, statusName);
}
}
Example of class dependent on lookup subclass:
Code:
@Entity
@Table(name="widget")
@TypeDefs({
@TypeDef(
name="com.widgerz.domain.WidgetStatus",
typeClass= LookupUserType.class,
parameters={@Parameter(name="lookupClassName", value="com.widgerz.domain.WidgetStatus")})
})
public class Widget
{
...
private WidgetStatus status;
@Type(type="com.widgerz.domain.WidgetStatus")
public WidgetStatus getStatus() { return status; }
public void setStatus(WidgetStatus status) { this.status = status; }
}
I realize the sample above is kinda lame to read, but the general idea is that you are implementing a Flyweight class and then telling Hibernate how to map the key to an actual class value.
It's been working pretty well for me so far. The only downside is having to make all of your "lookup" classes extend from a common base class. But, on the plus side, your code doesn't depend on hibernate at all (except for the annotations, but I used those just so I wouldn't have to also post a config file). One key thing: declare the typedef on the class it is being used by. Declaring it on the class that the type is really managingin (Lookup subclass) doesn't seem to work vis-a-vis annotations.