如何在数据库中存储Enum类名和枚举值

时间:2019-05-15 15:28:06

标签: java mysql jooq

我有一个带有不同枚举的类,例如:

class EligibilityRule{
ProductEligibility productEligibility;
CountryEligibility countryEligibility
}

enum ProductEligibility{
PRODUCT_X,
PRODUCT_Y
}

enum CountryEligibility{
US,
IN,
CN
..
}

我想将这些不同的枚举类名及其值存储到名为eligibility rule的数据库表中,并且我的表架构看起来像这样,

String id  => auto_increment id
String ruleType => enum class name (ex: ProductEligibility)
String ruleValue => enum value (ex: PRODUCT_X)

我使用的是JOOQ,过去我曾经使用强制类型来存储枚举值。但是,在这种情况下,我想存储枚举类名称和枚举值。当我从db查询记录时,我也想重建枚举对象。

我可以遵循任何模式吗?JOOQ是否可以扩展以解决此问题?

1 个答案:

答案 0 :(得分:2)

JOOQ支持custom data types。这意味着您可以为数据库字段定义一个converter,然后在加载时自动将其映射到您的自定义类型。数据库字段仍然是String,但是为该字段生成的Record将包含Field<EligibilityRule>。这意味着您不必显式存储类名。

为此,您必须在代码生成器中注册转换器(从上面的文档页面获取):

<database>

  <!-- Then, associate custom types with database columns -->
  <forcedTypes>
    <forcedType>

      <!-- Specify the Java type of your custom type. This corresponds to the Converter's <U> type. -->
      <userType>java.util.GregorianCalendar</userType>

      <!-- Associate that custom type with your converter. -->
      <converter>com.example.CalendarConverter</converter>

      <!-- Add a Java regular expression matching fully-qualified columns. Use the pipe to separate several expressions.

           If provided, both "expressions" and "types" must match. -->
      <expression>.*\.DATE_OF_.*</expression>

      <!-- Add a Java regular expression matching data types to be forced to
           have this type.

           Data types may be reported by your database as:
           - NUMBER              regexp suggestion: NUMBER
           - NUMBER(5)           regexp suggestion: NUMBER\(5\)
           - NUMBER(5, 2)        regexp suggestion: NUMBER\(5,\s*2\)
           - any other form

           It is thus recommended to use defensive regexes for types.

           If provided, both "expressions" and "types" must match. -->
      <types>.*</types>
    </forcedType>
  </forcedTypes>
</database>

另请参阅JOOQ支持的custom data type binding


  

我明白您的意思了,在我的情况下,我不是为每个枚举创建新的数据库字段,而是将它们存储为rule_type和rule_value(key / value)对,其中键是类名, value是枚举值。

我明白了。不幸的是,我认为不可能以类型安全的方式执行此操作。我相信this的答案几乎就是您要问的-基于列值的特殊类型的绑定。

但是,由于您使用的是枚举,因此不能像上面的答案中那样使它们扩展超类(因为所有Enum都隐式扩展java.lang.Enum,并且Java不支持多重继承)。但是,您可能会尝试稍微重构代码,并让所有枚举实现某种接口,即:

enum ProductEligibility implements Rule {...};
enum CountryEligibility implements Rule {...};

Rule为:

interface Rule { String getRuleType(); String getRuleValue(); }

然后像在docs页面中的示例或单独的链接答案中那样创建一个转换器。

当然,这也意味着您的Record中将有一个Field<Rule>字段,而不是枚举的特定类型。如果这对您来说可以接受,那么这可能是一种可行的方法。


  

我没有得到这部分,Of course, this also means that your Records would have a Field<Rule> field in them, not the specific type of the enum.。这是否意味着我仍将用CustomConverter在db中存储两个字段,rule_type和rule_value?

不。您仍然只有一个Converter<Object, Rule>类型的转换器。此转换器将返回 ProductEligibilityCountryEligibility,但不能同时返回

因此,如果您的数据库表具有以下内容:

eligiblity_rules
------------------
id    user    type                              value 
234   223     com.foo.bar.ProductEligibility    PRODUCT_Y
856   855     com.foo.bar.CountryEligibility    US

您的转换器看起来像这样:

public Converter<Object, Rule> converter() {
    return new Converter<Object, Rule>() {
        @Override
        public Rule from(Object t) {
            // t here refers to the "value" db field above
            if (checkIsProductEligibilityRule())
                 return ProductEligibility.fromValue(...);
            else
                return CountryEligibility.fromValue(...)
        }

        // Other converter methods
    };
}

因此,在基于JOOQ的代码中,您最终将拥有:

EligibilityRuleRecord record = dslContext.selectFrom(ELIGIBILITY_RULE).where(...).fetchOne();
Rule rule = record.getValue();

然后,如果要使用特定的规则类型,则需要检查并强制转换:

if (rule instanceof ProductEligibility) {
    ProductEligibility peRule = (ProductEligibility) rule; 
    ...
}
if (rule instanceof CountryEligibility) {
    CountryEligibility ceRule = (CountryEligibility) rule;
    ...
}
...

type数据库字段的唯一原因是选择正确的数据。不幸的是,Java代码不知道(在编译时)类型是什么,因此每次您想知道该字段的特定类型时,都需要在运行时进行类检查。