出身背景

  • 春天3.x、 JPA 2.0,Hibernate 4.x、 Postgresql 9.十、
  • 正在处理Hibernate映射类,该类具有要映射到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();
}

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示例给出了相同的异常.
  • 在psql中查询select * from move where direction = 'LEFT';项工作正常.
  • Hardcoding 100 in the query in the XML works.
  • .setString().setText()相同,.setParameter("direction", direction.name())不会将异常更改为:

    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,它不那么脆弱,也更灵活.

其他注释

JPA2.1类型转换器应该不是必需的,但无论如何都不是一个选项,因为我现在正在使用JPA2.0.

推荐答案

HQL

正确的别名和使用qualified property name是解决方案的第一部分.

<query name="getAllMoves">
    <![CDATA[
        from Move as move
        where move.directionToMove = :direction
    ]]>
</query>

Hibernate 映射

@Enumerated(EnumType.STRING)仍然不起作用,所以需要定制UserType.关键是要正确地覆盖nullSafeSet,就像这个答案https://stackoverflow.com/a/7614642/1090474similar 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.
  • 如果你愿意放弃postgres枚举,你可以将列设为text,原始代码将不需要额外的工作就可以工作.

Postgresql相关问答推荐

如何在Common Lisp中使用Postmodern在表更改时获得通知?

如何在docker-compose中使用docker secrets设置ConnectionString?

如何查找具有包含花括号内值且遵循模式的属性的顶点?

Postgres 唯一 JSON 数组聚合值

用于生产的 Rails 性能调优?

Postgres 会话处于空闲状态,query = COMMIT 或 ROLLBACK

PostgreSQL - 我应该如何使用 first_value()?

如何从 .sql postgresql 备份恢复单个表?

如何让 Flask SQLAlchemy 重用数据库连接?

需要将整个 postgreSQL 数据库加载到 RAM 中

为什么 sqlalchemy 的默认列值不起作用

sql语句错误:column .. does not exist

Postgres 删除表语法错误

使用 PostGIS 查找给定点的 n 个最近邻?

如何启动 Postgres 服务器?

使用 PostgreSQL 创建数据透视表

PostgreSQL 嵌套 CTE 和 UNION

PostgreSQL 条件 where 子句

PostgreSQL 字符串(255) 限制 - Rails、Ruby 和 Heroku

使用 PostgreSQL 进行字母数字排序