Problema com múltiplos joins em Criteria

Post movido para: http://blog.guilhermegarnier.com/2009/11/12/problema-com-multiplos-joins-em-criteria/

O Criteria é uma API do Hibernate que facilita muito quando precisamos montar uma query complexa com filtros opcionais. Adicionar restrições ou criar joins com esta API é muito mais simples de gerenciar do que concatenando Strings, como faríamos ao trabalhar com SQL puro.

Apesar das vantagens, o Criteria também tem alguns problemas. O último que encontrei foi ao tentar fazer 2 joins entre as mesmas 2 tabelas. No meu caso, eu tinha no banco as tabelas projeto e historico. A segunda tabela é populada através de uma trigger no banco: sempre que o status do projeto muda, a tabela historico registra o status anterior do projeto com data/hora da mudança. Eu precisava fazer uma query que buscasse um projeto com status “iniciado” num determinado período de datas e com status “finalizado” em outro período. Inicialmente, pensei simplesmente em criar 2 joins entre as tabelas, cada um com um alias diferente e filtrando pelas datas específicas:

Criteria criteria = getSession().createCriteria(Projeto.class);

// Primeiro join
criteria.createCriteria("historicoList", "historicoIniciado", Criteria.LEFT_JOIN)
        .add(Restrictions.eq("historicoIniciado.status", Status.INICIADO.value()))
        .add(Restrictions.ge("historicoIniciado.data", dataIniciadoDe))
        .add(Restrictions.le("historicoIniciado.data", dataIniciadoAte));

// Segundo join
criteria.createCriteria("historicoList", "historicoFinalizado", Criteria.LEFT_JOIN)
        .add(Restrictions.eq("historicoFinalizado.status", Status.FINALIZADO.value()))
        .add(Restrictions.ge("historicoFinalizado.data", dataFinalizadoDe))
        .add(Restrictions.le("historicoFinalizado.data", dataFinalizadoAte));

O código acima, apesar de semelhante ao que eu já havia criado para adicionar outros filtros à query de projetos, fazendo joins com outras tabelas, não funcionava. Tentei retirar um dos joins com a tabela historico e funcionou. Ou seja, o problema estava na criação do segundo join com as mesmas tabelas, mesmo utilizando aliases diferentes. Ao pesquisar este problema, descobri que não é um bug. Na verdade, o Criteria não suporta múltiplos joins para a mesma associação.

Sendo assim, a solução que encontrei para este problema foi criar uma subquery para a tabela historico, utilizando um DetachedCriteria:

DetachedCriteria historicoCriteria = DetachedCriteria.forClass(Historico.class, "historicoIniciado")
        .setProjection(Projections.distinct(Projections.property("projeto")))
        .add(Restrictions.eq("historicoIniciado.status", Status.INICIADO.value()));
        .add(Restrictions.ge("historicoIniciado.data", dataIniciadoDe));
        .add(Restrictions.le("historicoIniciado.data", dataIniciadoAte));
criteria.add(Subqueries.propertyIn("id", historicoCriteria));

Desta forma, apenas um dos joins precisa ser substituído por uma subquery. O outro join pode ser mantido sem problemas.

4 Responses to “Problema com múltiplos joins em Criteria”


  1. 1 Ânderson Soares fevereiro 24, 2010 às 2:48 pm

    Fala meu amigo.

    Não sei se meu problema era semelhante ao seu, mas vou postar uma solução, em que consegui fazer o JOIN entre 4 tabelas.
    Segue:

    Eu tenho as tabelas, vou colocar so os dados que interessam
    – EntrevistadorPesquisa(id, (FK)idPesquisa, (FK)idEntrevistador)
    – Entrevistador(id, (FK)idUsuario, (FK)idNivel)
    – Usuario(id)
    – NivelEntrevistador(id, descricao)
    ps: todos id’s são P.Keys.
    ps: 2, tudo que for CAIXA ALTA a partir de agora é tabela

    Sendo assim, eu acesso a tabela ENTREVISTADOR_PESQUISA, pra saber quais entrevistadores estão relacionados a quais pesquisas.
    Preciso então cruzar essa ultima com ENTREVISTADOR, pra ter os dados do entrevistador. Preciso cruzar ENTREVISTADOR com USUARIO, para eu ter acesso a dados de login, senha, ativo…etc.
    E preciso cruzar ENTREVISTADOR com NIVEL_ENTREVISTADOR, pra saber qual o nivel de cada entrevistador.

    Sendo assim, com o join da forma que eu estava tentando

    session.createCriteria(EntrevistadorPesquisa.class)
    .createCriteria(“entrevistador”)
    .createCriteria(“usuario”);
    .createCriteria(“nivelEntrevistador”)

    eu teria meus dados não mão, correto?
    NÃO, ERRADO.

    Como vc disse, por padrão o hibernate não deixa eu fazer isso, pois consome muita memória, por muitas vezes atoa.

    Pois então é preciso mudar a propriedade @ManyToOne da tabela Entrevistador, de LAZY para EAGER.

    Sendo assim, só mudei minha configuração:
    – @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = “ID_USUARIO”, nullable = false)
    public Usuario getUsuario() {
    return this.usuario;
    }

    – @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = “ID_NIVEL”, nullable = false)
    public NivelEntrevistador getNivelEntrevistador() {
    return this.nivelEntrevistador;
    }

    e obtive sucesso na minha consulta.

    O que achou? O problema era semelhante mesmo? Espero que tenha sido útil a resposta
    Aguardo uma resposta por email, pra gente trocar idéia. Obrigado e até mais.

    • 2 ggarnier março 1, 2010 às 8:37 am

      Ânderson, se eu entendi bem, acho que não é o mesmo caso. No exemplo que eu citei, eu tinha 2 tabelas e precisava fazer 2 joins entre elas. No seu caso são várias tabelas diferentes, e isso o Hibernate suporta bem.

      Só uma observação: o FetchType.EAGER não deve ser usado em relacionamentos OneToMany, pois cada vez que você buscar um registro, buscará também uma lista de elementos da outra tabela, o que pode provocar um impacto considerável no desempenho da sua aplicação.

  2. 3 Ânderson Soares março 1, 2010 às 9:16 am

    Sim. Nesse caso, o EAGER soloucionou bem meu caso, ou existe alguma forma melhor?

  3. 4 ggarnier março 9, 2010 às 3:27 pm

    No seu caso não há problema, pois o EAGER foi colocado no @ManyToOne. O problema é quando ele é usado no @OneToMany, ou seja, na outra ponta do relacionamento, onde o EAGER faria com que um select buscasse também uma lista de objetos da outra entidade.


Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s




@guilhermgarnier

Erro: o Twitter não respondeu. Por favor, aguarde alguns minutos e atualize esta página.

Estatísticas

  • 58,044 hits
Linux Counter

%d blogueiros gostam disto: