使用enum列上的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();
}
Hibernate xml查询:
<query name="getAllMoves">
<![CDATA[
select move from Move move
where directiontomove = :directionToMove
]]>
</query>
id
查询而不是枚举按预期工作。没有数据库交互的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进行查询,与Apache's JPA and Enums via @Enumerated documentation中的findByRating
示例相似,但也提供了相同的例外。select * from move where direction = 'LEFT';
在psql中查询按预期工作。where direction = 'FORWARD'
。 .setParameter("direction", direction.name())
与.setString()
和.setText()
相同,异常更改为:
Caused by: org.postgresql.util.PSQLException: ERROR: operator does not exist: movedirection = character varying
根据此接受的答案[{3}}建议的自定义UserType
以及:
@Column(name = "direction", nullable = false)
@Enumerated(EnumType.STRING) // tried with and without this line
@Type(type = "full.path.to.HibernateMoveDirectionUserType")
private Direction directionToMove;
根据上述同一问题中较高评分但未被接受的答案https://stackoverflow.com/a/1594020/1090474与Hibernate&#39; EnumType
进行映射,以及:
@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")
})
EnumType.ORDINAL
,因为我想坚持使用EnumType.STRING
,这样不那么脆弱和灵活。JPA 2.1类型转换器不是必需的,但不管怎样都不是,因为我现在只使用JPA 2.0。
答案 0 :(得分:20)
您不必手动创建以下所有Hibernate类型。您可以使用以下依赖项通过Maven Central获取它们:
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>${hibernate-types.version}</version>
</dependency>
有关详细信息,请查看hibernate-types open-source project。
作为explained in this article,如果使用以下自定义类型轻松将Java Enum映射到PostgreSQL枚举列类型:
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
枚举类型:
CREATE TYPE post_status_info AS ENUM (
'PENDING',
'APPROVED',
'SPAM'
)
就是这样,它就像一个魅力。这是test on GitHub that proves it。
答案 1 :(得分:9)
正确别名并使用限定属性名称是解决方案的第一部分。
<query name="getAllMoves">
<![CDATA[
from Move as move
where move.directionToMove = :direction
]]>
</query>
@Enumerated(EnumType.STRING)
仍然无效,因此需要自定义UserType
。关键是要正确覆盖nullSafeSet
,例如来自网络的此答案https://stackoverflow.com/a/7614642/1090474和similar implementations。
@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;
}
不幸的是,意味着任何其他类似映射的枚举属性都需要这样的类:
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
。
nullSafeGet
来使其特定于postgres。text
,原始代码无需额外工作即可生效。答案 2 :(得分:1)
正如8.7.3. Type Safety of Postgres Docs所说:
如果您真的需要做类似的事情,可以编写自定义运算符或向查询添加显式强制转换:
因此,如果您想要快速简单的解决方法,请执行以下操作:
<query name="getAllMoves">
<![CDATA[
select move from Move move
where cast(directiontomove as text) = cast(:directionToMove as text)
]]>
</query>
答案 3 :(得分:0)
让我开始说我能够使用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的值可能会改变。但是,这将提供更少的代码。
答案 4 :(得分:0)
我对持久性转换器有另一种方法:
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]。