62 votes

Hibernation : Comment définir la valeur NULL d'un paramètre de requête avec HQL ?

Comment puis-je attribuer la valeur "null" à un paramètre Hibernate ? Exemple :

Query query = getSession().createQuery("from CountryDTO c where c.status = :status  and c.type =:type")
.setParameter("status", status, Hibernate.STRING)
.setParameter("type", type, Hibernate.STRING);

Dans mon cas, la chaîne d'état peut être nulle. J'ai débogué cela et hibernate génère alors une chaîne/quête SQL comme ceci .... status = null ... Cependant, cela ne fonctionne pas dans MYSQL, car l'instruction SQL correcte doit être " status is null "(Mysql ne comprend pas status=null et l'évalue à false de sorte qu'aucun enregistrement ne sera jamais retourné pour la requête, selon les docs mysql que j'ai lu...)

Mes questions :

  1. Pourquoi est-ce que Hibernate traduire correctement une chaîne nulle en "est nulle" (et crée plutôt et à tort "=null") ?

  2. Quelle est la meilleure façon de réécrire cette requête pour qu'elle soit nulle ? Par nullsafe, j'entends que, dans le cas où la chaîne "status" est nulle, il faut créer un "is null" ?

1 votes

Pour tous ceux qui sont intéressés par une solution, je pense que l'API Critères est une voie à suivre. Mais je ne suis pas encore vraiment convaincu car elle bloque le code de façon horrible et l'utilisation de HQL serait beaucoup plus propre. Peut-être que la vraie solution est d'implémenter votre propre type Hibernate (j'en ai implémenté un pour les ENUMs mais ces types auto-implémentés, au moins les types de base ont de gros désavantages par rapport aux types Hibernate standard pour les requêtes avec HQL (à moins que vous n'étendiez aussi le parseur HQL ?) ce qui en fait un énorme projet et nécessite beaucoup de connaissances sur Hibernate...(suite dans la partie 2)

0 votes

Partie 2 : Peut-être que le plus simple (d'un autre côté, ce n'est certainement PAS une bonne pratique) est de modifier directement la classe Hibernate String Type elle-même et d'ajouter la logique manquante. Cela ne devrait être qu'une ligne de vue pour vérifier les valeurs null String et agir en conséquence...

5 votes

Je viens de tomber sur ça aussi... mon Dieu, c'est de la folie.

40voto

Sergey Demin Points 226
  1. Je crois qu'Hibernate traduit d'abord votre requête HQL en SQL et seulement après il essaie de lier vos paramètres. Ce qui signifie qu'il ne sera pas capable de réécrire la requête à partir de param = ? a param is null .

  2. Essayez d'utiliser l'api Criteria :

    Criteria c = session.createCriteria(CountryDTO.class);
    c.add(Restrictions.eq("type", type));
    c.add(status == null ? Restrictions.isNull("status") : Restrictions.eq("status", status));
    List result = c.list();

1 votes

Bonjour Sergey, Merci pour votre réponse. En fait, je l'ai implémenté (déjà avant que vous ne postiez votre réponse) également avec l'API Critères. Mais je pense néanmoins que ce n'est pas la "solution idéale" au problème. Pour être honnête, il arrive que les "bizarreries/bugs" n'aient pas de bonne solution..... J'ai marqué votre réponse comme étant la "solution" au problème.

0 votes

Vous êtes le bienvenu :) mais qu'attendez-vous d'une solution idéale ? Si vous êtes d'accord avec l'api Criterion au lieu de l'hql, vous pouvez toujours créer votre propre méthode d'usine pour le Criterion 'null-safe'.

0 votes

Excellente réponse ! Ce simple correctif m'a permis d'effectuer une requête sur un champ nullable sans avoir à construire une chaîne de caractères pour la requête !

34voto

egallardo Points 388

