Отображение гибернации между перечислением PostgreSQL и перечислением Java
Фон
- Spring 3.x, JPA 2.0, Hibernate 4.x, Postgresql 9.x.
- Работа над сопоставленным классом Hibernate с свойством enum, которое я хочу сопоставить с перечислением Postgresql.
проблема
Запрос с предложением where в столбце перечисления вызывает исключение.
org.hibernate.exception.SQLGrammarException: could not extract ResultSet
...
Caused by: org.postgresql.util.PSQLException: ERROR: operator does not exist: movedirection = bytea
Hint: No operator matches the given name and argument type(s). You might need to add explicit type casts.
Код (сильно упрощен)
SQL:
create type movedirection as enum (
'FORWARD', 'LEFT'
);
CREATE TABLE move
(
id serial NOT NULL PRIMARY KEY,
directiontomove movedirection NOT NULL
);
Hibernate сопоставленный класс:
@Entity
@Table(name = "move")
public class Move {
public enum Direction {
FORWARD, LEFT;
}
@Id
@Column(name = "id")
@GeneratedValue(generator = "sequenceGenerator", strategy=GenerationType.SEQUENCE)
@SequenceGenerator(name = "sequenceGenerator", sequenceName = "move_id_seq")
private long id;
@Column(name = "directiontomove", nullable = false)
@Enumerated(EnumType.STRING)
private Direction directionToMove;
...
// getters and setters
}
Java, которая вызывает запрос:
public List<Move> getMoves(Direction directionToMove) {
return (List<Direction>) sessionFactory.getCurrentSession()
.getNamedQuery("getAllMoves")
.setParameter("directionToMove", directionToMove)
.list();
}
Запрос xibernate xml:
<query name="getAllMoves">
<![CDATA[
select move from Move move
where directiontomove = :directionToMove
]]>
</query>
Поиск проблемы
- Запрос по
id
вместо enum работает, как ожидалось. -
Java без взаимодействия с базами данных работает нормально:
public List<Move> getMoves(Direction directionToMove) {
List<Move> moves = new ArrayList<>();
Move move1 = new Move();
move1.setDirection(directionToMove);
moves.add(move1);
return moves;
}
-
createQuery
вместо запроса в XML, аналогично примеру findByRating
в Apache JPA и Enums через @Enumerated документацию, дает то же исключение. - Запросить в psql с помощью
select * from move where direction = 'LEFT';
работает, как ожидалось. - Hardcoding,
where direction = 'FORWARD'
в запросе в XML работает. -
.setParameter("direction", direction.name())
не совпадает с параметрами .setString()
и .setText()
, исключение изменяется на:
Caused by: org.postgresql.util.PSQLException: ERROR: operator does not exist: movedirection = character varying
Попытки разрешения
-
Пользовательский UserType
как предлагается в этом принятом ответе qaru.site/info/316702/... а также:
@Column(name = "direction", nullable = false)
@Enumerated(EnumType.STRING) // tried with and without this line
@Type(type = "full.path.to.HibernateMoveDirectionUserType")
private Direction directionToMove;
-
Сопоставление с Hibernate EnumType
как было предложено более высоким, но не принятым ответом qaru.site/info/316702/... по тому же вопросу, что и выше, а также:
@Type(type = "org.hibernate.type.EnumType",
parameters = {
@Parameter(name = "enumClass", value = "full.path.to.Move$Direction"),
@Parameter(name = "type", value = "12"),
@Parameter(name = "useNamed", value = "true")
})
С двумя и без двух параметров после просмотра qaru.site/info/355864/...
- Пробовал аннотировать геттер и сеттер, как в этом ответе qaru.site/info/355866/....
- Не пробовал
EnumType.ORDINAL
потому что я хочу придерживаться EnumType.STRING
, который менее хрупкий и более гибкий.
Другие примечания
Конвертер типа JPA 2.1 не должен быть необходимым, но это не вариант, поскольку я сейчас на JPA 2.0.
Ответы
Ответ 1
HQL
Правильное использование псевдонима и использование имени квалифицированной собственности было первой частью решения.
<query name="getAllMoves">
<![CDATA[
from Move as move
where move.directionToMove = :direction
]]>
</query>
Отображение гибернации
@Enumerated(EnumType.STRING)
все еще не работает, поэтому необходим пользовательский UserType
. Ключ должен был правильно переопределить nullSafeSet
как в этом ответе fooobar.com/questions/355863/... и аналогичный реализации из Интернета.
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
if (value == null) {
st.setNull(index, Types.VARCHAR);
}
else {
st.setObject(index, ((Enum) value).name(), Types.OTHER);
}
}
Объезд
implements ParameterizedType
не сотрудничал:
org.hibernate.MappingException: type is not parameterized: full.path.to.PGEnumUserType
поэтому я не смог аннотировать свойство enum следующим образом:
@Type(type = "full.path.to.PGEnumUserType",
parameters = {
@Parameter(name = "enumClass", value = "full.path.to.Move$Direction")
}
)
Вместо этого я объявил класс следующим образом:
public class PGEnumUserType<E extends Enum<E>> implements UserType
с конструктором:
public PGEnumUserType(Class<E> enumClass) {
this.enumClass = enumClass;
}
который, к сожалению, означает, что любое другое свойство enum, аналогично отображаемое, будет нуждаться в таком классе:
public class HibernateDirectionUserType extends PGEnumUserType<Direction> {
public HibernateDirectionUserType() {
super(Direction.class);
}
}
Аннотация
Аннотировать свойство, и все готово.
@Column(name = "directiontomove", nullable = false)
@Type(type = "full.path.to.HibernateDirectionUserType")
private Direction directionToMove;
Другие примечания
-
EnhancedUserType
и три метода, которые он хочет реализовать
public String objectToSQLString(Object value)
public String toXMLString(Object value)
public String objectToSQLString(Object value)
не имел никакого значения, я мог видеть, поэтому я застрял с implements UserType
.
- В зависимости от того, как вы используете класс, это может быть не обязательно, чтобы сделать его postgres-специфичным, переопределив
nullSafeGet
в том, как это делали два связанных решения.
- Если вы готовы отказаться от перечисления postgres, вы можете сделать столбец
text
, а исходный код будет работать без дополнительной работы.
Ответ 2
Вам не нужно создавать все следующие типы гибернации вручную. Вы можете просто получить их через Maven Central, используя следующую зависимость:
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>${hibernate-types.version}</version>
</dependency>
Для получения дополнительной информации ознакомьтесь с проектом open-source типа hibernate.
Как я объяснил в этой статье, если вы легко сопоставляете Java Enum с типом столбца PostgreSQL Enum, используя следующий настраиваемый тип:
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
);
}
}
}
Чтобы использовать его, вам необходимо аннотировать поле с аннотацией Hibernate @Type
как показано в следующем примере:
@Entity(name = "Post")
@Table(name = "post")
@TypeDef(
name = "pgsql_enum",
typeClass = PostgreSQLEnumType.class
)
public static class Post {
@Id
private Long id;
private String title;
@Enumerated(EnumType.STRING)
@Column(columnDefinition = "post_status_info")
@Type( type = "pgsql_enum" )
private PostStatus status;
//Getters and setters omitted for brevity
}
Это сопоставление предполагает, что в PostgreSQL post_status_info
тип перечисления post_status_info
:
CREATE TYPE post_status_info AS ENUM (
'PENDING',
'APPROVED',
'SPAM'
)
Что это, он работает как шарм. Вот тест на GitHub, который это доказывает.
Ответ 3
Как сказано в 8.7.3. Тип Безопасность документов Postgres:
Если вам действительно нужно сделать что-то подобное, вы можете либо написать пользовательский оператор, либо добавить явные приведения к вашему запросу:
поэтому, если вам нужно быстрое и простое обходное решение, выполните следующие действия:
<query name="getAllMoves">
<![CDATA[
select move from Move move
where cast(directiontomove as text) = cast(:directionToMove as text)
]]>
</query>
К сожалению, вы не можете сделать это просто с двумя двоеточиями:
Ответ 4
Позвольте мне сказать, что я смог сделать это, используя Hibernate 4.3.x и Postgres 9.x.
Я основал свое решение от чего-то похожего на то, что вы сделали. Я считаю, что если вы объедините
@Type(type = "org.hibernate.type.EnumType",
parameters = {
@Parameter(name = "enumClass", value = "full.path.to.Move$Direction"),
@Parameter(name = "type", value = "12"),
@Parameter(name = "useNamed", value = "true")
})
и этот
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
if (value == null) {
st.setNull(index, Types.VARCHAR);
}
else {
st.setObject(index, ((Enum) value).name(), Types.OTHER);
}
}
Вы должны иметь возможность получить что-то в соответствии с этим, без необходимости делать изменения выше.
@Type(type = "org.hibernate.type.EnumType",
parameters = {
@Parameter(name = "enumClass", value = "full.path.to.Move$Direction"),
@Parameter(name = "type", value = "1111"),
@Parameter(name = "useNamed", value = "true")
})
Я считаю, что это работает, поскольку вы по сути говорите, что Hibernate отображает перечисление на другой тип (Types.OTHER == 1111). Это может быть немного хрупкое решение, так как значение Types.OTHER может измениться. Однако это обеспечит значительно меньше кода.
Ответ 5
У меня есть другой подход с конвертером постоянства:
import javax.persistence.Convert;
@Column(name = "direction", nullable = false)
@Converter(converter = DirectionConverter.class)
private Direction directionToMove;
Это определение преобразователя:
import javax.persistence.Converter;
@Converter
public class DirectionConverter implements AttributeConverter<Direction, String> {
@Override
public String convertToDatabaseColumn(Direction direction) {
return direction.name();
}
@Override
public Direction convertToEntityAttribute(String string) {
return Diretion.valueOf(string);
}
}
Он не разрешает сопоставление с типом перечисления psql, но может эффективно имитировать @Enumerated (EnumType.STRING) или @Enumerated (EnumType.ORDINAL).
Для порядкового использования direction.ordinal() и Direction.values() [number].