将EnumSet存储在数据库中?

因此,在C ++ / C#中,您可以创建标记枚举以保存多个值,并且在数据库中存储单个有意义的整数当然是微不足道的。

在Java中你有EnumSets,它似乎是一种在内存中传递枚举的好方法,但是如何将组合的EnumSet输出到整数存储? 还有另一种方法来解决这个问题吗?

// From Adamski's answer public static > int encode(EnumSet set) { int ret = 0; for (E val : set) { ret |= 1 << val.ordinal(); } return ret; } @SuppressWarnings("unchecked") private static > EnumSet decode(int code, Class enumType) { try { E[] values = (E[]) enumType.getMethod("values").invoke(null); EnumSet result = EnumSet.noneOf(enumType); while (code != 0) { int ordinal = Integer.numberOfTrailingZeros(code); code ^= Integer.lowestOneBit(code); result.add(values[ordinal]); } return result; } catch (IllegalAccessException ex) { // Shouldn't happen throw new RuntimeException(ex); } catch (InvocationTargetException ex) { // Probably a NullPointerException, caused by calling this method // from within E's initializer. throw (RuntimeException) ex.getCause(); } catch (NoSuchMethodException ex) { // Shouldn't happen throw new RuntimeException(ex); } } 

将序数存储为EnumSet的表示并不是一个好主意。 序数取决于Enum类中定义的顺序( 相关讨论在这里 )。 您可以通过重构更改Enum值的顺序或在中间引入新的重构来轻松破坏您的数据库。

您必须引入单个枚举值的稳定表示。 这些可以再次为int值,并以EnumSet的建议方式表示。

您的枚举可以实现接口,因此稳定表示可以直接在枚举值中(改编自Adamski):

 interface Stable{ int getStableId(); } public enum X implements Stable { A(1), B(2); private int stableId; X(int id){ this.stableId = id; } @Override public int getStableId() { return stableId; } } 

改编自Adamski的代码:

 public  int encode(EnumSet set) { int ret = 0; for (E val : set) { ret |= (1 << val.getStableId()); } return ret; } 

