type how enumerated enum column annotation java postgresql enums annotations hibernate-mapping

how - Asignación de hibernación entre PostgreSQL enum y Java enum



hibernate postgresql enum type (5)

HQL

La creación de alias correctamente y el uso del nombre de propiedad calificado fue la primera parte de la solución.

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

Mapeo de hibernación

@Enumerated(EnumType.STRING) aún no funcionaba, por UserType era necesario un UserType personalizado. La clave fue anular correctamente nullSafeSet como en esta respuesta https://stackoverflow.com/a/7614642/1090474 e implementations similar de la web.

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

Desvío

implements ParameterizedType no estaba cooperando:

org.hibernate.MappingException: type is not parameterized: full.path.to.PGEnumUserType

así que no pude anotar la propiedad enum como esta:

@Type(type = "full.path.to.PGEnumUserType", parameters = { @Parameter(name = "enumClass", value = "full.path.to.Move$Direction") } )

En su lugar, declaré la clase como tal:

public class PGEnumUserType<E extends Enum<E>> implements UserType

con un constructor:

public PGEnumUserType(Class<E> enumClass) { this.enumClass = enumClass; }

lo que, desafortunadamente, significa que cualquier otra propiedad enum asignada de manera similar necesitará una clase como esta:

public class HibernateDirectionUserType extends PGEnumUserType<Direction> { public HibernateDirectionUserType() { super(Direction.class); } }

Anotación

Anota la propiedad y listo.

@Column(name = "directiontomove", nullable = false) @Type(type = "full.path.to.HibernateDirectionUserType") private Direction directionToMove;

Otras notas

  • EnhancedUserType y los tres métodos que quiere implementar

    public String objectToSQLString(Object value) public String toXMLString(Object value) public String objectToSQLString(Object value)

    No había ninguna diferencia que pudiera ver, así que me quedé con los implements UserType .

  • Dependiendo de cómo utilice la clase, puede que no sea estrictamente necesario hacerlo específico para postgres al anular nullSafeGet en la forma en que lo nullSafeGet las dos soluciones vinculadas.
  • Si está dispuesto a renunciar a la enumeración de postgres, puede hacer que el text la columna y el código original funcionen sin trabajo adicional.

Fondo

  • Spring 3.x, JPA 2.0, Hibernate 4.x, Postgresql 9.x.
  • Trabajando en una clase asignada de Hibernate con una propiedad de enumeración que quiero asignar a una enumeración de Postgresql.

Problema

La consulta con una cláusula where en la columna enum arroja una excepción.

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.

Código (muy simplificado)

SQL:

create type movedirection as enum ( ''FORWARD'', ''LEFT'' ); CREATE TABLE move ( id serial NOT NULL PRIMARY KEY, directiontomove movedirection NOT NULL );

Hibernate asignó clase:

@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 que llama a la consulta:

public List<Move> getMoves(Direction directionToMove) { return (List<Direction>) sessionFactory.getCurrentSession() .getNamedQuery("getAllMoves") .setParameter("directionToMove", directionToMove) .list(); }

Hibernate consulta xml:

<query name="getAllMoves"> <![CDATA[ select move from Move move where directiontomove = :directionToMove ]]> </query>

Solución de problemas

  • La consulta por id lugar de la enumeración funciona como se esperaba.
  • Java sin interacción con la base de datos funciona bien:

    public List<Move> getMoves(Direction directionToMove) { List<Move> moves = new ArrayList<>(); Move move1 = new Move(); move1.setDirection(directionToMove); moves.add(move1); return moves; }

  • createQuery lugar de tener la consulta en XML, similar al ejemplo findByRating en JPA y Enums de Apache a través de la documentación @Enumerated dio la misma excepción.
  • Consultando en psql con select * from move where direction = ''LEFT''; Funciona como se espera.
  • Hardcoding where direction = ''FORWARD'' en la consulta en el XML funciona.
  • .setParameter("direction", direction.name()) no lo hace, lo mismo que con .setString() y .setText() , la excepción cambia a:

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

Intentos de resolución

  • UserType personalizado como lo sugiere esta respuesta aceptada https://stackoverflow.com/a/1594020/1090474 junto con:

    @Column(name = "direction", nullable = false) @Enumerated(EnumType.STRING) // tried with and without this line @Type(type = "full.path.to.HibernateMoveDirectionUserType") private Direction directionToMove;

  • Asignación con EnumType de Hibernate como lo sugiere una respuesta más alta pero no aceptada https://stackoverflow.com/a/1604286/1090474 de la misma pregunta que arriba, junto con:

    @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") })

    Con y sin los dos segundos parámetros, después de ver https://stackoverflow.com/a/13241410/1090474

  • Intenté anotar el getter y el setter como en esta respuesta https://stackoverflow.com/a/20252215/1090474 .
  • No he probado EnumType.ORDINAL porque quiero seguir con EnumType.STRING , que es menos quebradizo y más flexible.

Otras notas

Un convertidor de tipo JPA 2.1 no debería ser necesario, pero tampoco es una opción, ya que estoy en JPA 2.0 por ahora.



No es necesario crear todos los siguientes tipos de hibernación manualmente. Simplemente puede obtenerlos a través de Maven Central usando la siguiente dependencia:

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

Para obtener más información, echa un vistazo al proyecto de código abierto de tipo hibernate .

Como expliqué en este artículo , si asigna fácilmente Java Enum a un tipo de columna PostgreSQL Enum utilizando el siguiente Tipo personalizado:

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

Para usarlo, debe anotar el campo con la anotación Hibernate @Type como se ilustra en el siguiente ejemplo:

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

Esta asignación asume que tiene el tipo de enumeración post_status_info en PostgreSQL:

CREATE TYPE post_status_info AS ENUM ( ''PENDING'', ''APPROVED'', ''SPAM'' )

Eso es, funciona como un encanto. Aquí hay una prueba en GitHub que lo demuestra .


Permítanme comenzar diciendo que pude hacer esto utilizando Hibernate 4.3.xy Postgres 9.x.

Basé mi solución en algo similar a lo que hiciste. Creo que si combinas

@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") })

y esto

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

Debería poder obtener algo parecido a esto, sin tener que hacer ninguno de los cambios anteriores.

@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") })

Creo que esto funciona ya que básicamente le está diciendo a Hibernate que asigne la enumeración a un tipo de otro (Types.OTHER == 1111). Puede ser una solución ligeramente quebradiza ya que el valor de los Tipos. OTRA podría cambiar. Sin embargo, esto proporcionaría significativamente menos código en general.


Tengo otro enfoque con un convertidor de persistencia:

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

Esta es una definición de convertidor:

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

No resuelve la asignación al tipo de enumeración psql, pero puede simular @Enumerated (EnumType.STRING) o @Enumerated (EnumType.ORDINAL) de una buena manera.

Para uso ordinal direction.ordinal () y Direction.values ​​() [número].