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 implementarpublic 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 lonullSafeGet
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 ejemplofindByRating
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 conEnumType.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.
Como se dice en 8.7.3. Tipo de seguridad de los documentos de Postgres :
Si realmente necesita hacer algo así, puede escribir un operador personalizado o agregar conversiones explícitas a su consulta:
así que si quieres una solución rápida y sencilla, haz esto:
<query name="getAllMoves">
<![CDATA[
select move from Move move
where cast(directiontomove as text) = cast(:directionToMove as text)
]]>
</query>
Desafortunadamente, no puedes hacerlo simplemente con dos puntos :
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].