2009-03-23 12 views
15

Pour cette question, nous voulons éviter d'avoir à écrire une requête spéciale car la requête doit être différente sur plusieurs bases de données. En utilisant uniquement des critères d'hibernation, nous voulons pouvoir échapper des caractères spéciaux.En utilisant les critères d'hibernation, y a-t-il un moyen d'échapper des caractères spéciaux?

Cette situation est la raison pour avoir besoin de la capacité d'échapper à des caractères spéciaux:

Supposons que nous ayons table « foo » dans la base de données. La table 'foo' contient seulement 1 champ, appelé 'nom'. Le champ 'nom' peut contenir des caractères pouvant être considérés comme spéciaux dans une base de données. Deux exemples d'un tel nom sont 'name_1' et 'name% 1'. Les '_' et '%' sont des caractères spéciaux, au moins dans Oracle. Si un utilisateur souhaite rechercher l'un de ces exemples après leur saisie dans la base de données, des problèmes peuvent survenir. 'SearchValue' est la valeur que l'utilisateur a donné à l'application pour sa recherche. Si l'utilisateur veut rechercher '%', l'utilisateur sera renvoyé avec chaque entrée 'foo' dans la base de données. En effet, le caractère « % » représente « un certain nombre de caractères » joker pour la correspondance de chaîne et le code SQL qui hibernent produit ressemblera:

select * from foo where name like '%' 

Est-il possible de dire en veille prolongée pour échapper à certains personnages, ou pour créer une solution de contournement qui n'est pas spécifique au type de base de données?

Répondre

10

Les constructeurs de LikeExpression sont tous protégés, donc ce n'est pas une option viable. En outre, il a problems of its own.

Un collègue et moi avons créé un patch qui fonctionne plutôt bien. L'essentiel du patch est que pour le constructeur LikeExpression qui consomme un MatchMode, nous échappons aux caractères spéciaux. Pour le constructeur qui consomme un caractère (le caractère d'échappement), nous supposons que l'utilisateur échappe lui-même les caractères spéciaux.

Nous avons également paramétré le caractère d'échappement pour s'assurer qu'il ne peut pas corrompre la requête SQL s'ils utilisent quelque chose comme \ ou un guillemet.

package org.hibernate.criterion; 

import org.hibernate.Criteria; 
import org.hibernate.HibernateException; 
import org.hibernate.dialect.Dialect; 
import org.hibernate.engine.TypedValue; 

public class LikeExpression implements Criterion { 
    private final String propertyName; 
    private final String value; 
    private final Character escapeChar; 

    protected LikeExpression(
      String propertyName, 
      Object value) { 
     this(propertyName, value.toString(), (Character) null); 
    } 

    protected LikeExpression(
      String propertyName, 
      String value, 
      MatchMode matchMode) { 
     this(propertyName, matchMode.toMatchString(value 
       .toString() 
       .replaceAll("!", "!!") 
       .replaceAll("%", "!%") 
       .replaceAll("_", "!_")), '!'); 
    } 

    protected LikeExpression(
      String propertyName, 
      String value, 
      Character escapeChar) { 
     this.propertyName = propertyName; 
     this.value = value; 
     this.escapeChar = escapeChar; 
    } 

    public String toSqlString(
      Criteria criteria, 
      CriteriaQuery criteriaQuery) throws HibernateException { 
     Dialect dialect = criteriaQuery.getFactory().getDialect(); 
     String[] columns = criteriaQuery.getColumnsUsingProjection(criteria, propertyName); 
     if (columns.length != 1) { 
      throw new HibernateException("Like may only be used with single-column properties"); 
     } 
     String lhs = lhs(dialect, columns[0]); 
     return lhs + " like ?" + (escapeChar == null ? "" : " escape ?"); 

    } 

    public TypedValue[] getTypedValues(
      Criteria criteria, 
      CriteriaQuery criteriaQuery) throws HibernateException { 
     return new TypedValue[] { 
       criteriaQuery.getTypedValue(criteria, propertyName, typedValue(value)), 
       criteriaQuery.getTypedValue(criteria, propertyName, escapeChar.toString()) 
     }; 
    } 

    protected String lhs(Dialect dialect, String column) { 
     return column; 
    } 

    protected String typedValue(String value) { 
     return value; 
    } 

} 

Si vous vous demandez ce que les LHS et les méthodes TypedValue sont pour, la nouvelle IlikeExpression devrait répondre à ces questions.

package org.hibernate.criterion; 

import org.hibernate.dialect.Dialect; 

