将枚举集存储在数据库中?

2022-09-02 05:16:43

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

在Java中,你有EnumSets,这似乎是在内存中传递枚举的好方法,但是如何将组合的EnumSet输出到一个整数进行存储呢?有没有其他方法可以解决这个问题?


答案 1

将序数存储为枚举集的表示形式不是一个好主意。序数取决于 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 <E extends Stable> int encode(EnumSet<E> set) {
  int ret = 0;

  for (E val : set) {
    ret |= (1 << val.getStableId());
  }

  return ret;
}

答案 2

如果你的枚举适合一个int(即有<= 32个值),我会使用每个枚举的序号值来滚动我自己的实现;例如:

public <E extends Enum<E>> int encode(EnumSet<E> 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 <E extends Enum<E>> EnumSet<E> decode(int encoded, Class<E> enumKlazz) {
  // First populate a look-up map of ordinal to Enum value.
  // This is fairly disgusting: Anyone know of a better approach?
  Map<Integer, E> ordinalMap = new HashMap<Integer, E>();
  for (E val : EnumSet.allOf(enumKlazz)) {
    ordinalMap.put(val.ordinal(), val);
  }

  EnumSet<E> 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;
}

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

编辑

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

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

恕我直言,这比我上面描述的序数方法要干净/健壮得多。


推荐