Hay varias formas de afrontar el problema incluyendo el full text search con plugins como searchable o elasticsearch, pero si no queremos complicarnos en configurarlos o nuestra situación no requiere de búsquedas complejas, podemos usar otra solución.
En mi caso estoy utilizando PostgreSQL como base de datos en su versión 9.0. A partir de esta versión se incluye por defecto (sólo es necesario instalarla) la función unaccent que elimina todas las tildes de un campo. Así, la consulta que contruiríamos a mano sería algo como:
select * from user where unaccent("name") ilike unaccent('%iván%')Y devolvería cualquier usuario que se llamase: ivan, Iván, iván, IVÁN,... Todo esto está muy bien, pero que ocurre si ya tenemos el siguiente criteria de grails:
def users = User.createCriteria().list() { ilike('name', '%' + value + '%') }¿Cómo añadimos esa llamada a la función unaccent?. Vamos a crear nuestro propio Hibernate Criteria.
Editamos el archivo BootStrap.groovy y añadimos lo siguiente:
HibernateCriteriaBuilder.metaClass.unaccent = { String propertyName, Object propertyValue -> if (!validateSimpleExpression()) { throwRuntimeException(new IllegalArgumentException("Call to [unaccent] with propertyName [" + propertyName + "] and other property name [" + otherPropertyName + "] not allowed here.")); } propertyName = calculatePropertyName(propertyName); propertyValue = calculatePropertyValue(propertyValue);[ACTUALIZACIÓN]: He cambiado la forma de generar la consulta para que evitar posibles ataques por inyección de sql.def query = "unaccent(\"${propertyName}\") ilike unaccent('%${propertyValue}%')" return addToCriteria(Restrictions.sqlRestriction(query));def query = "unaccent(\"${propertyName}\") ilike unaccent(?)" def value = "%${propertyValue}%" return addToCriteria(Restrictions.sqlRestriction(query.toString(), value.toString(), Hibernate.STRING)); }
Lo que estamos haciendo es inyectar al HibernateCriteriaBuilder un método llamado unaccent que recibe como parámetros un string con el nombre de la propiedad y un objeto con el valor que queremos comparar.
Con esto podemos reescribir la consulta anterior de la siguiente manera:
def users = User.createCriteria().list() { // Old method //ilike('name', '%' + name + '%') unaccent('name', value) }Si ejecutamos el criteria vemos que la consulta es la siguiente:
select this_.id as id19_0_, this_.name as name19_0_ from user this_ where unaccent("name") ilike unaccent('%iván%')Que efectivamente devuelve los mismos registros de antes :-)
Todo esto se puede mejorar puesto que en función del número de registros que esperemos tener en la tabla, aplicar la función unaccent a la columna obliga a la base de datos a hacer un full scan en toda la tabla. Podríamos crear un índice, usar un campo paralelo para realizar las búsquedas que se mantenga automáticamente con un trigger, controlar este campo desde la aplicación grails con los métodos afterSave() y afterUpdate() de la clase de dominio User,... en fin, unas cuantas alternativas.
3 comentarios:
Hola Iván trate de agregar la función de unaccent en el BootStrap.groovy pero no me lo reconoce me marca un error al correr la aplicación, te envío mi BootStrap
class BootStrap {
def init = { servletContext ->
HibernateCriteriaBuilder.metaClass.unaccent = { String propertyName, Object propertyValue ->
if (!validateSimpleExpression()) {
throwRuntimeException(new IllegalArgumentException("Call to [unaccent] with propertyName [" +
propertyName + "] and other property name [" + otherPropertyName + "] not allowed here."));
}
propertyName = calculatePropertyName(propertyName);
propertyValue = calculatePropertyValue(propertyValue);
def query = "unaccent(\"${propertyName}\") ilike unaccent(?)"
def value = "%${propertyValue}%"
return addToCriteria(Restrictions.sqlRestriction(query.toString(), value.toString(), Hibernate.STRING));
}
}
def destroy = {
}
}
Hola siari,
sin ver el error es muy difícil poder ayudarte. Tal vez te falten los imports o alguna otra cosa.
Si quieres crear criterias personalizados te recomiendo que eches un vistazo a este plugin del que soy autor para utilizar tipos nativos de Postgresql en aplicaciones Grails. Podrás ver cómo se crean y se utilizan los distintos criterias.
https://github.com/kaleidos/grails-postgresql-extensions
Saludos, Iván.
Hola Iván, espero te encuentres bien. Te cuento estoy por presentar una aplicación para una materia y se me está agotando el tiempo =(
Tengo una consulta, no estoy pudiendo hacer queries desde grails en bd mysql,
me devuelve el siguiente error:
No signature of method: java.util.ArrayList.findAllByFechaAndHora() is applicable for argument types: (java.lang.String, java.lang.String) values: [2016/02/27, 20]
en controller este es la línea de code:
def reservadas= listaReservas.findAllByFechaAndHora(params.fecha, params.hora)
fecha está definido en domain y bd como date y hora es de tipo float en ambas.
He estado investigando y probando muchas cosas pero no encuentro manera de resolverlo,
Puede ser que las queries con findBy soporte sólo strings??
y en ese caso qué opciones se pueden aplicar?
Desde ya muchas gracias!
Saludos!!
Publicar un comentario