public class IlikeExpression extends LikeExpression { 

    protected IlikeExpression(
      String propertyName, 
      Object value) { 
     super(propertyName, value); 
    } 

    protected IlikeExpression(
      String propertyName, 
      String value, 
      MatchMode matchMode) { 
     super(propertyName, value, matchMode); 

    } 

    protected IlikeExpression(
      String propertyName, 
      String value, 
      Character escapeChar) { 
     super(propertyName, value, escapeChar); 
    } 

    @Override 
    protected String lhs(Dialect dialect, String column) { 
     return dialect.getLowercaseFunction() + '(' + column + ')'; 
    } 

    @Override 
    protected String typedValue(String value) { 
     return super.typedValue(value).toLowerCase(); 
    } 

} 

Après cela, la seule chose qui reste est de faire restrictions utiliser ces nouvelles classes:

public static Criterion like(String propertyName, Object value) { 
    return new LikeExpression(propertyName, value); 
} 

public static Criterion like(String propertyName, String value, MatchMode matchMode) { 
    return new LikeExpression(propertyName, value, matchMode); 
} 

public static Criterion like(String propertyName, String value, Character escapeChar) { 
    return new LikeExpression(propertyName, value, escapeChar); 
} 

public static Criterion ilike(String propertyName, Object value) { 
    return new IlikeExpression(propertyName, value); 
} 

public static Criterion ilike(String propertyName, String value, MatchMode matchMode) { 
    return new IlikeExpression(propertyName, value, matchMode); 
} 

public static Criterion ilike(String propertyName, String value, Character escapeChar) { 
    return new IlikeExpression(propertyName, value, escapeChar); 
} 

Edit: Oh oui. Cela fonctionne pour Oracle. Cependant, nous ne sommes pas sûrs des autres bases de données.

1

Si vous utilisez directement LikeExpression, cela vous permet de spécifier le caractère d'échappement. Je suppose que cela devrait être tout ce dont vous avez besoin.

+0

Merci. J'espère pouvoir trouver le temps d'essayer ça plus tard aujourd'hui. Je vais mettre à jour après l'avoir essayé. –

+0

IlikeExpression ne l'a pas cependant. –

+0

Tous les constructeurs de LikeExpression sont protégés, vous devez sous-classer et créer un constructeur public. – EkcenierK

3

Ce n'est pas une façon très propre de le faire mais sqlRestrinction devrait être plus facile:

criterions.add(Restrictions.sqlRestriction(columnName+ " ilike '!%' escape '!'")); 

Vous pouvez même faire un départ avec la recherche en utilisant le même principe:

criterions.add(Restrictions.sqlRestriction(columnName+ " ilike '!%%' escape '!'")); 
+1

J'ai commencé avec cette solution et cela a bien fonctionné pour moi sur de simples requêtes, mais un problème plus tard rencontré - si vous devez utiliser l'espace réservé '{alias}' dans 'columnName', cet espace réservé fait toujours référence à la table l'entité racine est mappée sur. Si vous utilisez des jointures dans votre requête, Hibernate ne peut pas insérer les alias pour les tables de jointure. Dans ce cas, j'ai dû recourir au sous-classement «LikeExpression». – EkcenierK

+0

Cela aide: j'ai essayé criterions.add (Restrictions.sqlRestriction (nomColonne + "like '!%' Escape '!'")); Ilike ne fonctionne pas pour moi – junior

0

Si vous utilisez Hibernate 3.2+, vous pouvez sous-classe LikeExpression, puis créer l'usine like/ilike méthodes:

import org.hibernate.criterion.Criterion; 
import org.hibernate.criterion.LikeExpression; 
import org.hibernate.criterion.MatchMode; 

public class EscapedLikeRestrictions { 
    private EscapedLikeRestrictions() {} 

    public static Criterion likeEscaped(String propertyName, String value, MatchMode matchMode) { 
     return likeEscaped(propertyName, value, matchMode, false); 
    } 

    public static Criterion ilikeEscaped(String propertyName, String value, MatchMode matchMode) { 
     return likeEscaped(propertyName, value, matchMode, true); 
    } 

    private static Criterion likeEscaped(String propertyName, String value, MatchMode matchMode, boolean ignoreCase) { 
     return new LikeExpression(propertyName, escape(value), matchMode, '!', ignoreCase) {/*a trick to call protected constructor*/}; 
    } 

    private static String escape(String value) { 
     return value 
       .replace("!", "!!") 
       .replace("%", "!%") 
       .replace("_", "!_"); 
    } 
} 
Questions connexes