Modo mais eficiente para mapear uma @OneToMany relação com JPA e Hibernate
Eu escolhi abrir este post com esta citação porque sou fã de Linus Torvalds.😉

Este é o meu primeiro artigo de sempre. Neste eu estarei cobrindo todos os casos possíveis em One-to-Many/Many-to-One
associação de entidades. Restantes Many-to-Many
e One-to-One
serão cobertos nos próximos artigos.
Eu espero que isto definitivamente ajude todos os novatos que querem aprender jpa/hibernate, Por favor leia a peça inteira 😛
NOTE:
Aqui eu cobri todos os casos possíveis de
One-to-Many/Many-to-One
mapeamento. Entre todos , a associação Bidirecional `@OneToMany` é a melhor maneira de mapear uma relação de um para muitos bancos de dados.
A associação hibernar classificada em One-to-One
, One-to-Many/Many-to-One
e Many-to-Many
.
- A direcção de uma relação pode ser bidireccional ou unidireccional.
- Uma relação bidireccional tem tanto um lado proprietário como um lado inverso.
- Uma relação unidireccional tem apenas um lado proprietário. O lado proprietário de uma relação determina como o tempo de execução da Persistência faz atualizações da relação no banco de dados.
- Unidirecional é uma relação onde um lado não sabe sobre a relação.
- Em uma relação unidirecional, apenas uma entidade tem um campo de relação ou propriedade que se refere à outra. Por exemplo, o Item do documento teria um campo de relação que identifica o Produto, mas o Produto não teria um campo de relação ou propriedade para o Item do documento. Em outras palavras, o Item sabe sobre o Produto, mas o Produto não sabe quais instâncias de Item se referem a ele.
Relações bidirecionais:
- Relação bidirecional fornece acesso de navegação em ambas as direções, para que você possa acessar o outro lado sem consultas explícitas.
- Em uma relação bidirecional, cada entidade tem um campo ou propriedade de relação que se refere à outra entidade. Através do campo ou propriedade de relação, o código de uma classe de entidade pode acessar seu objeto relacionado. Se uma entidade tem um campo relacionado, diz-se que a entidade “conhece” o seu objeto relacionado. Por exemplo, se Order sabe quais instâncias de Line Item tem e se Line Item sabe a que Order pertence, eles têm uma relação bidirecional.
As relações bidirecionais devem seguir estas regras.
- O lado inverso de uma relação bidirecional deve se referir ao seu próprio lado (Entidade que contém a chave estrangeira) usando o elemento
mappedBy
do elemento@OneToOne
,@OneToMany
, ou@ManyToMany
anotação. O elementomappedBy
designa a propriedade ou campo na entidade que é a proprietária da relação. - Os muitos lados de
@ManyToOne
relações bidirecionais não devem definir o elementomappedBy
. Os muitos lados são sempre o lado proprietário da relação. - Para
@OneToOne
relações bidirecionais, o lado proprietário corresponde ao lado que contém @JoinColuna, ou seja, a chave estrangeira correspondente. - Para
@ManyToMany
relações bidirecionais, cada lado pode ser o lado de propriedade.

@UmaToMuita relação com JPA e Hibernate
Simplesmente colocado, um-para-muitos mapeamentos significa que uma linha em uma tabela é mapeada para várias linhas em outra tabela.
Quando usar um para muitos mapeamentos
Utilizar um para mapear para criar 1…N relação entre entidades ou objetos.
Temos que escrever duas entidades i.e.
Company
eBranch
de modo que várias filiais possam ser associadas a uma única empresa, mas uma única filial não pode ser compartilhada entre duas ou mais empresas.
Hibernar uma a muitas soluções de mapeamento:
- Um a muitos mapeamentos com associação de chaves estrangeiras
- Um a muitos mapeamentos com tabela de join
Este problema pode ser resolvido de duas formas diferentes.
Uma é ter uma coluna de chaves estrangeiras na tabela de filiais, ou seja
company_id
. Esta coluna irá se referir à chave primária da tabela da Empresa. Desta forma não podem ser associadas duas filiais a múltiplas empresas.Segunda abordagem é ter uma tabela de junção comum, digamos
Company_Branch,
Esta tabela terá duas colunas i.e.company_id
que será chave estrangeira referindo-se a chave primária na tabela de Empresa e de forma similarbranch_id
que será chave estrangeira referindo-se a chave primária da tabela de Filiais.
Se @OneToMany/@ManyToOne não tiver uma associação @ManyToOne/@OneToMany respectivamente no lado infantil, então a associação @OneToMany/@ManyToOne é unidirecional.
@OneToMany Relação Unidirecional
Nesta abordagem, qualquer entidade será responsável por fazer a relação e mantê-la. Ou a Empresa declara a relação como uma para muitas, ou a Filial declara a relação do seu fim como muitas para uma.
CASO 1: (Mapeamento com associação de chave estrangeira)
Se usarmos apenas @OneToMany
então haverá 3 tabelas. Tais como company
, branch
e company_branch.

>
NOTE:
No exemplo acima usei termos como cascata, orphanRemoval, fetch e targetEntity, que explicarei no meu próximo post.
Tabela company_branch
terá duas chaves estrangeiras company_id
e branch_id
.
Agora, se persistirmos uma Empresa e duas Filiais:
Hibernar vai executar as seguintes instruções SQL:
- A
@OneToMany
associação é, por definição, uma associação parent(non-owning), mesmo que seja unidirecional ou bidirecional. Apenas o lado pai de uma associação faz sentido em cascata as transições do estado de sua entidade para as crianças. - Quando persistir a entidade
Company
, a cascata irá propagar a operação persistente para as crianças do ramo subjacente também. Ao remover umBranch
da coleção de ramos, a linha de associação é excluída da tabela de links, e o atributoorphanRemoval
irá acionar uma remoção de ramos também.
As associações unidirecionais não são muito eficientes quando se trata de remover entidades filhas. Neste exemplo em particular, ao descarregar o contexto de persistência, Hibernate apaga todas as entradas filhas da base de dados e reinsere as que ainda são encontradas no contexto de persistência na memória.
Por outro lado, uma associação bidirecional @OneToMany
é muito mais eficiente porque a entidade filha controla a associação.
CASO 2: (Mapeamento com associação de chave estrangeira)
Se usarmos apenas @ManyToOne
então haverá 2 tabelas. Como empresa, filial.

Este código acima gerará 2 tabelas Company(company_id,name)
e Branch(branch_id,name,company_company_id)
. Aqui Branch é o lado proprietário já que tem a associação de chave estrangeira.
CASO 3: (Mapeamento com associação de chave estrangeira)
Se usarmos ambos @ManyToOne
e @OneToMany
então ele criará 3 tabelas Company(id,name), Branch(id,name,company_id), Company_Branch(company_id,branch_id)
Este mapeamento abaixo pode parecer bi-direcional mas não é. Ele define não uma relação bidirecional, mas duas relações unidirecionais separadas.

CASO 4: (Unidirecional @OneToMany com @JoinColumn)(Mapeamento com associação de chave estrangeira)
Se usarmos @OneToMany
com @JoinColumn
então haverá 2 tabelas. Como empresa, filial

Na entidade acima dentro de @JoinColumn
, o nome refere-se ao nome da coluna de chave estrangeira que é companyId i.e company_id
e nome da coluna referenciada indica a chave primária i.e id
da entidade(Empresa) à qual a chave estrangeira companyId
se refere.
CASO 5: (Mapeamento com associação de chave estrangeira)
Se usarmos @ManyToOne
com @JoinColumn
então haverá 2 tabelas. Tal como empresa,filial.

A@JoinColumn
anotação ajuda Hibernate a descobrir que existe uma coluna company_id
Chave Estrangeira na tabela da filial que define esta associação.
>
Branch terá a chave estrangeira company_id
, portanto é o lado do proprietário.
Agora, se persistirmos 1 Empresa e 2 Ramo(s):
quando remover a primeira entrada da coleção de crianças:
company.getBranches().remove(0);
Hibernate executa duas declarações em vez de uma :
- Primeiro fará com que o campo chave estrangeira seja nulo(para quebrar associação com o pai) e depois apagará o registro.
Update branch set branch_id = null where where id = 1 delete from branch where id = 1 ;
CASO 6: (Mapping with join table)
Agora, vamos considerar one-to-many
relação onde Person(id,name)
se associar com múltiplos Veichle(id,name,number)
e múltiplos veículos podem pertencer à mesma pessoa.
Um dia, enquanto na estrada o xerife Carlos encontrou poucos veículos abandonados, muito provavelmente roubados. Agora o xerife tem de actualizar os detalhes dos veículos (número, nome) na sua base de dados mas o principal problema é que não há dono para esses veículos, por isso person_id(foreign key )
campo permanecerá nulo.
Agora o sheriff salva dois veículos roubados para db da seguinte forma:
Vehicle vehicle1 = new Vehicle("ford", 1);
Vehicle vehicle2 = new Vehicle("gmc", 2);
List<Vehicle> vehicles = new ArrayList<>(); vehicles.add(vehicle1);
vehicles.add(vehicle2);
entityManager.persist(veichles);
Hibernate vai executar as seguintes instruções SQL:
insert into vehicle (id, name, person_id) values (1, "ford", null); insert into vehicle (id, name, person_id) values (2, "gmc", null);id |name|person_id|
-----|----|---------|
1 |ford|NULL |
2 |benz|NULL |
---------------------
Esta estratégia acima vai nos forçar a colocar valores nulos na coluna para lidar com relações opcionais.
Tipicamente, pensamos em many-to-many
relacionamentos quando consideramos um join table
, mas, usando uma tabela de join, neste caso, pode nos ajudar a eliminar estes valores nulos:
Esta abordagem usa uma tabela de join para armazenar as associações entre as entidades da Filial e da Empresa. @JoinTable
anotação tem sido usada para fazer esta associação.
Neste exemplo aplicamos @JoinTable
no lado do Veículo(Many Side).
Vejamos como será o esquema da base de dados:

Acima do código gerará 3 tabelas como , vehicle(id,name)
e vehicle_person(vehicle_id,person_id)
. Aqui vehicle_person
manterá a relação da chave estrangeira para ambas as entidades Pessoa e Veículo.
Então quando o xerife salvar os detalhes do veículo, nenhum valor nulo tem que ser persistido para a tabela do veículo porque estamos mantendo a associação da chave estrangeira em vehicle_person
tabelas não na tabela do veículo.
Vehicle vehicle1 = new Vehicle("ford", 1);
Vehicle vehicle2 = new Vehicle("gmc", 2);
List<Vehicle> vehicles = new ArrayList<>(); vehicles.add(vehicle1);
vehicles.add(vehicle2);
entityManager.persist(veichles);
Hibernate vai executar as seguintes instruções SQL:
insert into vehicle (id, name) values (1, "ford"); insert into vehicle (id, name) values (2, "gmc");id |name|
-----|----|
1 |ford|
2 |benz|
-----------
Este exemplo acima mostra, como nos livramos da inserção de valor nulo.
@OneToMany Bi-directional Relationship
- O bidirecional
@OneToMany
associação também requer uma@ManyToOne
associação do lado da criança. Embora o Modelo de Domínio exponha dois lados para navegar nesta associação, nos bastidores, a base de dados relacional tem apenas uma chave estrangeira para esta relação. - Todas as associações bidirecionais devem ter apenas um lado próprio (o lado infantil), sendo o outro referido como o lado inverso (ou o
mappedBy
). - Se usarmos o
@OneToMany
com o conjunto de atributosmappedBy
, então temos uma associação bidirecional, significando que precisamos ter uma associação@ManyToOne
no lado infantil que omappedBy
referências. - O elemento
mappedBy
define uma relação bidirecional. Este atributo permite referenciar as entidades associadas de ambos os lados.
A melhor maneira de mapear uma associação @OneToMany
é contar com o lado @ManyToOne
para propagar todas as mudanças de estado das entidades.

