Java Enums,JPA和Postgres枚举 - 如何让它们协同工作?

时间:2022-09-11 12:54:16

We have a postgres DB with postgres enums. We are starting to build JPA into our application. We also have Java enums which mirror the postgres enums. Now the big question is how to get JPA to understand Java enums on one side and postgres enums on the other? The Java side should be fairly easy but I'm not sure how to do the postgres side.

我们有一个postgres数据库与postgres枚举。我们开始在我们的应用程序中构建JPA。我们还有Java枚举,它反映了postgres枚举。现在最大的问题是如何让JPA一方面了解Java枚举,另一方面知道postgres枚举? Java方面应该相当容易,但我不知道如何做postgres方面。

3 个解决方案

#1


21  

This involves making multiple mappings.

这涉及进行多次映射。

First, a Postgres enum is returned by the JDBC driver as an instance of type PGObject. The type property of this has the name of your postgres enum, and the value property its value. (The ordinal is not stored however, so technically it's not an enum anymore and possibly completely useless because of this)

首先,JDBC驱动程序将Postgres枚举作为PGObject类型的实例返回。它的type属性具有postgres枚举的名称,value属性具有其值。 (但序数并未存储,因此从技术上讲,它不再是枚举,因此可能完全没用)

Anyway, if you have a definition like this in Postgres:

无论如何,如果你在Postgres中有这样的定义:


CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');

Then the resultset will contain a PGObject with type "mood" and value "happy" for a column having this enum type and a row with the value 'happy'.

然后,结果集将包含一个PGObject,其类型为“mood”,值为“happy”,表示具有此枚举类型的列和值为“happy”的行。

Next thing to do is writing some interceptor code that sits between the spot where JPA reads from the raw resultset and sets the value on your entity. E.g. suppose you had the following entity in Java:

接下来要做的是编写一些拦截器代码,它位于JPA从原始结果集读取的位置和设置实体上的值之间。例如。假设你在Java中有以下实体:


public @Entity class Person {

  public static enum Mood {sad, ok, happy}

  @Id Long ID;
  Mood mood;

}

Unfortunately, JPA does not offer an easy interception point where you can do the conversion from PGObject to the Java enum Mood. Most JPA vendors however have some proprietary support for this. Hibernate for instance has the TypeDef and Type annotations for this (from Hibernate-annotations.jar).

不幸的是,JPA没有提供一个简单的拦截点,您可以从PGObject转换到Java枚举情绪。然而,大多数JPA供应商都有一些专有支持。例如,Hibernate具有TypeDef和Type注释(来自Hibernate-annotations.jar)。


@TypeDef(name="myEnumConverter", typeClass=MyEnumConverter.class)
public @Entity class Person {

  public static enum Mood {sad, ok, happy}

  @Id Long ID;
  @Type(type="myEnumConverter") Mood mood;

These allow you to supply an instance of UserType (from Hibernate-core.jar) that does the actual conversion:

这些允许您提供实际转换的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

}

This is not a complete working solution, but just a quick pointer in hopefully the right direction.

这不是一个完整的工作解决方案,而只是一个快速指针,希望是正确的方向。

#2


12  

I've actually been using a simpler way than the one with PGObject and Converters. Since in Postgres enums are converted quite naturally to-from text you just need to let it do what it does best. I'll borrow Arjan's example of moods, if he doesn't mind:

我实际上使用的方法比使用PGObject和Converters的方式更简单。因为在Postgres中,enums很自然地转换为文本,所以你只需要让它做它最擅长的事情。如果他不介意的话,我会借用Arjan的情绪例子:

The enum type in Postgres:

Postgres中的枚举类型:

CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');

The class and enum in Java:

Java中的类和枚举:

public @Entity class Person {

  public static enum Mood {sad, ok, happy};

  @Enumerated(EnumType.STRING)
  Mood mood;

}

That @Enumerated tag says that serialization/deserialization of the enum should be done in text. Without it, it uses int, which is more troublesome than anything.

@Enumerated标记表示枚举的序列化/反序列化应该在文本中完成。没有它,它使用int,这比任何东西都麻烦。

At this point you have two options. You either:

此时您有两个选择。你要么:

  1. Add stringtype=unspecified to the connection string, as explained in JDBC connection parameters. This lets Postgres guess the right-side type and convert everything adequately, since it receives something like 'enum = unknown', which is an expression it already knows what to do with (feed the ? value to the left-hand type deserialiser). This is the preferred option, as it should work for all simple UDTs such as enums in one go.
  2. 将stringtype = unspecified添加到连接字符串,如JDBC连接参数中所述。这让Postgres猜测右侧类型并充分转换所有内容,因为它收到类似'enum = unknown'的内容,这是一个表达式,它已经知道该怎么做(将?值提供给左侧类型的反序列化器)。这是首选选项,因为它应该适用于所有简单的UDT,例如枚举。

Or:

  1. Create an implicit conversion from varchar to the enum in the database. So in this second case the database receives some assignment or comparison like 'enum = varchar' and it finds a rule in its internal catalog saying that it can pass the right-hand value through the serialization function of varchar followed by the deserialization function of the enum. That's more steps than should be needed; and having too many implicit casts in the catalog can cause arbitrary queries to have ambiguous interpretations, so use it sparingly. The cast creation is:

    创建从varchar到数据库中枚举的隐式转换。所以在第二种情况下,数据库接收一些赋值或比较,如'enum = varchar',它在其内部目录中找到一条规则,说它可以通过varchar的序列化函数传递右侧值,然后是反序列化函数。枚举。这比我们需要的步骤更多;并且在目录中有太多隐式强制转换会导致任意查询具有模糊的解释,因此请谨慎使用它。演员创作是:

    CREATE CAST (CHARACTER VARYING as mood) WITH INOUT AS IMPLICIT;

    创建CAST(字符变化为情绪),而不是隐含的;

Should work with just that.

应该只是这样。

#3


4  

I filed a bug report with a patch included for Hibernate: HHH-5188

我提交了一个错误报告,其中包含针对Hibernate的补丁:HHH-5188

The patch works for me to read a PostgreSQL enum into a Java enum using JPA.

该补丁适用于我使用JPA将PostgreSQL枚举读入Java枚举。

#1


21  

This involves making multiple mappings.

这涉及进行多次映射。

First, a Postgres enum is returned by the JDBC driver as an instance of type PGObject. The type property of this has the name of your postgres enum, and the value property its value. (The ordinal is not stored however, so technically it's not an enum anymore and possibly completely useless because of this)

首先,JDBC驱动程序将Postgres枚举作为PGObject类型的实例返回。它的type属性具有postgres枚举的名称,value属性具有其值。 (但序数并未存储,因此从技术上讲,它不再是枚举,因此可能完全没用)

Anyway, if you have a definition like this in Postgres:

无论如何,如果你在Postgres中有这样的定义:


CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');

Then the resultset will contain a PGObject with type "mood" and value "happy" for a column having this enum type and a row with the value 'happy'.

然后,结果集将包含一个PGObject,其类型为“mood”,值为“happy”,表示具有此枚举类型的列和值为“happy”的行。

Next thing to do is writing some interceptor code that sits between the spot where JPA reads from the raw resultset and sets the value on your entity. E.g. suppose you had the following entity in Java:

接下来要做的是编写一些拦截器代码,它位于JPA从原始结果集读取的位置和设置实体上的值之间。例如。假设你在Java中有以下实体:


public @Entity class Person {

  public static enum Mood {sad, ok, happy}

  @Id Long ID;
  Mood mood;

}

Unfortunately, JPA does not offer an easy interception point where you can do the conversion from PGObject to the Java enum Mood. Most JPA vendors however have some proprietary support for this. Hibernate for instance has the TypeDef and Type annotations for this (from Hibernate-annotations.jar).

不幸的是,JPA没有提供一个简单的拦截点,您可以从PGObject转换到Java枚举情绪。然而,大多数JPA供应商都有一些专有支持。例如,Hibernate具有TypeDef和Type注释(来自Hibernate-annotations.jar)。


@TypeDef(name="myEnumConverter", typeClass=MyEnumConverter.class)
public @Entity class Person {

  public static enum Mood {sad, ok, happy}

  @Id Long ID;
  @Type(type="myEnumConverter") Mood mood;

These allow you to supply an instance of UserType (from Hibernate-core.jar) that does the actual conversion:

这些允许您提供实际转换的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

}

This is not a complete working solution, but just a quick pointer in hopefully the right direction.

这不是一个完整的工作解决方案,而只是一个快速指针,希望是正确的方向。

#2


12  

I've actually been using a simpler way than the one with PGObject and Converters. Since in Postgres enums are converted quite naturally to-from text you just need to let it do what it does best. I'll borrow Arjan's example of moods, if he doesn't mind:

我实际上使用的方法比使用PGObject和Converters的方式更简单。因为在Postgres中,enums很自然地转换为文本,所以你只需要让它做它最擅长的事情。如果他不介意的话,我会借用Arjan的情绪例子:

The enum type in Postgres:

Postgres中的枚举类型:

CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');

The class and enum in Java:

Java中的类和枚举:

public @Entity class Person {

  public static enum Mood {sad, ok, happy};

  @Enumerated(EnumType.STRING)
  Mood mood;

}

That @Enumerated tag says that serialization/deserialization of the enum should be done in text. Without it, it uses int, which is more troublesome than anything.

@Enumerated标记表示枚举的序列化/反序列化应该在文本中完成。没有它,它使用int,这比任何东西都麻烦。

At this point you have two options. You either:

此时您有两个选择。你要么:

  1. Add stringtype=unspecified to the connection string, as explained in JDBC connection parameters. This lets Postgres guess the right-side type and convert everything adequately, since it receives something like 'enum = unknown', which is an expression it already knows what to do with (feed the ? value to the left-hand type deserialiser). This is the preferred option, as it should work for all simple UDTs such as enums in one go.
  2. 将stringtype = unspecified添加到连接字符串,如JDBC连接参数中所述。这让Postgres猜测右侧类型并充分转换所有内容,因为它收到类似'enum = unknown'的内容,这是一个表达式,它已经知道该怎么做(将?值提供给左侧类型的反序列化器)。这是首选选项,因为它应该适用于所有简单的UDT,例如枚举。

Or:

  1. Create an implicit conversion from varchar to the enum in the database. So in this second case the database receives some assignment or comparison like 'enum = varchar' and it finds a rule in its internal catalog saying that it can pass the right-hand value through the serialization function of varchar followed by the deserialization function of the enum. That's more steps than should be needed; and having too many implicit casts in the catalog can cause arbitrary queries to have ambiguous interpretations, so use it sparingly. The cast creation is:

    创建从varchar到数据库中枚举的隐式转换。所以在第二种情况下,数据库接收一些赋值或比较,如'enum = varchar',它在其内部目录中找到一条规则,说它可以通过varchar的序列化函数传递右侧值,然后是反序列化函数。枚举。这比我们需要的步骤更多;并且在目录中有太多隐式强制转换会导致任意查询具有模糊的解释,因此请谨慎使用它。演员创作是:

    CREATE CAST (CHARACTER VARYING as mood) WITH INOUT AS IMPLICIT;

    创建CAST(字符变化为情绪),而不是隐含的;

Should work with just that.

应该只是这样。

#3


4  

I filed a bug report with a patch included for Hibernate: HHH-5188

我提交了一个错误报告,其中包含针对Hibernate的补丁:HHH-5188

The patch works for me to read a PostgreSQL enum into a Java enum using JPA.

该补丁适用于我使用JPA将PostgreSQL枚举读入Java枚举。