JPA&枚举表(又名“一个真正的查找表”)

时间:2014-10-15 23:08:25

标签: java orm jpa-2.1

问题

缺少SQL枚举类型,不幸的是有些流行的数据库设计模式似乎是创建one table for all enum values(感谢您的链接,Nathan)。多年来我看到了很多这方面的变化,但我目前正在努力解决的问题看起来像这样:

 ID  | ENUM        | VALUE
-----+-------------+----------
   1 | DAY_OF_WEEK | SUNDAY
   2 | DAY_OF_WEEK | MONDAY
    ... 
   7 | DAY_OF_WEEK | SATURDAY
    ...
  18 | PERSON_TYPE | EMPLOYEE
  19 | PERSON_TYPE | MANAGER

然后像这样使用 - 例如在人员表中:

 ID | NAME     | TYPE
----+----------+------
  1 | Jane Doe | 19     

意思是Jane是一个经理,因为19是人类类型的主键"经理"在枚举表中。

问题

使用JPA(2.1),是否有一种优雅的方法将此构造映射到propper Java枚举?

重要提示:我的"枚举表有很多版本"使用不同的主键值在野外,即" manager"有时可能是#19行,但有时排#231。但是,值在运行时永远不会更改。遗憾的是,更改数据库模式也不是一种选择,但使用任何JPA提供程序的专有功能都是一种选择。

什么有用

我实际上找到了一个有效的解决方案,但这对我来说太糟糕了:

public enum PersonType { EMPLOYEE, MANAGER }

@Table(name="PERSONS") @Entity public class Persons {
  @Id @Column(name="ID") long id;
  @Column(name="NAME") String name;
  @Convert(converter = PtConv.class) @Column(name="TYPE") PersonType type;
  // ...
}

@Converter public class PtConv implements AttributeConverter<PersonType, Integer> {
  // In a static initializer run a JDBC query to fill these maps:
  private static Map<Integer, PersonType> dbToJava;
  private static Map<PersonType, Integer> javaToDb;

  @Override public Integer convertToDatabaseColumn(PersonType attribute) {
    return javaToDb.get(attribute);
  }

  @Override public PersonType convertToEntityAttribute(Integer dbData) {
    return dbToJava.get(dbData);
  }
}

如果CDI在@Converter中可用,我会和它一起生活 - 但静态构造测试是一场噩梦。

2 个答案:

答案 0 :(得分:0)

您可以使用旧式方法:

public final class EnumMapping {
    ... // define & load singleton data
    public int getId(Enum<?> e) { 
        ... 
    }
    public <E extends Enum<E>> E valueOf(int id, Class<E> strictCheck) { 
        ... 
        return strictCheck.cast(result);
    }
}

@Table(name="PERSONS") @Entity public class Persons {
  @Id @Column(name="ID") long id;
  @Column(name="NAME") String name;
  @Column(name="TYPE") int typeId;

  public void setPersonType(PersonType type) { this.typeId = EnumMapping.getInstance().getId(type); }
  public PersonType getPersonType() { return EnumMapping.getInstance().valueOf(typeId, PersonType.class); }
}

所以你可以(a)使用java枚举来获取/设置值,以及(b)在你想要的特定时刻初始化映射。

答案 1 :(得分:0)

作为参考,这是我解决问题的方法。适当的Java枚举将是我的偏好,我会接受任何比这更好的答案。

@Table(name="PERSONS") @Entity public class Persons {
  @Id @Column(name="ID") long id;
  @Column(name="NAME") String name;
  @Column(name="TYPE") BaseEnum type;   // known to be "PersonTypeEnum"

  public PersonType getType() {
    switch(type.getValue()) {
      case "EMPLOYEE": return PersonType.EMPLOYEE;
      case "MANAGER":  return PersonType.MANAGER;
    }
    throw new IllegalStateException(); 
  }

  public void setType(PersonTypeEnum type) {
    this.type = type;
  }
  // ...
}

@Entity @Inheritance @DiscriminatorColumn(name="ENUM") @Table(name="ENUMS")
public abstract class BaseEnum {
  @Id private int id;
  @Column(name="VALUE") String value;
  // ...
}

@Entity @DiscriminatorValue("PERSON_TYPE")
public class PersonTypeEnum extends BaseEnum { }

因此枚举值的getter和setter有不同的类型,设置值需要引用实体,这进一步扰乱了代码。