Java JPA&;枚举表(又名“一个真正的查找表”) 问题
在缺少SQL枚举类型的情况下,很遗憾,似乎要创建一种比较流行的数据库设计模式(感谢您的链接,Nathan)。这些年来,我看到了许多不同的情况,但我目前正在努力解决的问题看起来是这样的:Java JPA&;枚举表(又名“一个真正的查找表”) 问题,java,orm,jpa-2.1,Java,Orm,Jpa 2.1,在缺少SQL枚举类型的情况下,很遗憾,似乎要创建一种比较流行的数据库设计模式(感谢您的链接,Nathan)。这些年来,我看到了许多不同的情况,但我目前正在努力解决的问题看起来是这样的: ID | ENUM |值 -----+-------------+---------- 1 |每周的第|天|周日 2 |每周的第|天|周一 ... 7 |每周第|天|周六 ... 18 |个人|类型|员工 19 |个人|型|经理 然后像这样使用-例如在人员表中: ID | NAME |类型 ----+----
ID | ENUM |值
-----+-------------+----------
1 |每周的第|天|周日
2 |每周的第|天|周一
...
7 |每周第|天|周六
...
18 |个人|类型|员工
19 |个人|型|经理
然后像这样使用-例如在人员表中:
ID | NAME |类型
----+----------+------
1 | Jane Doe | 19
这意味着Jane是经理,因为19是枚举表中person类型“manager”的主键
问题:
使用JPA(2.1),是否有一种优雅的方法将此构造映射到propper Java枚举
重要提示:我的“枚举表”有很多版本,它们的主键值不同,即“管理器”有时可能是第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);
}
}
public enum PersonType{EMPLOYEE,MANAGER}
@表(name=“PERSONS”)@实体公共类人员{
@Id@Column(name=“Id”)长Id;
@列(name=“name”)字符串名称;
@Convert(converter=PtConv.class)@Column(name=“TYPE”)PersonType类型;
// ...
}
@转换器公共类PtConv实现AttributeConverter{
//在静态初始值设定项中,运行JDBC查询以填充这些映射:
私有静态映射dbToJava;
私有静态映射javaToDb;
@重写公共整数convertToDatabaseColumn(PersonType属性){
返回javaToDb.get(属性);
}
@重写公共PersonType convertToEntityAttribute(整数dbData){
返回dbToJava.get(dbData);
}
}
如果CDI在
@Converter
s中可用,我会接受这种情况,但静态构造测试是一场噩梦。您可以使用老式方法:
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); }
}
公共最终类枚举映射{
…//定义并加载单例数据
公共int getId(枚举e){
...
}
public E valueOf(int-id,Class-strictCheck){
...
返回strictCheck.cast(结果);
}
}
@表(name=“PERSONS”)@实体公共类人员{
@Id@Column(name=“Id”)长Id;
@列(name=“name”)字符串名称;
@列(name=“TYPE”)int-typeId;
public void setPersonType(PersonType类型){this.typeId=EnumMapping.getInstance().getId(类型);}
public PersonType getPersonType(){返回EnumMapping.getInstance().valueOf(typeId,PersonType.class);}
}
因此,您可以(a)使用java枚举获取/设置值,以及(b)在您想要的特定时刻初始化映射。作为参考,这就是我解决问题的方法。适当的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有不同的类型,设置值需要对实体的引用,这会进一步破坏代码。我不知道为什么您认为这有点流行。只需使用并保持您(以及未来维护人员)的生活简单即可。不幸的是,数据库模式不是我设计的。它的存在时间比Java应用程序长得多,并且安装在接受严格审核的客户机上。这就是为什么我写了
更改数据库模式不幸的是也不是一个选项
。您所描述的是“一个真正的查找表”,谢谢您的链接!很高兴知道,即使Joe Celko也同意这是一个糟糕的设计……JPA2.1将在本机上支持转换器。请浏览此链接()。我理解您的建议,这与使用@Converter
进行测试一样困难。基本上,这将是非常相同的方法,只是避免注释?如果我误解了你的意思,你能详细说明一下吗?据我所知,你有两个不同的任务:(a)将int-id映射到enum值,反之亦然;(b)在实体中使用枚举,而不是int-id。您案例中的第一个任务可以通过从DB加载(或在测试期间生成)并初始化EnumMapping实例来解决。它可以是非最终的,并且为了测试目的而更改,非常简单。第二个任务-您应该有一些静态上下文中可用的映射,我建议使用singleton(最简单的方法)。是的,它类似于@Converter方法,但您在初始化方面有更多的自由。问题-我可能完全不同意-如果您有一个javaenum
,为什么还需要一个查找表?这是否意味着您需要冗余地维护两个数据集?如果您只有一个枚举,而没有数据库表来查找该枚举(拥有实体的列值除外),那么只需使用该值并在属性上使用注释即可。否则我认为你的解决方案是好的。我知道java枚举更易于使用,但每次1/22/2对数据库枚举集进行更改时都必须编译新代码,这很麻烦,有时会破坏交易。@coderatchet-是的,你完全正确。这是多余的。在这个问题中,我不能真正清楚地表达的是DB模式和内容超出了我的控制范围。看来JPA2.2将允许在转换器中进行注入,所以这将为我解决这个问题。