Вы находитесь на странице: 1из 8

Porque relacionamentos bidirecionais?

Quando estvamos configurando a Movimentacao para ser gerenciada pelo Hibernate, vimos que bastou anotarmos o atributo conta com @ManyToOne para estabelecer a relao de muitos para um. Com isso conseguimos recuperar a conta associada movimentao simplesmente invocando o getConta(), por exemplo: Vamos mostrar isso uma criando uma nova classe TesteMovimentacaoConta dentro do package financas.teste. No mtodo main abrimos o EntityManager e carregamos a movimentao com o id 2. Cuidado, o registro deve existir no banco. Falta mostrar o resultado no console. Com a movimentao carregada vamos imprimir a conta associada usando o relacionamento

movimentacao.getConta().getTitular():

public class TesteMovimentacaoConta { public static void main(String[] args) { EntityManager manager = new JPAUtil().getEntityManager(); Movimentacao movimentacao = manager.find(Movimentacao.class, 2); System.out.println("Titular da conta: " movimentacao.getConta().getTitular()); } }

Ao executar, ser impresso o nome do titular. Repare que no foi necessrio se preocupar com o join ou chave estrangeira, usamos o nosso modelo OO para navegar no relacionamento.

Navegando no relacionamento sem Join


Imagine que agora queremos saber quantas movimentaes uma determinada conta possui. Pensando de uma maneira mais orientada a objetos, seria natural pedirmos a conta todas as suas movimentaes atravs de um mtodo getMovimentacoes() e depois verificar o tamanho da lista retornada. Parecido com o que fizemos anteriormente. Primeiro carregar a conta e depois acessar o relacionamento, para recuperar a quantidade de movimentaes.

Conta conta = // recuperamos uma conta System.out.println(conta.getMovimentacoes().size());

Definio do relacionamento Um-Para-Muitos


Seguindo essa ideia, precisamos modificar a nossa classe Conta para ter um atributo que possa guardar todas as movimentaes. Vamos modificar ento a nossa classe para que isso seja possvel. Primeiro criaremos o atributo, que ser uma lista de movimentaes. Para ter acesso ao atributo tambm vamos gerar o getter:

package br.com.caelum.financas.modelo; // imports omitidos @Entity public class Conta { // demais atributos omitidos private List<Movimentacao> movimentacoes; public List<Movimentacao> getMovimentacoes() { return movimentacoes; } // demais getters e setters omitidos }
Mas deixando dessa maneira, ainda no conseguimos falar para o Hibernate trazer todas movimentaes da conta. Perceba que simplesmente criamos um atributo, nada de mais. Assim como anotamos com @ManyToOne quando queremos indicar um relacionamento de muitos para um, precisamos de uma anotao para indicar um relacionamento de um para muitos. Ela se chama @OneToMany. Uma conta possui vrias movimentaes.

O cdigo ficaria:

