Grails: entendendo a busca por critérios (criterias)

Um dos recursos mais mal compreendidos do Grails são as criterias (será que posso falar “critérias” no plural?). O objetivo deste post é ir além do feijão com arroz que encontramos na documentação oficial. Espero que assim algumas das dúvidas mais comuns que escuto a respeito deste recurso sejam resolvidas.

Vamos tratar de uma situação típica: uma árvore genealógica bem idiota, representada pelas três classes de domínio abaixo:

class Pai {

String nome

String sobrenome

static hasMany = [filhos:Filho]

String toString() {this.nome}

}

class Filho {

String nome

String sobrenome

Integer idade

static belongsTo = [pai:Pai]

static hasMany = [filhos:Neto]

String toString() {this.nome}

}

class Neto {

String nome

String sobrenome

String status

static belongsTo = [pai:Filho]

String toString() {this.nome}

}

Com base nesta estrutura de classes, criei uma hierarquia que representei no grafo abaixo, que servirá para que possamos trabalhar neste post.

Tá: mas o que é uma criteria?

Grails nos oferece três ferramentas para que possamos fazer consultas em um banco de dados: finders dinâmicos, HQL (Hibernate Query Language) e criterias. A busca por critérios do Grails é na realidade uma abstração do mesmo recurso oferecido pelo Hibernate. A diferença é que temos aqui uma versão mais “groovy”, uma DSL baseada nos builders do Groovy.

Por que usar criterias se já tenho os lindos finders dinâmicos?

Os finders dinâmicos são feitos para consultas simples que envolvam algo entre uma e três condições no máximo (minha opinião). Se for necessário buscar os registros levando como base o atributo de um atributo composto da sua classe, esqueça os finders dinâmicos. Exercício pra você: retorne usando finders dinâmicos todos os netos de “Joe Lonely” em nosso exemplo.

Por que usar criterias se já tenho HQL?

A HQL já nos permite criar consultas BEM mais avançadas que os finders dinâmicos e com mesmo nível de complexidade que as critérias. Além disto, tanto os finders dinâmicos quanto as critérias geram, no frigir dos ovos, HQL. Sendo assim, porque usar uma camada superior se posso ir direto ao HQL? Simples: porque a concatenação de strings uma hora ou outra aparece quando trabalhamos com consultas REALMENTE complexas usando HQL.

E acredite: concatenação de strings pode não gerar tragédias no primeiro dia, mas quando você menos esperar elas virão atrás de você (elas são terríveis!).

O Básico

Toda classe de domínio do Grails possui injetada a função createCriteria, que retorna um objeto do tipo grails.gorm.HibernateCriteriaBuilder. Esta classe, por sua vez, possui uma função que será usada em 99% dos casos: a função list.

Na prática, caso desejemos listar todos os netos do nosso exemplo, poderíamos escrever o código abaixo:


def criteria = Neto.createCriteria()
criteria.list {}

O método list sempre recebe como parâmetro uma estrutura hierarquica. Neste caso, como foi passada uma estrutura vazia, todos os resultados são retornados. Caso fosse de nosso interesse retornar todos os netos cujo nome comece com “Pentelho”, escreveriamos o código abaixo:


def criteria = Neto.createCriteria()
criteria.list {
like("nome", "Pentelho%")
}

Seriam retornados os netos “Pentelho Lonely Joe” e “Pentelhox Lonely JOe”. No interior desta estrutura incluimos as expressões que irão compor a nossa consulta.

Dica: sempre pense na estrutura passada como um documento XML. Assim fica BEM mais fácil compreender o que está sendo feito.

É possível também usar os operadores lógicos or, and e not. No exemplo abaixo listaríamos todos os netos cujo nome comece com “Lori” ou “Pentelho”.


def criteria = Neto.createCriteria()
criteria {
or {
like("nome", "Pentelho%")
like("nome", "Lori%")

}
}

Observe que neste exemplo não usei a função list. Se passarmos diretamente o builder para o objeto criteria, usando a invocação dinâmica de métodos do Groovy, este já detecta que estamos na realidade chamando list. Trata-se apenas de uma conveniencia provida pelo framework.

A listagem completa de operadores suportados na busca por critérios pode ser obtida neste link: http://grails.org/doc/latest/ref/Domain%20Classes/createCriteria.html

Operadores lógicos

As critérias brilham quando precisamos usar expressões que envolvam os operadores lógicos and e, principalmente, o or. A melhor maneira de expor a sua superioridade é expor uma situação na qual o uso destes operadores da merda: finders dinâmicos.

