Um grande problema dos ORM’s é a necessidade de conversão da linguagem específica do ORM para a linguagem específica do BD. Com isso já existe uma perda de desempenho nas consultas e se nós não nos preocuparmos com a qualidade do código que esta sendo escrito, a forma como realizamos as consultas, nosso BD pode realmente parar.
Então vamos lá. Primeira coisa a entender é o significado de Inner Join, Right e Left Join.
Em um relacionamento sempre existirá uma entidade fraca e uma forte. A entidade fraca é reconhecida por manter a chave estrangeira e forte é a que existe por si só. Entendido isso vamos lá.
Inner Join irá trazer resultados do plano cartesiano caso a sua condição de agregação seja realizada em ambas as tabelas. Exemplo: Todas as cidades que possuem o estado CE. Neste caso as cidades que não possuem o relacionamento com CE não irão vir pois a condição para agregar é a necessidade de ser do Estado do CE.
Left Join irá trazer os resultados do plano cartesiano que a condição de agregação é realizada e todos os registros da tabela “a esquerda da junção” que não realizam a condição de agregação.
Exemplo: Todos os registros da tabela A que possuem vinculo com a tabela B, caso não exista o resultado em B será nulo.
Right Join é o inverso de left Join.
Na prática com SQL:
Inner Join:
Select * from cidade c inner join UF u on c.ud_id = u.id and u.sigla = 'CE';
Left Join:
Select * from tabelaA a left join tabelaB b on a.b_id = b.id
Right Join:
Select * from tabelaA a right join tabela B b on a.b_id = b.id
Entendido o significado vamos para o Hibernate.
Estou utilizando a versão 3.6 do Hibernate, na 4.x a forma muda um pouco. Nada traumático. Utilizaremos Criteria
Criteria criteria = session.createCriteria(Cidade.class); criteria.createAlias("uf", "uf"); criteria.add(Restrictions.eq("uf.sigla", "CE"));
Essas três linhas fazem o seguinte. Primeira cria o seu objeto criteria a partir do Session do Hibernate. Não irei me importar com a criação do Session, existem ‘n’ tutorias disponíveis na net ensinando a inicializar a Criteria.
Segunda linha adiciona um Alias (um JOIN) a sua consulta. Feito da forma como foi ele não irá colocar os dados na sua Projeção do SQL, ou seja, os dados de UF não irão vir na consulta mas serão utilizados como restrição da sua consulta. A forma que foi feita indica que será utilizado INNER JOIN.
Terceira cria a restrição por sigla “CE”.
Entendido isso como faço então para criar um LEFT ou RIGHT e para trazer os dados do meu objeto UF preenchido?
Criteria criteria = session.createCriteria(Cidade.class); criteria.createAlias("uf", "uf", Criteria.INNER_JOIN); // INNER_JOIN, LEFT_JOIN, RIGHT_JOIN criteria.add(Restrictions.eq("uf.sigla", "CE"));
No primeiro exemplo foi incluido apenas um “join” para realizar a restrição pela Sigla. Os dados de UF não são jogados para sua projeção e dessa forma ao realizar a consulta o objeto CIDADE não vem com o objeto UF preenchido.
No segundo exemplo foi adicionado um terceiro parametro ao createAlias. O primeiro parametro é o nome do atributo na classe Cidade, o segundo é o Alias que vc quer dar a esse atributo e o terceiro parametro é a forma como vc quer tratar esse JOIN. Ao informar o terceiro parametro a sua consulta irá jogar, em sua projeção, todos os campos da tabela UF e dessa forma ao executar a consulta o objeto CIDADE virá com o objeto UF todo preenchido.
Perceba que, se em algum momento você fosse precisar do objeto UF na primeira forma você teria que realizar novamente uma consulta no BD para recuperar o dado da UF enquanto na segunda forma o objeto já viria todo preenchido. Imagine isso sendo feito para 100 mil registros? para cada registro eu teria mais uma consulta no BD então eu teria um total de 101 mil consultas – A consulta inicial trazendo os 100 mil registros e 1 consulta para cada resultado. Da segundo forma vc teria apenas 1 consulta, ou seja, um ganho enorme de performace.
Ai vocês me perguntam – “Mas eu não uso Hibernate, eu uso JPA puro” e eu lhe respondo “Tudo bem, a forma é a mesma, a diferença é como escrever”. Vamos ver com JPA (EJBQL)
String query = "select c from Cidade c inner join c.uf uf where uf.sigla = 'CE'";
Essa consulta é idêntica a consulta que criamos com criteria no primeiro exemplo. Fará o mesmo, ou seja, irá trazer apenas os dados de Cidade. Como resolver isso? FETCH é a nossa solução.
String query = "select c from Cidade c inner join fetch c.uf uf where uf.sigla = 'CE';
Pronto. Agora, com o FETCH, temos nosso objeto Cidade todo preenchido igual no segundo exemplo da criteria.
Alguns pontos a levar em consideração:
- Hibernate / JPA só consegue realizar FETCH de uma única lista. O objeto List<?> é tratado com um BAG e por conta disso você somente conseguirá realizar o fetch de um bag somente. Caso tente realizar o join de mais de uma lista você irá receber a exceção MultipleBagFetchException. Algumas pessoas dizem para colocar o LazyLoad do objeto para Eager. Mesmo que você faça isso (o que por sinal é uma péssima solução) se você tiver duas Listas irá ocorrer o mesmo erro. Porque digo péssima solução? Simples, nem sempre você irá necessitar de todos os dados e utilizando Eager isso sempre irá ocorrer então você estaria consumindo recursos desnecessários. A solução para esse problema é trabalhar com Set<?> ao invês de List<?> caso seja possível.
- Na sua consulta faça o JOIN com somente o que você precisar. Se estiver usando EJBQL você poderá inicializar somente os campos que deseja exibir, exempo, “uf.sigla” e não todos os campos de UF. Para tanto basta utilizar o primeiro exemplo de EJBQL e na sua projeção “select * from” incluir o “c.uf.sigla”. Ficaria assim “Select c, c.uf.sigla from Cidade c”
- Tente otimizar ao máximo suas consultas. Elas poderão prejudicar bastante o desempenho de sua aplicação. Então tente criar consultas mais limpas, utilizar índices no BD e sempre manter os dados consistentes.
- Não trabalhe com relacionamentos EAGER. Isso pode matar o seu banco de dados. Sempre use LAZY em seus relacionamentos e inicialize apenas o que desejar exibir. É mais trabalhoso mas é muito mais seguro.
Abraços!!!