2009-03-23 28 views
15

Para esta pregunta, queremos evitar tener que escribir una consulta especial ya que la consulta debería ser diferente en varias bases de datos. Utilizando solo criterios de hibernación, queremos poder escapar caracteres especiales.Al utilizar los criterios de hibernación, ¿hay alguna manera de escapar de los caracteres especiales?

Esta situación es la razón por la que necesita la capacidad de escapar caracteres especiales:

Supongamos que tenemos la tabla 'foo' en la base de datos. La tabla 'foo' contiene solo 1 campo, llamado 'nombre'. El campo 'nombre' puede contener caracteres que pueden considerarse especiales en una base de datos. Dos ejemplos de dicho nombre son 'nombre_1' y 'nombre% 1'. Tanto '_' como '%' son caracteres especiales, al menos en Oracle. Si un usuario desea buscar uno de estos ejemplos después de haberlo ingresado en la base de datos, pueden surgir problemas.

criterion = Restrictions.ilike("name", searchValue, MatchMode.ANYWHERE); 
return findByCriteria(null, criterion); 

En este código, 'searchValue' es el valor que el usuario ha dado a la aplicación a usar para su búsqueda. Si el usuario desea buscar '%', el usuario será devuelto con cada entrada 'foo' en la base de datos. Esto se debe a que el carácter '%' representa "cualquier número de caracteres" comodín para la coincidencia de cadenas y el código SQL que hibernación produce se verá así:

select * from foo where name like '%' 

¿Hay una manera de decirle a Hibernate a escapar de ciertos caracteres, o para crear una solución alternativa que no sea específica del tipo de base de datos?

Respuesta

10

Los constructores LikeExpression están todos protegidos, por lo que no es una opción viable. Además, tiene problems of its own.

Un colega y yo creamos un parche que funciona bastante bien. La esencia del parche es que para el constructor LikeExpression que consume un MatchMode, escapamos de los caracteres especiales. Para el constructor que consume un carácter (el carácter de escape), suponemos que el usuario escapa de los caracteres especiales por su cuenta.

También parametrizamos el carácter de escape para asegurarnos de que no puede dañar la consulta SQL si usan algo como \ o un carácter de comillas.

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 te estás preguntando qué LHS y métodos TypedValue son para el nuevo IlikeExpression debe responder a estas preguntas.

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(); 
    } 

} 

Después de esto, lo único que queda es hacer restricciones de uso de estas nuevas clases:

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 sí. Esto funciona para Oracle. Sin embargo, no estamos seguros acerca de otras bases de datos.

1

si usa LikeExpression directamente, le permite especificar el carácter de escape. supongo que debería ser todo lo que necesitas.

+0

Gracias. Espero poder encontrar el momento para probar esto más tarde hoy. Voy a actualizar después de probarlo. –

+0

IlikeExpression no lo tiene. –

+0

Todos los constructores de LikeExpression están protegidos, debe crear una subclase y crear un constructor público. – EkcenierK

3

No es una forma muy limpia para hacerlo, pero una sqlRestrinction debería ser más fácil:

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

Puede incluso hacer un comienzo con la búsqueda utilizando el mismo principio:

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

Comencé con esta solución y funcionó bien para mí en consultas simples, pero más tarde tuve un problema con ella: si necesita usar el marcador de posición '{alias}' en 'columnName', este marcador de posición siempre se refiere a la tabla que la entidad raíz está mapeada a. Si está utilizando alguna combinación en su consulta, Hibernate no puede insertar los alias para las tablas de combinación. En este caso, tuve que recurrir a la subclasificación de 'LikeExpression'. – EkcenierK

+0

Eso ayuda: probé criterions.add (Restrictions.sqlRestriction (columnName + "like '!%' Escape '!'")); ilike no funciona para mí – junior

0

Si utiliza Hibernate 3.2+, puede subclase LikeExpression, y luego crear la fábrica like/ilike métodos:

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("_", "!_"); 
    } 
} 
Cuestiones relacionadas