提供你的枚举适合int(即有<= 32个值)我将使用每个枚举的序数值滚动我自己的实现; 例如

 public > int encode(EnumSet set) { int ret = 0; for (E val : set) { // Bitwise-OR each ordinal value together to encode as single int. ret |= (1 << val.ordinal()); } return ret; } public > EnumSet decode(int encoded, Class enumKlazz) { // First populate a look-up map of ordinal to Enum value. // This is fairly disgusting: Anyone know of a better approach? Map ordinalMap = new HashMap(); for (E val : EnumSet.allOf(enumKlazz)) { ordinalMap.put(val.ordinal(), val); } EnumSet ret= EnumSet.noneOf(enumKlazz); int ordinal = 0; // Now loop over encoded value by analysing each bit independently. // If the bit is set, determine which ordinal that corresponds to // (by also maintaining an ordinal counter) and use this to retrieve // the correct value from the look-up map. for (int i=1; i!=0; i <<= 1) { if ((i & encoded) != 0) { ret.add(ordinalMap.get(ordinal)); } ++ordinal; } return ret; } 

免责声明 :我没有测试过这个!

编辑

正如托马斯在评论中提到的那样,序数是不稳定的,因为代码中对枚举定义的任何更改都会导致数据库中的编码损坏(例如,如果在现有定义的中间插入新的枚举值)。 我解决这个问题的方法是为每个枚举定义一个“枚举”表,其中包含一个数字ID( 不是序数 )和String枚举值。 当我的Java应用程序启动时,DAO层所做的第一件事就是将每个Enum表读入内存,并且:

  • validation数据库中的所有String枚举值是否与Java定义匹配。
  • 初始化ID的双向映射到枚举,反之亦然,然后每当我持久化枚举时使用它(换句话说,所有“数据”表引用数据库特定的枚举ID,而不是显式存储String值) 。

这比我上面描述的序数方法更清晰/更强大的恕我直言。

如果您查看RegularEnumSet的源代码,这是Enum的<= 64个成员的实现,您将看到它包含:

 /** * Bit vector representation of this set. The 2^k bit indicates the * presence of universe[k] in this set. */ private long elements = 0L; 

元素是一个位掩码,位位置等于枚举,这正是你需要的。 但是,此属性不能通过getter或setter使用,因为它与JumboEnumSet的等效访问器不匹配。

它不是最好的解决方案之一,但如果简单性和速度是您所追求的,您可以创建2个静态实用程序方法,使用reflection检索和设置elements属性。

对我来说,我可能只是设置一个常量类,将枚举值保持为整数常量,我可以确定哪个枚举被分配了什么位。

EnumSet实现了Serializable ,但是如果你使用它会有很多开销(它被写成一个ID数组,而不是你BitSet ,加上对象流头。)

这是我发现有用的旧post,但是对于Java 8或更新版本,我已将@finnw发布的解决方案改编为此界面:

 public interface BitMaskable { int getBitMaskOrdinal(); static int bitMaskValue(Set set) { int mask = 0; for (BitMaskable val : set) { mask |= (1 << val.getBitMaskOrdinal()); } return mask; } static  & BitMaskable> Set valueOfBitMask(int mask, Class enumType) { E[] values = enumType.getEnumConstants(); EnumSet result = EnumSet.noneOf(enumType); Map ordinalCache = null; while (mask != 0) { int ordinal = Integer.numberOfTrailingZeros(mask); mask ^= Integer.lowestOneBit(mask); E value = null; if (ordinalCache != null) { value = ordinalCache.get(ordinal); } if (value == null) { for (E e : values) { if (e.getBitMaskOrdinal() == ordinal) { value = e; break; } // if there are more values to decode and e has a higher // ordinal than what we've seen, cache that for later if (mask != 0 && e.getBitMaskOrdinal() > ordinal) { if (ordinalCache == null) { ordinalCache = new HashMap<>(values.length); } ordinalCache.put(e.getBitMaskOrdinal(), e); } } } if (value != null) { result.add(value); } } return result; } } 

像这样的枚举的用法( 注意 bmOrdinal值是内置枚举序数值的无序值):

 public enum BitMaskEnum implements BitMaskable { A(0), B(2), C(1), D(3); private int bmOrdinal; private BitMaskEnum(int bmOrdinal) { this.bmOrdinal = bmOrdinal; } @Override public int getBitMaskOrdinal() { return bmOrdinal; } } 

然后沿着这些方向:

 // encode as bit mask; result == 5 int result = BitMaskable.bitMaskValue(EnumSet.of(BitMaskEnum.A, BitMaskEnum.B)); // decode into set; result contains A & B Set result = BitMaskable.valueOfBitMask(5, BitMaskEnum.class); 

使用答案中给出的方法,可以将整数转换为EnumSet,反之亦然。 但我发现这往往容易出错。 特别是当你得到负值时,因为java只签署了int和long。 因此,如果您计划对所有枚举集进行此类转换,则可能需要使用已支持此function的数据结构。 我创建了这样一个数据结构,可以像BitSet或EnumSet一样使用,但它也有toLong()和toBitSet()等方法。 请注意,这需要Java 8或更高版本。

这是链接: http : //claude-martin.ch/enumbitset/

没有进入关于数据库中序数值的利弊的辩论 – 我在这里发布了对给定问题的可能答案: JPA地图集合的枚举

我们的想法是创建一个新的PersistentEnumSet ,它使用java.util.RegularEnumSet的实现,但是为JPA提供了elements位掩码。

那个可以用于嵌入式:

 @Embeddable public class InterestsSet extends PersistentEnumSet { public InterestsSet() { super(InterestsEnum.class); } } 

该集合用于实体:

 @Entity public class MyEntity { // ... @Embedded private InterestsSet interests = new InterestsSet(); } 

如需进一步评论,请参阅我的答案。

我对finnw的代码做了一些更改,因此它适用于最多包含64个项目的枚举。

 // From Adamski's answer public static > long encode(EnumSet set) { long ret = 0; for (E val : set) { ret |= 1L << val.ordinal(); } return ret; } @SuppressWarnings("unchecked") public static > EnumSet decode(long code, Class enumType) { try { E[] values = (E[]) enumType.getMethod("values").invoke(null); EnumSet result = EnumSet.noneOf(enumType); while (code != 0) { int ordinal = Long.numberOfTrailingZeros(code); code ^= Long.lowestOneBit(code); result.add(values[ordinal]); } return result; } catch (IllegalAccessException ex) { // Shouldn't happen throw new RuntimeException(ex); } catch (InvocationTargetException ex) { // Probably a NullPointerException, caused by calling this method // from within E's initializer. throw (RuntimeException) ex.getCause(); } catch (NoSuchMethodException ex) { // Shouldn't happen throw new RuntimeException(ex); } } 

让我感到惊讶的是,没有人建议维护良好的库而不是编写自己的库。 上面的答案是现场和教育,但它只是鼓励人们复制和粘贴代码(然后大多忘记学分)。

这是我的2美分:

 EnumSet mySet = EnumSet.of(YourEnum.FIRST); long vector = EnumUtils.generateBitVector(YourEnum.class, mySet); EnumSet sameSet = EnumUtils.processBitVector(YourEnum.class, vector); 

请参阅https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/EnumUtils.html