@Entity public class Conta { // demais atributos omitidos @OneToMany private List<Movimentacao> movimentacoes;

// demais getters e setters omitidos }

A diferena dos relacionamentos do mundo OO e relacional


Ao testar o nosso cdigo, o select gerado mas o resultado mostra que no h nenhuma movimentao nessa conta. Para verificar vamos dar uma olhada no banco. Ao abrir um terminal e se conectar com o MySQL, vamos selecionar a base financas. Depois executamos um simples select para ver todas as

movimentaes.

mysql -u root use financas; select * from Movimentacao;

Na tabela de registros podemos ver que existem sim 4 movimentaes da conta com o id 1. Algo est errado com o nosso mapeamento.

Aqui encontramos uma clara diferena entre o mundo relacional e o orientado a objetos. Para o nosso banco de dados, quando mapeamos a chave estrangeira na tabela de movimentaes, automaticamente j est estabelecida a relao movimentao para conta e vice versa. Mas na orientao a objetos, essa relao no implcita. Repare que mapeamos o relacionamento na classe Movimentacao e na Conta. De certo modo isso pode ser um problema, j que o JPA vai entender que os relacionamentos so independentes.

Para ela existem duas relaes, uma de muitos para um da movimentao para a
conta e outra de um para muitos daconta para as movimentaes. Para representar

esse novo mapeamento, o Hibernate cria uma nova tabelaConta_Movimentacao concorrendo com o antigo relacionamento da FK conta_id.

Declarando o relacionamento como bidirecional


O que acontece aqui que ele no entendeu que queramos um nico relacionamento com navegabilidade para ambos os lados (bidirecional) ao invs de dois relacionamentos independentes. O que queremos ento configurar o JPA para que ela entenda que esse novo relacionamento representa o mesmo que j temos configurado com a anotao @ManyToOne na classe Movimentacao. Para atingir esse objetivo, devemos escolher um dos lados para ser o dono do relacionamento e nesse caso somente a tabela do dono que ter a chave estrangeira, sendo assim, o outro relacionamento existe apenas no mundo OO, no havendo a necessidade de criar uma nova tabela. Usaremos um atributo da anotao @OneToMany, cujo nome mappedBy, que indica que essa relao a mesma representada pelo atributo conta na classe Movimentacao.

@Entity public class Conta { // outros atributos aqui @OneToMany(mappedBy="conta") private List<Movimentacao> movimentacoes; // getters e setters }

Atualizar o schema do banco de dados


Precisamos agora ento atualizar nosso Schema do banco de dados para corresponder ao nosso novo modelo de entidades e relacionamentos. Vamos conectar ao MySQL, apagar nossa base de dados: mysql -u root drop database financas; Cri-novamente: create database financas; Executar a classe PopulaBanco e pronto, o Hibernate se responsabiliza pela execuo do Schema e criao das tabelas e relacionamentos, agora sem a tabela
Conta_Movimentacao.

Testando o relacionamento bidirecional


J podemos testar o relacionamento que agora bidirecional. Aproveitando ainda nossa classe de teste, faremos uma consulta ao banco que retornar uma lista com todas as contas cadastradas. No mtodo main atravs do

mtodocreateQuery(..) do EntityManager faremos uma consulta no banco que retornar uma lista com todas as contas cadastradas.

public class TesteMovimentacaoConta { public static void main(String[] args) { EntityManager manager = new JPAUtil().getEntityManager(); Query query = manager.createQuery("select c from Conta c"); List<Conta> contas = query.getResultList(); } }
Nesse mesmo mtodo, vamos iterar na lista contas e para cada conta, imprimiremos o tamanho da lista de movimentaes com o mtodo size(), ou seja o nmero de movimentaes:

public class TesteMovimentacaoConta { public static void main(String[] args) { // cdigo com a query for (Conta conta : contas) { System.out.println("Nmero de movimentaes: " + conta.getMovimentacoes().size() ); } } }

O problema das N+1 consultas e como resolv-lo


Note que o Hibernate, logo no incio da execuo da classe, gera e executa um SQL para recuperar os dados das contas: select conta0.id as id0, conta0.agencia as agencia0, conta0.banco as banco0, conta0.numero as numero0, conta0.titular as titular0 from Conta conta0_

Mas a cada chamada do mtodo getMovimentacoes(), ele gera um novo SQL e executa uma nova consulta no banco, agora para as movimentaes de cada conta:
select movimentac0.conta_id as conta6_0_1, movimentac0.id as id1_1,

movimentac0.id as id1_0, movimentac0.conta_id as conta6_1_0, movimentac0.data as data1_0, movimentac0.descricao as descricao1_0, movimentac0.valor as valor1_0 movimentac0.tipoMovimentacao as tipoMovi4_1_0, from Movimentacao movimentac0 where

movimentac0.conta_id=?

Tivemos logo no incio uma primeira consulta que buscou as contas no banco e retornou os objetos. Para cada uma das contas, tivemos uma consulta extra para buscar suas movimentaes, e por isso dizemos que tivemos N+1 consultas. N o nmero de objetos retornados por uma consulta original. Neste caso, se tivermos 5 contas cadastradas, faremos 1 consulta para recuperar todas essas contas, mais 5 novas consultas para buscas as movimentaes de cada uma delas (5+1), ou seja, 6 consultas ao banco.

Comportamento LAZY dos relacionamentos paramuitos


Isso s acontece porque todos os relacionamento do tipo ToMany assumem um comportamento Lazy (preguioso), que por definio s recupera os dados dos relacionamentos quando eles realmente so demandados pela aplicao. Essa geralmente uma boa estratgia, visto que na maioria das vezes no precisaremos de todos os dados dos relacionamentos entre as entidades, e assim economizamos no processamento das consultas pelo servidor. Para resolver esse problema, a opo mais "certeira" seria tratar especificamente esse relacionamento como se tivesse um comportamento Eager (ancioso). Para esse fim temos o join fetch. Usando essa instruo fazemos com que um relacionamento que fetch=LAZY comporte-se como fetch=EAGER para realizar a consulta. Nossa consulta ento ficaria assim:

public class TesteMovimentacaoConta { public static void main(String[] args) { // cdigos omitidos Query query = manager

.createQuery("Select c from Conta c join fetch c.movimentacoes"); // cdigos omitidos


} }

Para no termos valores repetidos na lista dos resultados podemos usar o


DISTINCT:

Query query = manager .createQuery("select distinct c from Conta c join fetch c.movimentacoes");

Mas ateno, o contrrio no possvel. No tem como definirmos um relacionamento como eager e fazer uma consulta lazy para carregar um simples combo, por exemplo. Por isso geralmente deixamos o relacionamento lazy e caso precisemos foramos o carregamento somente em uma consulta.

O FetchType.EAGER
Uma outra forma de fazer isso transformando todo o relacionamento em eager, fazendo o parmetro fetch do mapeamento @OneToMany da entidade
Conta, receber o valor FetchType.EAGER. Mas isso na maioria dos casos

seria uma m prtica e afetaria outras reas da aplicao.

@OneToMany(mappedBy = "conta", fetch = FetchType.EAGER) private List<Movimentacao> movimentacoes;

Вам также может понравиться