Se persistirmos 2 Ramo(s)
Hibernar gera apenas uma instrução SQL para cada entidade de Ramo persistente:
insert into company (name, id) values ("company1",1);
insert into branch (company_id, name, id) values (1,"branch1",1); insert into branch (company_id, name, id) values (1,"branch2",2);
Se removermos um Ramo:
Company company = entityManager.find( Company.class, 1L ); Branch branch = company.getBranches().get(0); company.removeBranches(branch);
Existe apenas uma instrução SQL de exclusão que é executada:
delete from Branch where id = 1
Então, a associação bidirecional @OneToMany é a melhor maneira de mapear uma relação de um para muitos bancos de dados quando realmente precisamos da coleção do lado pai da associação.
@JoinColumn
Especifica uma coluna para unir uma associação ou coleção de elementos de uma entidade. A anotação@JoinColumn
indica que esta entidade é a proprietária da relação. Esta é a tabela correspondente tem uma coluna com uma chave estrangeira para a tabela referenciada.No exemplo acima, a Entidade proprietária Ramo, tem uma Coluna de Junção chamada
company_id
que tem uma chave estrangeira para a entidade não proprietária Empresa.Quando uma associação bidirecional é formada, o desenvolvedor da aplicação deve certificar-se de que ambos os lados estão sempre em sincronia. Os
addBranches()
eremoveBranches()
são métodos utilitários que sincronizam ambas as extremidades sempre que um elemento criança (ou seja, Ramo) é adicionado ou removido.Não como o unidirecional
@OneToMany
, a associação bidirecional é muito mais eficiente ao gerenciar o estado de persistência da coleção.A remoção de cada elemento só requer uma única atualização (na qual a coluna de chave estrangeira é definida como NULL) e se o ciclo de vida da entidade criança está vinculado ao seu próprio pai ou mãe para que a criança não possa existir sem o seu pai ou mãe, então podemos anotar a associação com o atributo orphan-removal e desassociar a criança irá disparar uma declaração de exclusão na linha da tabela da criança real também.
NOTE:
- No exemplo bi-direcional acima o termo Pai e Filho na Entidade/OO/Modelo (ou seja, na classe java) se refere ao lado Não Ganancioso/Inverso e Proprietário em SQL respectivamente.
No ponto de vista java Companhia é o Pai e ramo é o filho aqui. Como uma filial não pode existir sem pai.
No ponto de vista de SQL a filial é o lado do Proprietário e a Empresa é o lado do Não remunerado(Inverso). Como há 1 empresa para N filiais, cada filial contém uma chave estrangeira para a empresa a que pertence, o que significa que a filial “possui” (ou literalmente contém) a conexão (informação). Isto é exatamente o oposto do mundo OO/modelo.
@OneToMany Bi-directional Relationship(Mapping with join table)
From CASE 6
we have Unidirectional mapping with @JoinTable
, so if we add mappedBy
attribute to Person entity then the relationship will become bi-directional.
SUMMARY
Existem várias coisas a notar no mapeamento acima:
- A associação
@ManyToOne
utilizaFetchType.LAZY
porque, caso contrário, voltaríamos ao EAGER fetching que é ruim para o desempenho. - As associações bidirecionais devem ser sempre atualizadas em ambos os lados, portanto o lado dos Pais deve conter a combinação
addChild
eremoveChild
(O lado dos Pais contém dois métodos de utilidadeaddBranches
eremoveBranches
). Estes métodos garantem que sempre sincronizamos ambos os lados da associação, para evitar problemas de corrupção de objetos ou dados relacionais. - A entidade criança, Branch, implementa os métodos igual e hashCode. Uma vez que não podemos confiar num identificador natural para a verificação da igualdade, precisamos de utilizar o identificador da entidade em vez disso. Contudo, precisamos de o fazer correctamente para que a igualdade seja consistente em todas as transições de estados de entidade. Como confiamos na igualdade para os removeBranches, é uma boa prática sobrepor iguais e hashCode para a entidade filha em uma associação bidirecional.
- A associação @OneToMany é por definição uma associação de pais, mesmo que seja unidirecional ou bidirecional. Apenas o lado pai de uma associação faz sentido para cascata as transições de estado da sua entidade para crianças.