PostgreSQL枚举和Java枚举之间的Hibernate映射

时间:2015-01-06 17:36:42

标签: java postgresql enums annotations hibernate-mapping

背景

  • Spring 3.x,JPA 2.0,Hibernate 4.x,Postgresql 9.x。
  • 使用我要映射到Postgresql枚举的枚举属性处理Hibernate映射类。

问题

使用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中查询按预期工作。
  • XML中查询中的硬编码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")
        })
    

    在看到https://stackoverflow.com/a/1604286/1090474

  • 之后,有两个第二个参数
  • 尝试在此答案https://stackoverflow.com/a/13241410/1090474中注释吸气剂和制定者。
  • Haven没有尝试EnumType.ORDINAL,因为我想坚持使用EnumType.STRING,这样不那么脆弱和灵活。

其他说明

JPA 2.1类型转换器不是必需的,但不管怎样都不是,因为我现在只使用JPA 2.0。

5 个答案:

答案 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)

HQL

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

<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,原始代码无需额外工作即可生效。

答案 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>

不幸的是,you can't do it simply with two colons

答案 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]。

相关问题