Java枚举、JPA和Postgres枚举-如何使它们协同工作?
我们有一个带有postgres枚举的postgres DB。我们开始将JPA构建到我们的应用程序中。我们还有Java枚举,它镜像postgres枚举。现在最大的问题是如何让JPA理解一边是Java枚举,另一边是postgres枚举?Java端应该相当简单,但我不确定如何做postgres端。这涉及到多个映射 首先,JDBC驱动程序将Postgres枚举作为PGObject类型的实例返回。此属性的type属性具有postgres枚举的名称,value属性具有其值。(但是序号没有存储,因此从技术上讲它不再是枚举,因此可能完全没有用处) 无论如何,如果你在Postgres中有这样的定义:Java枚举、JPA和Postgres枚举-如何使它们协同工作?,java,postgresql,jpa,Java,Postgresql,Jpa,我们有一个带有postgres枚举的postgres DB。我们开始将JPA构建到我们的应用程序中。我们还有Java枚举,它镜像postgres枚举。现在最大的问题是如何让JPA理解一边是Java枚举,另一边是postgres枚举?Java端应该相当简单,但我不确定如何做postgres端。这涉及到多个映射 首先,JDBC驱动程序将Postgres枚举作为PGObject类型的实例返回。此属性的type属性具有postgres枚举的名称,value属性具有其值。(但是序号没有存储,因此从技术上讲
CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');
CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');
然后,对于具有此枚举类型的列和值为“happy”的行,结果集将包含类型为“mood”且值为“happy”的PGObject
接下来要做的事情是编写一些拦截器代码,它位于JPA从原始结果集读取并设置实体值的位置之间。例如,假设您在Java中有以下实体:
public @Entity class Person {
public static enum Mood {sad, ok, happy}
@Id Long ID;
Mood mood;
}
不幸的是,JPA没有提供一个简单的截取点,您可以在该截取点上执行从PGObject到JavaEnum的转换。然而,大多数JPA供应商对此有一些专有支持。例如,Hibernate对此有TypeDef和Type注释(来自Hibernate annotations.jar)
这些允许您提供一个UserType实例(来自Hibernate core.jar),用于执行实际转换:
public class MyEnumConverter implements UserType {
private static final int[] SQL_TYPES = new int[]{Types.OTHER};
public Object nullSafeGet(ResultSet arg0, String[] arg1, Object arg2) throws HibernateException, SQLException {
Object pgObject = arg0.getObject(X); // X is the column containing the enum
try {
Method valueMethod = pgObject.getClass().getMethod("getValue");
String value = (String)valueMethod.invoke(pgObject);
return Mood.valueOf(value);
}
catch (Exception e) {
e.printStackTrace();
}
return null;
}
public int[] sqlTypes() {
return SQL_TYPES;
}
// Rest of methods omitted
}
这不是一个完整的工作解决方案,但只是一个指向正确方向的快速指针。我提交了一份错误报告,其中包含了Hibernate的补丁:
这个补丁可以让我使用JPA将PostgreSQL枚举读入Java枚举。实际上,我使用的方法比使用PGObject和转换器的方法更简单。因为在Postgres中,枚举非常自然地转换为文本,所以您只需要让它做它最擅长的事情。如果阿扬不介意的话,我可以借用他关于情绪的例子: Postgres中的枚举类型:
CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');
CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');
Java中的类和枚举:
public @Entity class Person {
public static enum Mood {sad, ok, happy};
@Enumerated(EnumType.STRING)
Mood mood;
}
@Enumerated标记表示枚举的序列化/反序列化应该在文本中完成。没有它,它使用int,这比任何东西都麻烦
此时,您有两个选择。你可以:
jdbc:postgresql://localhost:5432/dbname?stringtype=unspecified
应该可以这样做。我们现在也将系统移动到JPA,但是我们有映射到varchar列的java枚举,但Hibernate仍然没有问题>我们现在也将系统移动到JPA,但是我们有映射到varchar列的java枚举,Hibernate varchar仍然没有问题,在DB中看起来不错,但与DB中的实际枚举相比,您遗漏了两件事:*键入safety,除非您希望向每一列添加检查约束,或对单独的表使用FK,该表将所有varchar值作为PK保存。*速度字符串查找和比较速度较慢,即使在索引时也是如此。因此,无论如何,这是一种权衡。您可以使用varchars,除了上述限制,或者使用本机PG enum并接受非自动映射。p.s。还有第三种方法,按序数映射,但这并不推荐;使代码很难读懂;如果从Java枚举中删除或删除单个常量,则数据库中已经存在的所有序号都可能无效!是否有理由使用反射而不是强制转换?@Martin来防止编译时依赖性。并非每个项目的类路径上都有驱动程序,例如,驱动程序通常位于服务器的某个/lib文件夹中。如果这对您来说不是问题,您就不必使用反射。虽然它不使用序号,但它不会存储最多63个字节的整个字符串。枚举值的大小为4字节。查看link()末尾的实现细节,在4.3.5中,这一点似乎更糟。现在我甚至无法将枚举持久化到Postgres,因为Hibernate似乎坚持将其作为文本或整数写入。这是目前为止最简单的最新实现选项。当枚举用作JPA存储库的参数时,CREATE CAST解决方案似乎不起作用。例如,
Entity findByMyEnum(MyEnum MyEnum)
使用自动ddl时,仍会将其创建为整数/字符变量。