Java Enums, JPA и Postgres enums - Как мне заставить их работать вместе? - PullRequest
30 голосов
/ 12 мая 2009

У нас есть БД postgres с перечислениями postgres. Мы начинаем встраивать JPA в наше приложение. У нас также есть перечисления Java, которые отражают перечисления postgres. Теперь большой вопрос, как заставить JPA понимать перечисления Java с одной стороны и postgres перечисления с другой? Сторона Java должна быть довольно простой, но я не уверен, как сделать сторону postgres.

Ответы [ 4 ]

22 голосов
/ 13 мая 2009

Это включает создание нескольких отображений.

Сначала перечисление Postgres возвращается драйвером JDBC как экземпляр типа PGObject. Свойство type для этого имеет имя вашего перечисления postgres, а свойство value - его значение. (Порядковый номер, однако, не хранится, поэтому технически он больше не является перечислением и, возможно, совершенно бесполезен из-за этого)

В любом случае, если у вас есть такое определение в Postgres:


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

Тогда результирующий набор будет содержать PGObject с типом «mood» и значением «happy» для столбца, имеющего этот тип перечисления, и строку со значением «happy».

Следующее, что нужно сделать, это написать некоторый код перехватчика, который находится между местом, где JPA читает из необработанного результирующего набора, и устанавливает значение для вашей сущности. Например. Предположим, у вас была следующая сущность в Java:


public @Entity class Person {

  public static enum Mood {sad, ok, happy}

  @Id Long ID;
  Mood mood;

}

К сожалению, JPA не предлагает простой точки перехвата, где вы можете выполнить преобразование из PGObject в Java enum Mood. Большинство поставщиков 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;

Они позволяют вам предоставить экземпляр 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

}

Это не полное рабочее решение, а просто быстрый указатель в правильном направлении.

12 голосов
/ 30 марта 2017

Я на самом деле использовал более простой способ, чем тот, что с PGObject и конвертерами. Поскольку в Postgres перечисления вполне естественно преобразуются в текст, вам просто нужно позволить ему делать то, что он делает лучше всего. Я заимствую пример настроений Арджана, если он не возражает:

Тип перечисления в Postgres:

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, что более хлопотно, чем что-либо.

На данный момент у вас есть два варианта. Вы либо:

  1. Добавить stringtype = unspecified к строке соединения, как описано в Параметры соединения JDBC . Это позволяет Postgres угадывать тип с правой стороны и адекватно преобразовывать все, поскольку он получает что-то вроде 'enum = unknown', то есть выражение, с которым он уже знает, что делать (передать значение? В десериализатор левого типа). Это предпочтительный вариант, , поскольку он должен работать для всех простых UDT, таких как перечисления за один раз.

Или:

  1. Создать неявное преобразование из varchar в enum в базе данных. Таким образом, во втором случае база данных получает некоторое присваивание или сравнение, например, 'enum = varchar', и находит во внутреннем каталоге правило, в котором говорится, что она может передавать правое значение через функцию сериализации varchar, за которой следует функция десериализации ENUM. Это больше шагов, чем нужно; а слишком большое количество неявных приведений в каталоге может привести к неоднозначным интерпретациям произвольных запросов, поэтому используйте его с осторожностью. Актерский состав:

    CREATE CAST (CHARACTER, ИЗМЕНЯЮЩИЙСЯ как настроение) С INOUT, КАК ПОДРАЗУМЕВАЮТСЯ;

Должно работать только с этим.

4 голосов
/ 05 мая 2010

Я отправил отчет об ошибке с патчем для Hibernate: HHH-5188

Патч работает для меня, чтобы прочитать перечисление PostgreSQL в перечисление Java с использованием JPA.

0 голосов
/ 20 ноября 2018

Я нашел лучшее решение:

public class PostgreSQLEnumType extends org.hibernate.type.EnumType {

    public void nullSafeSet(
            PreparedStatement st,
            Object value,
            int index,
            SharedSessionContractImplementor session)
            throws HibernateException, SQLException {
        if(value == null) {
            st.setNull( index, Types.OTHER );
        }
        else {
            st.setObject(
                    index,
                    value.toString(),
                    Types.OTHER
            );
        }
    }
}

Теперь мы можем использовать PostgreSQLEnumType следующим образом:

@Entity
@TypeDef(name = "mood", typeClass = PostgreSQLEnumType.class)
public class Person {

    @Enumerated(EnumType.STRING)
    @Type(type = "mood")
    Mood mood;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...