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 au findByRating 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 avec EnumType.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.

17
demandé sur Community 2015-01-06 20:36:42

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.

10
répondu Vlad Mihalcea 2018-04-01 16:34:15

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 œuvre

    public 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.
8
répondu Kenny Linsky 2017-05-23 11:46:41

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:

1
répondu bdshadow 2017-05-23 11:54:23

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.

0
répondu lew 2015-04-28 18:28:34

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].

0
répondu Marko Novakovic 2018-08-02 15:32:46