PostgreSQL enum 和 Java enum 之间的 Hibernate 映射

背景

  • Spring 3.x, JPA 2.0, Hibernate 4.x, Postgresql 9.x.
  • 使用一个包含枚举属性的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
);

休眠映射类:

@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();
}

休眠 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的JPA和Enums中的示例,@Enumerated文档给出了相同的异常。findByRating
  • 在 psql 中使用查询按预期工作。select * from move where direction = 'LEFT';
  • 在 XML 中的查询中,方向 = 'FORWARD' 的硬编码工作正常。
  • .setParameter("direction", direction.name())不,与 和 相同,异常更改为:.setString().setText()

    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的映射,如更高评级但未被接受的答案所建议的那样,来自与上述相同的问题 https://stackoverflow.com/a/1604286/1090474,以及: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")
        })
    

    有和没有两个第二个参数,看到 https://stackoverflow.com/a/13241410/1090474

  • 尝试像这个答案中那样注释 getter 和 setter https://stackoverflow.com/a/20252215/1090474
  • 没有尝试过,因为我想坚持,它不那么脆弱,更灵活。EnumType.ORDINALEnumType.STRING

其他注意事项

JPA 2.1 类型转换器不应该是必需的,但无论如何都不是一个选项,因为我现在使用的是 JPA 2.0。


答案 1

您可以使用 Hibernate Types 依赖项通过 Maven Central 获取这些类型:

<dependency>
    <groupId>com.vladmihalcea</groupId>
    <artifactId>hibernate-types-52</artifactId>
    <version>${hibernate-types.version}</version>
</dependency>

如果您使用以下自定义类型轻松地将 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上的一个测试,可以证明这一点


答案 2

总部

正确别名并使用限定的属性名称是解决方案的第一部分。

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

休眠映射

@Enumerated(EnumType.STRING)仍然不起作用,所以需要一个习惯。关键是要正确覆盖这个答案中 https://stackoverflow.com/a/7614642/1090474 和来自Web的类似实现UserTypenullSafeSet

@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<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

  • 根据您使用类的方式,可能没有必要通过以两个链接解决方案的方式重写来使其特定于后gres。nullSafeGet
  • 如果你愿意放弃postgres枚举,你可以制作列,原始代码将工作,而无需额外的工作。text

推荐