In some cases, it may be useful to map a collection of enums to a single column in the database table. This can be achieved using a custom Hibernate type, which will map a collection of enums to a varchar type.

Keep in mind that this idea is probably only good if there are only a few items in a collection, their number is limited and enum fields are rarely or never going to change.

For example, we may have an enum which represents the user’s permissions:

public enum Permission {
    CREATE, EDIT, DELETE, BLOCK, MUTE
}

Instead of using a List or some other collection type, Java has a specialized Set implementation for use with enum types, which is much more efficient – EnumSet.

First, we need to implement a descriptor that represents a Java type, here EnumSet, and does the conversion from EnumSet to a String (unwrap method) and from a String (wrap method). We’ll use enum’s ordinals and create a comma-separated String.

public class EnumSetTypeDescriptor extends AbstractTypeDescriptor<EnumSet> {

	private static final String SEPARATOR = ",";

	private Object[] enumConstants;

	protected EnumSetTypeDescriptor(Class enumClass) {
		super(EnumSet.class);
		this.enumConstants = enumClass.getEnumConstants();
	}

	@Override
	public String toString(EnumSet value) {
		return (String) value.stream()
				.map(Enum.class::cast)
				.map(v -> Integer.toString(((Enum) v).ordinal()))
				.collect(Collectors.joining(SEPARATOR));
	}

	@Override
	public EnumSet fromString(String string) {
		if (StringUtils.isEmpty(string))
			return null;

		List<Enum> list = Arrays.stream(StringUtils.split(string, SEPARATOR))
				.map(ordinal -> enumConstants[Integer.parseInt(ordinal)])
				.map(Enum.class::cast)
				.collect(Collectors.toList());

		if (list.isEmpty())
			return null;

		return EnumSet.copyOf(list);
	}

	@Override
	public int extractHashCode(EnumSet value) {
		return Objects.hashCode(value);
	}

	@Override
	public <X> X unwrap(EnumSet value, Class<X> type, WrapperOptions options) {
		if (value == null)
			return null;

		if (EnumSet.class.isAssignableFrom(type))
			return (X) value;

		if (String.class.isAssignableFrom(type))
			return (X) toString(value);

		throw unknownUnwrap(type);
	}

	@Override
	public <X> EnumSet wrap(X value, WrapperOptions options) {
		if (value == null)
			return null;

		if (EnumSet.class.isInstance(value))
			return (EnumSet) value;

		if (String.class.isInstance(value))
			return fromString((String) value);

		throw unknownWrap(value.getClass());
	}
}

StringUtils is a class from Apache Commons Lang library.

Next, we need to implement a BasicType, and since we would be using a single column to store the value, there is a specific implementation – AbstractSingleColumnStandardBasicType.
Also, we would like from this Hibernate custom type to support any enum type, so we need to create a parameterized type by also implementing DynamicParameterizedType.

public class EnumSetType extends AbstractSingleColumnStandardBasicType<EnumSet> implements DynamicParameterizedType {

	public EnumSetType() {
		super(VarcharTypeDescriptor.INSTANCE, null);
	}

	@Override
	public String getName() {
		return "enum-set";
	}

	@Override
	public String[] getRegistrationKeys() {
		return new String[] { getName(), "EnumSet", EnumSet.class.getName() };
	}

	@Override
	public void setParameterValues(Properties parameters) {
		String enumClassName = (String) parameters.get("enumClass");

		try {
			setJavaTypeDescriptor(new EnumSetTypeDescriptor(Class.forName(enumClassName)));
		}
		catch (ClassNotFoundException e) {}
	}
}

To make use of this custom type, we must declare it in an entity using the @TypeDef annotation, annotate the field with a @Type annotation and pass the fully qualified enum class name as a parameter.

@Entity
@Table(name = "user")
@TypeDef(typeClass = EnumSetType.class)
public class User {

    // ...

    @Column(length = 10)
    @Type(type = "enum-set", parameters = {
        @org.hibernate.annotations.Parameter(name = "enumClass", value = "xyz.joshefin.example.domain.constant.Permission")
    })
    private EnumSet<Permission> permissions;

    // ...

}

For some other custom types, check out a great library Hibernate Types created by Vlad Mihalcea.

Leave a Reply

Your email address will not be published. Required fields are marked *