Cartographie de l'hibernation entre le Postgresql enum et le Java enum
Background
- printemps 3.X, JPA 2.0, hibernation 4.x, Postgresql 9.x.
- travailler sur une classe cartographiée D'hibernation avec une propriété enum que je veux cartographier à un PostgreSQL enum.
Problème
questionnement avec une clause où sur la colonne enum lance 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.
Code (très simplifié)
SQL:
create type movedirection as enum (
'FORWARD', 'LEFT'
);
CREATE TABLE move
(
id serial NOT NULL PRIMARY KEY,
directiontomove movedirection NOT NULL
);
Hibernate mappé classe:
@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();
}
Hibernate requête xml:
<query name="getAllMoves">
<![CDATA[
select move from Move move
where directiontomove = :directionToMove
]]>
</query>
Dépannage
- l'Interrogation par
id
au lieu de l'enum fonctionne comme prévu. Java sans interaction de base de données fonctionne très 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 aufindByRating
exemple Apache JPA et les Énumérations via @Énumérés documentation a donné la même exception.- Interrogation dans psql
select * from move where direction = 'LEFT';
fonctionne comme prévu. - coder en dur
where direction = 'FORWARD'
dans la requête dans les travaux XML. .setParameter("direction", direction.name())
n'est pas, même chose avec.setString()
et.setText()
, à l'exception des modifications:Caused by: org.postgresql.util.PSQLException: ERROR: operator does not exist: movedirection = character varying
tentatives de résolution
Personnalisé
UserType
tel que suggéré par cette réponse acceptée https://stackoverflow.com/a/1594020/1090474 ainsi que:@Column(name = "direction", nullable = false) @Enumerated(EnumType.STRING) // tried with and without this line @Type(type = "full.path.to.HibernateMoveDirectionUserType") private Direction directionToMove;
cartographie avec hibernation
EnumType
tel que suggéré par une réponse mieux cotée mais non acceptée https://stackoverflow.com/a/1604286/1090474 from the same question as above, along with:@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 la seconde deux paramètres, après avoir vu https://stackoverflow.com/a/13241410/1090474
- Essayé Annoter le getter et le setter comme dans cette réponse https://stackoverflow.com/a/20252215/1090474.
- N'ont pas essayé
EnumType.ORDINAL
parce que je veux rester avecEnumType.STRING
, qui est moins cassant et plus souple.
autres notes
un convertisseur de Type JPA 2.1 ne devrait pas être nécessaire, mais n'est pas une option de toute façon, puisque je suis sur JPA 2.0 pour le moment.
5 réponses
vous n'avez pas à créer tous les types D'hibernation suivants manuellement. 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'information, consultez le mise en veille prolongée-types de projet open-source.
expliqué dans cet article, si vous pouvez facilement mapper Java Enum à un type de colonne PostgreSQL Enum en utilisant le 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 terrain avec the Hibernate @Type
annotation telle qu'illustrée 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 mapping suppose que vous avez le post_status_info
type enum dans PostgreSQL:
CREATE TYPE post_status_info AS ENUM (
'PENDING',
'APPROVED',
'SPAM'
)
ça y est, il fonctionne comme un charme. Voici un test sur GitHub qui le prouve.
HQL
Aliasing correctement et en utilisant le nom du bien admissible était la première partie de la solution.
<query name="getAllMoves">
<![CDATA[
from Move as move
where move.directionToMove = :direction
]]>
</query>
cartographie de L'hibernation
@Enumerated(EnumType.STRING)
n'a toujours pas fonctionné, donc une coutume UserType
a été nécessaire. La clé était de remplacer correctement nullSafeSet
comme dans cette réponse https://stackoverflow.com/a/7614642/1090474 et similairesimplémentations à partir 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);
}
}
Détour
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é d'enum cartographié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);
}
}
Annotation
Annoter l' de la propriété et vous avez terminé.
@Column(name = "directiontomove", nullable = false)
@Type(type = "full.path.to.HibernateDirectionUserType")
private Direction directionToMove;
autres notes
EnhancedUserType
et les trois méthodes qu'il veut en œuvrepublic String objectToSQLString(Object value) public String toXMLString(Object value) public String objectToSQLString(Object value)
cela n'a fait aucune différence que je puisse voir, donc je suis resté avec
implements UserType
.- selon la façon dont vous utilisez la classe, il pourrait ne pas être strictement nécessaire de la rendre spécifique à postgres en remplaçant
nullSafeGet
de la façon dont les deux solutions liées l'ont fait. - si vous êtes prêt à abandonner la postgres enum, vous pouvez faire la colonne
text
et le code original fonctionnera sans travail supplémentaire.
Comme dit dans 8.7.3. Type de Sécurité de Postgres Docs:
si vous avez vraiment besoin de faire quelque chose comme ça, vous pouvez soit écrire un opérateur personnalisé ou ajouter des casts explicites à votre requête:
alors si vous voulez une solution rapide et simple, faites comme ceci:
<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 tout simplement avec deux points:
permettez-moi de commencer en disant que j'ai été capable de faire cela en utilisant l'hibernation 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
@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 être capable d'obtenir quelque chose dans le sens de ceci, sans avoir à faire l'un ou l'autre des changements 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 crois que cela fonctionne puisque vous dites essentiellement Hibernate de cartographier l'enum à un type d'autre (Type.Autres = = 1111). Il peut s'agir d'une solution légèrement fragile étant donné la valeur des Types.D'autres pourraient changer. Cependant, cela fournirait beaucoup moins de code dans l'ensemble.
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;
C'est un convertisseur définition:
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 la mise en correspondance au type enum de psql, mais il peut simuler @Enumentype.Chaîne de caractères) ou @Enumerated (EnumType.ORDINAL) dans le bon sens.
pour la direction ordinale.ordinal () et Direction.les valeurs de()[numéro].