I'm using a slight modification of java.util.RegularEnumSet to have a persistent EnumSet:
@MappedSuperclass
@Access(AccessType.FIELD)
public class PersistentEnumSet<E extends Enum<E>>
extends AbstractSet<E> {
private long elements;
@Transient
private final Class<E> elementType;
@Transient
private final E[] universe;
public PersistentEnumSet(final Class<E> elementType) {
this.elementType = elementType;
try {
this.universe = (E[]) elementType.getMethod("values").invoke(null);
} catch (final ReflectiveOperationException e) {
throw new IllegalArgumentException("Not an enum type: " + elementType, e);
}
if (this.universe.length > 64) {
throw new IllegalArgumentException("More than 64 enum elements are not allowed");
}
}
// Copy everything else from java.util.RegularEnumSet
// ...
}
This class is now the base for all of my enum sets:
@Embeddable
public class InterestsSet extends PersistentEnumSet<InterestsEnum> {
public InterestsSet() {
super(InterestsEnum.class);
}
}
And that set I can use in my entity:
@Entity
public class MyEntity {
// ...
@Embedded
@AttributeOverride(name="elements", column=@Column(name="interests"))
private InterestsSet interests = new InterestsSet();
}
Advantages:
- Working with a type safe and performant enum set in your code (see
java.util.EnumSet
for a description)
- The set is just one numeric column in the database
- everything is plain JPA (no provider specific custom types)
- easy (and short) declaration of new fields of the same type, compared with the other solutions
Drawbacks:
- Code duplication (
RegularEnumSet
and PersistentEnumSet
are nearly the same)
- You could wrap the result of
EnumSet.noneOf(enumType)
in your PersistenEnumSet
, declare AccessType.PROPERTY
and provide two access methods which use reflection to read and write the elements
field
- An additional set class is needed for every enum class that should be stored in a persistent set
- If your persistence provider supports embeddables without a public constructor, you could add
@Embeddable
to PersistentEnumSet
and drop the
extra class (... interests = new PersistentEnumSet<>(InterestsEnum.class);
)
- You must use an
@AttributeOverride
, as given in my example, if you've got more than one PersistentEnumSet
in your entity (otherwise both would be stored to the same column "elements")
- The access of
values()
with reflection in the constructor is not optimal (especially when looking at the performance), but the two other options have their drawbacks as well:
- An implementation like
EnumSet.getUniverse()
makes use of a sun.misc
class
- Providing the values array as parameter has the risk that the given values are not the correct ones
- Only enums with up to 64 values are supported (is that really a drawback?)
- You could use BigInteger instead
- It's not easy to use the elements field in a criteria query or JPQL
- You could use binary operators or a bitmask column with the appropriate functions, if your database supports that