【问题标题】:Hibernate mapping between PostgreSQL enum and Java enumPostgreSQL 枚举和 Java 枚举之间的休眠映射
【发布时间】:2015-03-04 10:32:40
【问题描述】:

背景

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

问题

在枚举列上使用 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's JPA and Enums via @Enumerated documentation 中的 findByRating 示例给出了相同的异常。
  • 在 psql 中使用 select * from move where direction = 'LEFT'; 查询可以正常工作。
  • 在 XML 的查询中硬编码 where direction = 'FORWARD'
  • .setParameter("direction", direction.name()) 没有,与.setString().setText() 相同,异常更改为:

    Caused by: org.postgresql.util.PSQLException: ERROR: operator does not exist: movedirection = character varying
    

尝试解决问题

  • 自定义 UserType 由这个已接受的答案 https://stackoverflow.com/a/1594020/1090474 建议,以及:

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

【问题讨论】:

  • 这是一个写得很好的问题。我希望更多问题能够清楚地说明问题,显示相关代码并显示解决方法。干得好。
  • 截至 2017 年 2 月 14 日,@cslotty 的链接已失效。

标签: java postgresql enums annotations hibernate-mapping


【解决方案1】:

HQL

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

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

休眠映射

@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

所以我无法像这样注释枚举属性:

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

    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】:

      首先让我说我能够使用 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】:

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

        <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'
        )
        

        就是这样,它就像一个魅力。这是test on GitHub that proves it

        【讨论】:

        • 正如你所说,就像一个魅力!应该有更多的赞成票。
        • 这就是精神!
        • 太棒了,应该被接受为最佳答案,效果很好!赞成
        • @VladMihalcea:我对你的图书馆有点困惑。因为我不确定您的库现在是否支持 Postgress 枚举类型。那么,IFFF 我将库添加到我的项目中是否需要代码?造成混淆的原因是在您的文章中您解释了如何设置它,但仍然让我对功能产生怀疑。
        • 是的,它确实支持它。这个答案展示了如何编写一个支持 PostgreSQL Enum 的类型,而且,这正是 hibernate-types 库实际上消亡的原因。现在,你可以使用我写的类型,也可以自己写。就这么简单。
        【解决方案5】:

        我有另一种使用持久性转换器的方法:

        import javax.persistence.Column;
        import javax.persistence.Convert;
        
        @Column(name = "direction", nullable = false)
        @Convert(converter = DirectionConverter.class)
        private Direction directionToMove;
        

        这是一个转换器定义:

        import javax.persistence.AttributeConverter;
        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]。

        【讨论】:

          猜你喜欢
          • 2010-12-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-03-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-05-28
          相关资源
          最近更新 更多