[英]Hibernate mapping between PostgreSQL enum and Java enum
Querying with a where clause on the enum column throws an exception.使用枚举列上的 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 mapped class: 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 that calls the query:调用查询的Java:
public List<Move> getMoves(Direction directionToMove) {
return (List<Direction>) sessionFactory.getCurrentSession()
.getNamedQuery("getAllMoves")
.setParameter("directionToMove", directionToMove)
.list();
}
Hibernate xml query:休眠xml查询:
<query name="getAllMoves">
<![CDATA[
select move from Move move
where directiontomove = :directionToMove
]]>
</query>
id
instead of the enum works as expected.id
而不是 enum 查询按预期工作。 Java without database interaction works fine:没有数据库交互的 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
instead of having the query in XML, similar to the findByRating
example in Apache's JPA and Enums via @Enumerated documentation gave the same exception. createQuery
而不是在 XML 中进行查询,类似于Apache 的 JPA 和 Enums 中的findByRating
示例, 通过 @Enumerated 文档给出了相同的例外。select * from move where direction = 'LEFT';
select * from move where direction = 'LEFT';
works as expected.where direction = 'FORWARD'
in the query in the XML works.where direction = 'FORWARD'
有效。 .setParameter("direction", direction.name())
does not, same with .setString()
and .setText()
, exception changes to: .setParameter("direction", direction.name())
不,用相同.setString()
和.setText()
异常变化为:
Caused by: org.postgresql.util.PSQLException: ERROR: operator does not exist: movedirection = character varying
Custom UserType
as suggested by this accepted answer https://stackoverflow.com/a/1594020/1090474 along with:此接受的答案https://stackoverflow.com/a/1594020/1090474建议的自定义
UserType
以及:
@Column(name = "direction", nullable = false) @Enumerated(EnumType.STRING) // tried with and without this line @Type(type = "full.path.to.HibernateMoveDirectionUserType") private Direction directionToMove;
Mapping with Hibernate's EnumType
as suggested by a higher rated but not accepted answer https://stackoverflow.com/a/1604286/1090474 from the same question as above, along with:使用 Hibernate 的
EnumType
映射,如来自上述相同问题的更高评级但未接受的答案https://stackoverflow.com/a/1604286/1090474所建议的,以及:
@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") })
With and without the two second parameters, after seeing https://stackoverflow.com/a/13241410/1090474有无二秒参数,看到https://stackoverflow.com/a/13241410/1090474后
EnumType.ORDINAL
because I want to stick with EnumType.STRING
, which is less brittle and more flexible.EnumType.ORDINAL
因为我想坚持使用EnumType.STRING
,它不那么脆弱且更灵活。A JPA 2.1 Type Converter shouldn't be necessary, but isn't an option regardless, since I'm on JPA 2.0 for now. JPA 2.1 类型转换器不是必需的,但无论如何都不是一个选项,因为我现在使用的是 JPA 2.0。
You can simply get these types via Maven Central using the Hibernate Types dependency:您可以使用Hibernate Types依赖项通过 Maven Central 简单地获取这些类型:
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>${hibernate-types.version}</version>
</dependency>
If you easily map Java Enum to a PostgreSQL Enum column type using the following custom Type:如果您使用以下自定义类型轻松地将 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
);
}
}
}
To use it, you need to annotate the field with the Hibernate @Type
annotation as illustrated in the following example:要使用它,您需要使用 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
}
This mapping assumes you have the post_status_info
enum type in PostgreSQL:此映射假设您在 PostgreSQL 中有
post_status_info
枚举类型:
CREATE TYPE post_status_info AS ENUM (
'PENDING',
'APPROVED',
'SPAM'
)
That's it, it works like a charm.就是这样,它就像一个魅力。 Here's a test on GitHub that proves it .
这是GitHub 上的一个测试,证明了这一点。
Aliasing correctly and using the qualified property name was the first part of the solution.正确别名和使用限定的属性名称是解决方案的第一部分。
<query name="getAllMoves">
<![CDATA[
from Move as move
where move.directionToMove = :direction
]]>
</query>
@Enumerated(EnumType.STRING)
still didn't work, so a custom UserType
was necessary. @Enumerated(EnumType.STRING)
仍然不起作用,因此需要自定义UserType
。 The key was to correctly override nullSafeSet
like in this answer https://stackoverflow.com/a/7614642/1090474 and similar implementations from the web.关键是正确覆盖
nullSafeSet
就像这个答案https://stackoverflow.com/a/7614642/1090474和来自网络的类似实现。
@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
wasn't cooperating: implements ParameterizedType
不合作:
org.hibernate.MappingException: type is not parameterized: full.path.to.PGEnumUserType
so I wasn't able to annotate the enum property like this:所以我无法像这样注释 enum 属性:
@Type(type = "full.path.to.PGEnumUserType",
parameters = {
@Parameter(name = "enumClass", value = "full.path.to.Move$Direction")
}
)
Instead, I declared the class like so:相反,我像这样声明了这个类:
public class PGEnumUserType<E extends Enum<E>> implements UserType
with a constructor:使用构造函数:
public PGEnumUserType(Class<E> enumClass) {
this.enumClass = enumClass;
}
which, unfortunately, means any other enum property similarly mapped will need a class like this:不幸的是,这意味着任何其他类似映射的枚举属性都需要一个这样的类:
public class HibernateDirectionUserType extends PGEnumUserType<Direction> {
public HibernateDirectionUserType() {
super(Direction.class);
}
}
Annotate the property and you're done.注释属性,你就完成了。
@Column(name = "directiontomove", nullable = false)
@Type(type = "full.path.to.HibernateDirectionUserType")
private Direction directionToMove;
EnhancedUserType
and the three methods it wants implemented EnhancedUserType
和它想要实现的三个方法
public String objectToSQLString(Object value) public String toXMLString(Object value) public String objectToSQLString(Object value)
didn't make any difference I could see, so I stuck with implements UserType
.我看不出有什么区别,所以我坚持使用
implements UserType
。
nullSafeGet
in the way the two linked solutions did.nullSafeGet
来使其特定于 postgres 可能不是绝对必要的。text
and the original code will work without extra work.text
和原始代码无需额外工作即可工作。As said in 8.7.3.如8.7.3所述。Type Safety of Postgres Docs :
Postgres 文档的类型安全:
If you really need to do something like that, you can either write a custom operator or add explicit casts to your query:
如果您真的需要做类似的事情,您可以编写自定义运算符或向查询添加显式转换:
so if you want a quick and simple workaround, do like this:所以如果你想要一个快速简单的解决方法,请这样做:
<query name="getAllMoves">
<![CDATA[
select move from Move move
where cast(directiontomove as text) = cast(:directionToMove as text)
]]>
</query>
Unfortunately, you can't do it simply with two colons :不幸的是, 你不能简单地用两个冒号来做到这一点:
Let me start off saying I was able to do this using Hibernate 4.3.x and Postgres 9.x.首先让我说我可以使用 Hibernate 4.3.x 和 Postgres 9.x 来做到这一点。
I based my solution off something similar to what you did.我的解决方案基于与您所做的类似的事情。 I believe if you combine
我相信如果你结合
@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")
})
and this和这个
@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);
}
}
You should be able to get something along the lines of this, without having to make either above change.您应该能够获得与此类似的东西,而无需进行上述任何更改。
@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")
})
I believe that this works since you're essentially telling Hibernate to map the enum to a type of other ( Types.OTHER == 1111
).我相信这是有效的,因为您实际上是在告诉 Hibernate 将枚举映射到其他类型(
Types.OTHER == 1111
)。 It may be a slightly brittle solution since the value of Types.OTHER
could change.这可能是一个稍微脆弱的解决方案,因为
Types.OTHER
的值可能会改变。 However, this would provide significantly less code overall.但是,这将提供明显更少的代码。
I have another approach with a persistence converter:我有另一种使用持久性转换器的方法:
import javax.persistence.Convert;
@Column(name = "direction", nullable = false)
@Converter(converter = DirectionConverter.class)
private Direction directionToMove;
This is a converter definition:这是一个转换器定义:
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);
}
}
It does not resolve mapping to psql enum type, but it can simulate @Enumerated(EnumType.STRING) or @Enumerated(EnumType.ORDINAL) in a good way.它不会解析到 psql 枚举类型的映射,但它可以很好地模拟 @Enumerated(EnumType.STRING) 或 @Enumerated(EnumType.ORDINAL)。
For ordinal use direction.ordinal() and Direction.values()[number].对于序数使用 direction.ordinal() 和 Direction.values()[number]。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.