Herausforderung#

Der Object Relational Mapper Hibernate bietet das Mapping von Enums an. Eine praktische Einrichtung unter Java sind EnumSets, die nicht nur eine Konstante eines Enums enthalten können, sondern eine beliebige Anzahl davon.
public class User {
    private EnumSet<Role> 
    
    public EnumSet<Role> getRoles() {
	return roles;
    }
    public void setRoles(EnumSet<Role> roles) {
	this.roles = roles;
    }
}

Diese EnumSets können mit Hibernate nicht unmittelbar persistent gemacht werden. Die mögliche Abbildung unter Hibernate bestünde in einer weiteren Tabelle, dies erscheint aber zu kompliziert.

Lösung#

Die Intersult hat einen UserType entwickelt, der das EnumSet als Liste der Enum-Konstanten in einem String-Datentyp der Datenbank ablegt. Die Enum-Konstanten werden dabei durch Kommata voneinander getrennt.

Zunächst die Verwendung des EnumSetType:

public class User {
    private EnumSet<Role> 
    
    @Type(type = "com.intersult.core.persist.EnumSetType",
		parameters = @Parameter(name = EnumSetType.TYPE, value = "com.intersult.model.user.Role"))
    @Basic
    public EnumSet<Role> getRoles() {
	return roles;
    }
    public void setRoles(EnumSet<Role> roles) {
	this.roles = roles;
    }
}

Anschließend nun die Implementierung des EnumSetType als Hibernate UserType:

public class EnumSetType<Type extends Enum<Type>> implements UserType, ParameterizedType {
	public static final String TYPE = "type";
	private static final int[] SQL_TYPES = {Types.VARCHAR};
	private static final Pattern SEPARATOR = Pattern.compile(",");
	private Class<Type> type;

	public int[] sqlTypes() {
		return SQL_TYPES;
	}
	public Class<?> returnedClass() {
		return type;
	}
	public boolean equals(Object x, Object y) {
		if (x == null)
			return y == null;
		return x.equals(y);
	}
	public Object deepCopy(Object value) {
		return ((EnumSet<Type>)value).clone();
	}
	public boolean isMutable() {
		return true;
	}
	public Object nullSafeGet(ResultSet resultSet, String[] names, Object owner) throws HibernateException,
			SQLException {
		String name = resultSet.getString(names[0]);
		if (resultSet.wasNull())
			return null;
		EnumSet<Type> enumSet = EnumSet.noneOf(type);
		if (StringUtils.isNotEmpty(name)) {
			String[] enumNames = SEPARATOR.split(name);
			for (String enumName : enumNames) {
				enumSet.add(Enum.valueOf(type, enumName));
			}
		}
		return enumSet;
	}
	public void nullSafeSet(PreparedStatement statement, Object value, int index) throws HibernateException,
			SQLException {
		if (value == null) {
			statement.setNull(index, Types.VARCHAR);
		} else {
			StringBuilder buffer = new StringBuilder();
			for (Enum<?> enumValue : (EnumSet<?>)value) {
			        if (buffer.length() > 0)
			                buffer.append(",");
				buffer.append(enumValue.name());
			}
			statement.setString(index, buffer.toString());
		}
	}
    
	public Object assemble(Serializable cached, Object owner) throws HibernateException {
		return cached == null ? null : EnumSet.copyOf((EnumSet<?>)cached);
	}
	public Serializable disassemble(Object value) throws HibernateException {
		return value == null ? null : EnumSet.copyOf((EnumSet<?>)value);
	}
	public int hashCode(Object x) throws HibernateException {
		return x.hashCode();
	}
	public Object replace(Object original, Object target, Object owner) throws HibernateException {
		return original;
	}
	public void setParameterValues(Properties parameters) {
		String typeName = parameters.getProperty(TYPE);
		try {
			type = (Class<Type>)Class.forName(typeName);
		} catch (ClassNotFoundException exception) {
			throw new IllegalArgumentException(exception);
		}
	}
}