L'interrogation avec une clause where dans la colonne enum lève une exception.
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
);
Classe mappée 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 qui appelle la requête:
public List<Move> getMoves(Direction directionToMove) {
return (List<Direction>) sessionFactory.getCurrentSession()
.getNamedQuery("getAllMoves")
.setParameter("directionToMove", directionToMove)
.list();
}
Requête XML Hibernate:
<query name="getAllMoves">
<![CDATA[
select move from Move move
where directiontomove = :directionToMove
]]>
</query>
id
au lieu de l'énumération fonctionne comme prévu.Java sans interaction avec la base de données fonctionne 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
au lieu d'avoir la requête en XML, similaire à l'exemple findByRating
dans JPA d'Apache et Enums via @Enumerated documentation a donné la même exception.select * from move where direction = 'LEFT';
fonctionne comme prévu.where direction = 'FORWARD'
dans la requête dans les travaux XML..setParameter("direction", direction.name())
ne fonctionne pas, comme avec .setString()
et .setText()
, les exceptions se modifient comme suit:
Caused by: org.postgresql.util.PSQLException: ERROR: operator does not exist: movedirection = character varying
Personnalisé UserType
comme suggéré par cette réponse acceptée https://stackoverflow.com/a/1594020/1090474 avec:
@Column(name = "direction", nullable = false)
@Enumerated(EnumType.STRING) // tried with and without this line
@Type(type = "full.path.to.HibernateMoveDirectionUserType")
private Direction directionToMove;
Mappage avec EnumType
d'Hibernate comme suggéré par une réponse mieux notée mais non acceptée https://stackoverflow.com/a/1604286/1090474 de la même question que ci-dessus, avec:
@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")
})
Avec et sans les deux seconds paramètres, après avoir vu https://stackoverflow.com/a/13241410/1090474
EnumType.ORDINAL
parce que je veux m'en tenir à EnumType.STRING
, qui est moins fragile et plus flexible.Un convertisseur de type JPA 2.1 ne devrait pas être nécessaire, mais ce n'est pas une option, car je suis pour l'instant sur JPA 2.0.
L'aliasing correct et l'utilisation du nom de propriété qualifié constituaient la première partie de la solution.
<query name="getAllMoves">
<![CDATA[
from Move as move
where move.directionToMove = :direction
]]>
</query>
@Enumerated(EnumType.STRING)
ne fonctionnait toujours pas, donc une UserType
personnalisée était nécessaire. La clé consistait à remplacer correctement nullSafeSet
comme dans cette réponse https://stackoverflow.com/a/7614642/1090474 et similaireimplémentations à partir du 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);
}
}
implements ParameterizedType
n'a pas coopéré:
org.hibernate.MappingException: type is not parameterized: full.path.to.PGEnumUserType
donc je n'ai pas pu annoter la propriété enum comme ceci:
@Type(type = "full.path.to.PGEnumUserType",
parameters = {
@Parameter(name = "enumClass", value = "full.path.to.Move$Direction")
}
)
Au lieu de cela, j'ai déclaré la classe comme suit:
public class PGEnumUserType<E extends Enum<E>> implements UserType
avec un constructeur:
public PGEnumUserType(Class<E> enumClass) {
this.enumClass = enumClass;
}
ce qui, malheureusement, signifie que toute autre propriété enum mappée de la même manière aura besoin d'une classe comme celle-ci:
public class HibernateDirectionUserType extends PGEnumUserType<Direction> {
public HibernateDirectionUserType() {
super(Direction.class);
}
}
Annotez la propriété et vous avez terminé.
@Column(name = "directiontomove", nullable = false)
@Type(type = "full.path.to.HibernateDirectionUserType")
private Direction directionToMove;
EnhancedUserType
et les trois méthodes qu'il souhaite implémenter
public String objectToSQLString(Object value)
public String toXMLString(Object value)
public String objectToSQLString(Object value)
je n'ai vu aucune différence, je me suis donc retrouvé avec implements UserType
.
nullSafeGet
de la même manière que les deux solutions liées.text
et le code d'origine fonctionnera sans travail supplémentaire.Il n'est pas nécessaire de créer manuellement tous les types Hibernate suivants. Vous pouvez simplement les obtenir via Maven Central en utilisant la dépendance suivante:
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>${hibernate-types.version}</version>
</dependency>
Pour plus d'informations, consultez le projet open-source hibernate-types .
Comme _ expliqué dans cet article , si vous mappez facilement Java Enum à un type de colonne PostgreSQL Enum à l'aide du type personnalisé suivant:
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
);
}
}
}
Pour l'utiliser, vous devez annoter le champ avec l'annotation Hibernate @Type
, comme illustré dans l'exemple suivant:
@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
}
Ce mappage suppose que vous avez le type post_status_info
enum dans PostgreSQL:
CREATE TYPE post_status_info AS ENUM (
'PENDING',
'APPROVED',
'SPAM'
)
Ça y est, ça marche à merveille. Voici un test sur GitHub qui le prouve .
Comme indiqué dans 8.7.3. Type Sécurité des documents Postgres :
Si vous avez vraiment besoin de faire quelque chose comme ça, vous pouvez écrire un opérateur personnalisé ou ajouter des conversions explicites à votre requête:
donc si vous voulez une solution de contournement rapide et simple, procédez comme suit:
<query name="getAllMoves">
<![CDATA[
select move from Move move
where cast(directiontomove as text) = cast(:directionToMove as text)
]]>
</query>
Malheureusement, vous ne pouvez pas le faire simplement avec deux colons :
J'ai une autre approche avec un convertisseur de persistance:
import javax.persistence.Convert;
@Column(name = "direction", nullable = false)
@Converter(converter = DirectionConverter.class)
private Direction directionToMove;
Ceci est une définition de convertisseur:
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);
}
}
Il ne résout pas le mappage vers le type psum enum, mais il peut simuler correctement @Enumerated (EnumType.STRING) ou @Enumerated (EnumType.ORDINAL).
Pour ordinal, utilisez direction.ordinal () et Direction.values () [nombre].
Permettez-moi de commencer en disant que j'ai pu le faire avec Hibernate 4.3.x et Postgres 9.x.
J'ai basé ma solution sur quelque chose de similaire à ce que vous avez fait. Je crois que si vous combinez
@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")
})
et ça
@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);
}
}
Vous devriez pouvoir obtenir quelque chose dans le même ordre, sans avoir à apporter les modifications ci-dessus.
@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")
})
Je pense que cela fonctionne puisque vous demandez essentiellement à Hibernate de mapper l'énum sur un type d'autre (Types.OTHER == 1111). Il peut s’agir d’une solution légèrement fragile, car la valeur de Types.OTHER pourrait changer. Cependant, cela fournirait beaucoup moins de code dans l'ensemble.