У нас есть postgres DB с перечислениями postgres. Мы начинаем строить JPA в нашем приложении. У нас также есть перечисления Java, которые отражают перечисления postgres. Теперь большой вопрос: как получить JPA, чтобы понять Java перечисления с одной стороны и postgres enums - с другой? Сторона Java должна быть довольно простой, но я не уверен, как сделать postgres.
Перечисления Java, перечисления JPA и Postgres - Как мне заставить их работать вместе?
Ответ 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, что более хлопотно, чем что-либо.
На данный момент у вас есть два варианта. Вы либо:
Добавьте stringtype = unspecified в строку подключения, как описано в параметрах подключения JDBC. Это позволяет Postgres угадывать тип правой стороны и адекватно преобразовывать все, поскольку он получает что-то вроде 'enum = unknown', что является выражение, которое он уже знает, что делать (передать значение? в десериализатор левого типа). Это предпочтительный вариант,, поскольку он должен работать для всех простых UDT, таких как перечисления, за один раз.
jdbc:postgresql://localhost:5432/dbname?stringtype=unspecified
Или:
Создайте неявное преобразование из 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;
}