Il ne s'agit pas d'un problème spécifique à Hibernate (il s'agit simplement de la nature de SQL), et OUI, il existe une solution pour SQL et HQL :

@Peter Lang avait la bonne idée, et vous aviez la bonne requête HQL. Je suppose que vous aviez juste besoin d'une nouvelle exécution propre pour prendre en compte les changements de requête ;-)

Le code ci-dessous fonctionne parfaitement et il est idéal si vous conservez toutes vos requêtes dans orm.xml.

from CountryDTO c where ((:status is null and c.status is null) or c.status = :status) and c.type =:type

Si votre paramètre String est nul, la requête vérifiera si le statut de la ligne est également nul. Sinon, elle comparera avec le signe égal.

Notes :

Le problème peut être une bizarrerie spécifique de MySql. Je n'ai testé qu'avec Oracle.

La requête ci-dessus suppose qu'il y a des lignes de table où c.status est nul.

La clause where est hiérarchisée de manière à ce que le paramètre soit vérifié en premier.

Le nom du paramètre "type" est peut-être un mot réservé en SQL, mais cela ne devrait pas avoir d'importance puisqu'il est remplacé avant l'exécution de la requête.

Si vous souhaitez sauter la clause :status where_clause, vous pouvez coder de la manière suivante :

from CountryDTO c where (:status is null or c.status = :status) and c.type =:type

et c'est équivalent à :

sql.append(" where ");
if(status != null){
  sql.append(" c.status = :status and ");
}
sql.append(" c.type =:type ");

0 votes

Pourquoi pas from CountryDTO c where (:status is not null and c.status = :status) and c.type =:type ?

0 votes

@Alex78191 Avec or que la requête donnera : from CountryDTO c where (true) and c.type =:type et la vôtre cédera : from CountryDTO c where (false) and c.type =:type ce qui ne vous donnera aucune rangée.

0 votes

@Alex78191 Vous pouvez aussi essayer ceci : select 'data' from dual where (1=2) et ceci : select 'data' from dual where (1=1) pour voir la différence.

14voto

skaffman Points 197885

El javadoc pour setParameter(String, Object) est explicite, indiquant que la valeur de l'objet ne doit pas être nulle. C'est dommage qu'il ne lève pas une exception si un null est passé, cependant.

Une alternative est setParameter(String, Object, Type) qui fait autorisent les valeurs nulles, bien que je ne sois pas sûr de ce que les Type serait le plus approprié ici.

0 votes

Bonjour skaffman. Merci beaucoup pour votre aide ! En posant ma question, je pensais que j'étais trop bête pour trouver une solution. Mais il semble que ce soit une véritable "bizarrerie" d'Hibernate. Il est fort probable que je doive écrire mon propre type de chaîne de caractères nullable ou que je doive passer aux requêtes de critères... Après avoir creusé plus profondément, il semble que l'implémentation actuelle du type Hibernat.STRING n'est pas nullable : docjar.com/html/api/org/hibernate/type/StringType.java.html . (Mais le JConnecteur MySQL ne devrait-il pas implémenter correctement le PreparedStatment.setString ?). Merci Tim.

2 votes

Pour information, il s'agit en fait d'une "fonctionnalité" de JDBC : bugs.sun.com/bugdatabase/view_bug.do?bug_id=4312435 Hibernate utilise la méthode JDBC. PreparedStatement.setNull() de lier null paramètres

5voto

Arjan Points 7154

HQL supporte coalescer ce qui permet de contourner les problèmes :

where coalesce(c.status, 'no-status') = coalesce(:status, 'no-status')

4voto

Peter Lang Points 25877

Je n'ai pas essayé, mais que se passe-t-il lorsque vous utilisez :status deux fois pour vérifier si NULL ?

Query query = getSession().createQuery(
     "from CountryDTO c where ( c.status = :status OR ( c.status IS NULL AND :status IS NULL ) ) and c.type =:type"
)
.setParameter("status", status, Hibernate.STRING)
.setParameter("type", type, Hibernate.STRING);

0 votes

Hellp Peter, merci beaucoup pour votre aide ! Je ne sais pas pourquoi, mais il semble qu'Hibernate évalue toujours le ( c.status = :status OR ( c.status IS NULL AND :status IS NULL ) à c.status = null (selon mon journal de requête mysql). J'ai également changé l'ordre en (( c.status IS NULL AND :status IS NULL ) OR c.status = :status) avec le même résultat. Merci à Tim

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X