Tenha como exemplo a consulta abaixo:


Neto.findAllByNomeLikeOrNomeLikeAndStatus("Pentelho%", "Lori%", "Idiota")

Que consulta exatamente temos aqui?

  • (nome like “Pentelho%” ou nome like(“Lori%”) e (status = “Idiota”) ?
  • (nome like “Pentelho%”) ou (nome like(“Lori%”) e (status = “Idiota”) ?

Na realidade será a segunda opção. Porém há espaço para dúvida. E se este existe, o que vocẽ faz? Você foge dele e usa a busca por critérios ora! Veja como consulta similar pode ser escrita de maneira MUITO mais clara:


def criteria = Neto.createCriteria()
criteria {
 or {
 like("nome", "Pentelho%")
 like("nome", "Lori%")
 }
 eq("status", "Idiota")

}

O operador or recebe como parâmetro um novo builder, aonde é possível incluir quantas operações, comparadores ou builders sejam necessários. Como resultado, têm-se um código ordens de magnitude mais fácil de entender e não sujeito a erros de interpretação por parte do programador.

Nota importante: por default, dentro de uma busca por critérios temos o operador and. Sendo assim, a consulta abaixo:

def criteria = Neto.createCriteria()
criteria {
     like("nome", "Pentelho%")
     eq("status", "Idiota")
}

Equivale à consulta

def criteria = Neto.createCriteria()
criteria {
     and {
         like("nome", "Pentelho%")
         eq("status", "Idiota")
     }
}

Só pra expor o funcionamento por trás dos panos, quando usamos os blocos or ou and, estamos na realidade criando novas instâncias das classes Conjunction e Disjunction, derivadas de org.hibernate.criterion.Junction. Pra quem já trabalhou com Hibernate, fica nítido o fato de novamente aqui não haver NADA de novo (o que é ótimo!) :)

Criterias dinâmicas

Como mencionei acima, um dos objetivos da busca por critérios é evitar a concatenação de strings na construção de consultas. Um recurso muito pouco documentado das critérias é a possibilidade de construí-las em tempo de execução.

O código abaixo expõe um exemplo:


def valorNumerico = 1
def tipoBusca = "sobrenome"
def valorSobrenome = "Lonely"

def criteria = Pai.createCriteria()
criteria {
if (valorNumerico == 1) {
eq("nome", "Joe Lonely")
} else {
like("nome", "%o%")
}

if (tipoBusca)
eq("sobrenome", valorSobrenome)
}

Olha que legal: você pode incluir instruções condicionais como if, else e switch no interior do builder. Assim, este pode ser construído em tempo de execução. É um recurso poderosíssimo e, infelizmente, pouco exposto em exemplos. Fica como exercício implementar o mesmo código acima usando HQL. Produzir algo similar com finders dinâmicos é impossível (se você conseguir, poste nos comentários aqui ok?).

O atributo de um atributo

É muito comum situações nas quais é necessário buscar registros com base no atributo de um dos seus atributos compostos. Imagine que queiramos buscar todos os filhos de “Joelinho Lonely” cujo nome comece com “Pentelho”. Simples: basta adicionar, ao invés de uma operação de comparação, o nome do próprio atributo na criteria, tal como no código abaixo:


def criteria = Neto.createCriteria()
criteria {
pai {
eq("nome", "Joelinho Lonely")
}
like("nome", "Pentelho%")
}

Observe que interessante: estamos incluindo um builder dentro de outro. No caso, um dos nós do nosso builder é o atributo “pai” da classe “Neto”. Em seu interior, podemos incluir quantas expressões quisermos, inclusive outros builders. Pra complicar um pouco mais a situação, vamos buscar todos os netos cujo nome comece com “Pentelho”, sejam filhos de “Joelinho Lonely” e netos do bom e velho “Joe Lonely”.


def criteria = Neto.createCriteria()
criteria {
 pai {
 eq("nome", "Joelinho Lonely")
pai {
eq("nome", "Joe Lonely")
}
 }
 like("nome", "Pentelho%")
}

Buscando um pai pelos filhos

Ok, agora vamos supor que queiramos buscar todos os registros da classe “Filho” a partir dos filhos que possuem. Retornando à implementação da classe filho, vemos que há um atributo filhos nesta. Haveria como, por exemplo, buscar todos os registros de Filho que tenham gerado netos cujo nome comece com “Pentelho”? Yeap! Veja o código abaixo:

def criterio = Filho.createCriteria()

criterio {

filhos {

like("nome", "Pente%")

}

}

Concluindo

Espero que com estas duas características pouco conhecidas das critérias eu tenha ajudado a elucidar algumas dúvidas que costumam aparecer aos iniciantes em Grails. Para quem já trabalhou com Hibernate, fica nítido que estamos lidando exatamente com o mesmo recurso, porém com uma roupagem mais legível.

Para de fato entender a fundo este recurso, recomendo a leitura dos seguintes links:

Groovy Builders – compreender os builders do Groovy ajuda muito a entender outros recursos presentes no Grails como por exemplo as constraints (será meu próximo post, aguardem)

Hibernate Criteria – como funcionam as critérias do Hibernate, que é o pano de fundo deste recurso.

org.grails.HibernateCriteriaBuilder – O código fonte por trás do funcionamento da busca por critérios

Só pra lembrar, você deve usar as buscas por critérios se um dos casos abaixo aparecer:

  • A consulta envolve mais de duas ou três condições
  • É necessária a inclusão de operadores lógicos como or, and e not
  • Você deseja construir a consulta em tempo de execução e você quer evitar concatenação de strings
  • É necessário aumentar a legibilidade do código de suas consultas

Quanto à última razão, peço perdão ao leitor. Infelizmente, o renderizador de código deste blog é muito primitivo e não expõe corretamente a indentação (mas pretendo resolver isto em breve).

Bom, até a próxima então. Meu próximo assunto será ou um detalhamento maior sobre a validação em Grails ou algum novo experimento envolvendo desenvolvimento de jogos usando HTML5 (ou serão os dois de uma vez?)

Grande abraço!

13 thoughts on “Grails: entendendo a busca por critérios (criterias)

  1. Kico,

    Parabéns pelo post. Realmente vc demonstrou alguns recursos interessantes curiosamente omitidos da documentação do Grails. Inclusive precisei semana passada do recurso de pesquisar informações com base em atributos compostos e quebrei bastante a cabeça para poder desenvolver a consulta com criteria e algo tão simples de fazer como foi demonstrado. Grato pela aula :D

    Responda

    admin Reply:

    Oi André, fico feliz que tenha gostado.
    Valeu!
    Também me lembro de ter ficado surpreso com estes recursos. Nossa, como eu quebrava a cabeça com bobagens antes de conhecê-los! :D

    Responda

  2. Muito bom o post Kico, meus parabéns. Era um recurso sombrio para mim até ler aqui. A documentação dele é muito ruim e com este post ficou mais fácil conhecer seu funcionamento.

    Responda

    admin Reply:

    Oi Daniel, fico feliz em ter lhe ajudado cara, valeu!

    Responda

  3. Kiko eu tentei criar essa pagina das classes acima que vc criou pra esse exemplo só que na unha, mas não consegui, mas estou tentando. Voce poderia me dizer por favor como fazer a controller na unha? eu tentei algo assim:
    def salvar() {
    def result = [‘status’: 1, ‘mensagem’: ”]

    if (!params.nome) {
    result.status = 2
    result.mensagem = ‘Informe o Nome do Banco’

    } else {

    def pai = Pai.get(params.id)
    if (!pai) {
    pai = new Pai(params)
    pai.filho = new Filho(params)
    pai.neto = new Neto(params)
    pai.properties = params

    pai.save()
    result.mensagem = ‘Cadastro adicionado’

    } else {
    pai.properties = params
    pai.merge()
    result.mensagem = ‘Cadastro atualizado’
    }
    if (!pai.save()) {
    pai.errors.each {
    println it
    }
    }
    }
    render result as JSON
    }

    Mas ta dando erro e as vezes salva somente a tabela pai e as outras ficam vazias, desde ja parabens pela materia. Aguardo contato abraços.

    Responda

    Kico (Henrique Lobo Weissmann) Reply:

    Oi Silas, desculpe pela demora na resposta.
    Rola de postar esta dúvida no Grails Brasil?

    Responda

  4. Ótimo artigo.

    Responda

    Kico (Henrique Lobo Weissmann) Reply:

    Valeu! Devo gravar um vídeo sobre isto em breve.

    Responda

  5. Uma dúvida kiko, como eu poderia pegar o ultimo neto ou o ultimo filho somente. Ou até mesmo, pegar somente o filho mais velho(Neto) de cada Filho (Filho)

    Responda

    Kico (Henrique Lobo Weissmann) Reply:

    Oi Yure, como assim?

    Tem como postar esta dúvida no Groovy & Grails Brasil? http://www.grailsbrasil.com.br

    Por lá fica muito mais fácil te ajudar. :)

    Responda

Leave a Reply

Your email address will not be published. Required fields are marked *