Postgresql枚举和Java枚举之间的Hibernate映射
背景
- Spring 3.x,JPA 2.0,Hibernate 4.x,Postgresql 9.x.
- 使用我要映射到Postgresql枚举的枚举属性处理Hibernate映射类。
问题
在枚举列上使用where子句进行查询会引发exception。
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 getMoves(Direction directionToMove) { return (List) sessionFactory.getCurrentSession() .getNamedQuery("getAllMoves") .setParameter("directionToMove", directionToMove) .list(); }
Hibernate xml查询:
故障排除
- 通过
id
而不是enum查询按预期工作。 -
没有数据库交互的Java工作正常:
public List getMoves(Direction directionToMove) { List moves = new ArrayList(); Move move1 = new Move(); move1.setDirection(directionToMove); moves.add(move1); return moves; }
-
createQuery
而不是XML中的查询,类似于Apache的JPA中的findByRating
示例和Enums通过@Enumerated文档给出了相同的exception。 - 使用
select * from move where direction = 'LEFT';
查询psqlselect * from move where direction = 'LEFT';
按预期工作。 - 在XML中查询中的
where direction = 'FORWARD'
硬编码工作原理。 -
.setParameter("direction", direction.name())
不与.setString()
和.setString()
相同,exception更改为:Caused by: org.postgresql.util.PSQLException: ERROR: operator does not exist: movedirection = character varying
试图解决
-
自己接受的答案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;
-
使用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") })
在看到https://stackoverflow.com/a/13241410/1090474之后,有和没有两个第二个参数
- 尝试在这个答案https://stackoverflow.com/a/20252215/1090474中注释getter和setter。
- 没有尝试过
EnumType.ORDINAL
因为我想坚持使用EnumType.STRING
,它不那么脆弱且更灵活。
其他说明
JPA 2.1类型转换器不是必需的,但不管怎样都不是选项,因为我现在正在使用JPA 2.0。
您不必手动创建以下所有Hibernate类型。 您可以使用以下依赖项通过Maven Central获取它们:
com.vladmihalcea hibernate-types-52 ${hibernate-types.version}
有关更多信息,请查看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
枚举类型:
CREATE TYPE post_status_info AS ENUM ( 'PENDING', 'APPROVED', 'SPAM' )
就是这样,它就像一个魅力。 这是对GitHub的测试certificate了这一点 。
HQL
正确别名并使用限定属性名称是解决方案的第一部分。
Hibernate映射
@Enumerated(EnumType.STRING)
仍然无效,因此需要自定义UserType
。 关键是要正确覆盖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
不合作:
org.hibernate.MappingException: type is not parameterized: full.path.to.PGEnumUserType
所以我无法像这样注释枚举属性:
@Type(type = "full.path.to.PGEnumUserType", parameters = { @Parameter(name = "enumClass", value = "full.path.to.Move$Direction") } )
相反,我宣布这样的类是这样的:
public class PGEnumUserType> implements UserType
使用构造函数:
public PGEnumUserType(Class enumClass) { this.enumClass = enumClass; }
遗憾的是,这意味着任何其他类似映射的枚举属性都需要这样的类:
public class HibernateDirectionUserType extends PGEnumUserType { 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。 - 如果您愿意放弃postgres枚举,您可以使列
text
和原始代码无需额外工作。
如8.7.3所述。 Postgres文档的类型安全性 :
如果您确实需要执行类似的操作,可以编写自定义运算符或向查询添加显式强制转换:
因此,如果您想要快速简单的解决方法,请执行以下操作:
不幸的是, 你不能只用两个冒号做到这一点 :
让我开始说我能够使用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的值可能会改变。 但是,这将提供明显更少的代码。
我有另一种使用持久性转换器的方法:
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 { @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]。