Подтвердить что ты не робот

Перечисления Java, перечисления JPA и Postgres - Как мне заставить их работать вместе?

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

4b9b3361

Ответ 1

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

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

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


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

Затем набор результатов будет содержать PGObject с типом "настроение" и значением "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

}

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

Ответ 2

Я на самом деле использовал более простой способ, чем с PGObject и Converter. Поскольку в 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, таких как перечисления, за один раз.

    jdbc:postgresql://localhost:5432/dbname?stringtype=unspecified
    

Или:

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

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

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

Ответ 3

Я подал сообщение об ошибке с патчем, включенным для Hibernate: HHH-5188

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

Ответ 4

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

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;
}