Tutorial de hibernação de um para muitos
1. Introdução
Este rápido tutorial do Hibernate nos levará por um exemplo de mapeamentoone-to-many usando anotações JPA - uma alternativa ao XML.
Também aprenderemos o que são relacionamentos bidirecionais, como eles podem criar inconsistências e como a ideia deownership can ajuda.
Leitura adicional:
Inicialização de mola com hibernação
Uma introdução rápida e prática à integração do Spring Boot e do Hibernate / JPA.
Uma visão geral dos identificadores no Hibernate
Aprenda a mapear identificadores de entidade com o Hibernate.
2. Descrição
Simplificando,one-to-many mapping means that one row in a table is mapped to multiple rows in another table.
Vejamos o seguinte diagrama de relacionamento de entidade para ver uma associaçãoone-to-many:
Neste exemplo, implementaremos um sistema de carrinho, onde temos uma tabela para cada carrinho e outra tabela para cada item. One cart can have many items, so here we have a one-to-many mapping.
A maneira como isso funciona no nível do banco de dados é que temoscart_id como chave primária na tabelacart e tambémcart_id como chave estrangeira emitems.
E, a maneira como fazemos em código é com@OneToMany.
Vamos mapear a classeCart para o objetoItems de uma forma que reflete o relacionamento no banco de dados:
public class Cart {
//...
@OneToMany(mappedBy="cart")
private Set items;
//...
}
Também podemos adicionar uma referência aCart emItems usando@ManyToOne, tornando este um relacionamentobidirectional. Bidirectional significa quewe are able to access items from carts, and also carts from items.
A propriedademappedBy é o que usamos para dizer ao Hibernate qual variável estamos usando para representar a classe pai em nossa classe filha.
As seguintes tecnologias e bibliotecas são usadas para desenvolver um aplicativo Hibernate de amostra que implementa a associaçãoone-to-many:
-
JDK 1.8 ou posterior
-
Hibernate 5
-
Maven 3 ou posterior
-
Banco de dados H2
3. Configuração
3.1. Configuração do banco de dados
Abaixo está nosso script de banco de dados para tabelasCarteItems. Usamos a restrição de chave estrangeira para mapeamento deone-to-many:
CREATE TABLE `Cart` (
`cart_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`cart_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
CREATE TABLE `Items` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`cart_id` int(11) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `cart_id` (`cart_id`),
CONSTRAINT `items_ibfk_1` FOREIGN KEY (`cart_id`) REFERENCES `Cart` (`cart_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
Nossa configuração do banco de dados está pronta; vamos criar o projeto de exemplo do Hibernate.
3.2. Dependências do Maven
Em seguida, adicionaremos as dependências dos drivers Hibernate e H2 ao nosso arquivopom.xml. A dependência do Hibernate usa o log do JBoss e é automaticamente adicionada como dependências transitivas:
-
Versão do Hibernate 5.2.7.Final
-
Versão do driver H2 1.4.197
3.3. Configuração do Hibernate
Aqui está a configuração do Hibernate:
org.h2.Driver
jdbc:h2:mem:spring_hibernate_one_to_many
sa
org.hibernate.dialect.H2Dialect
thread
true
3.4. ClasseHibernateAnnotationUtil
Com a classeHibernateAnnotationUtil, precisamos apenas fazer referência ao novo arquivo de configuração do Hibernate:
private static SessionFactory sessionFactory;
private SessionFactory buildSessionFactory() {
ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().
configure("hibernate-annotation.cfg.xml").build();
Metadata metadata = new MetadataSources(serviceRegistry).getMetadataBuilder().build();
SessionFactory sessionFactory = metadata.getSessionFactoryBuilder().build();
return sessionFactory;
}
public SessionFactory getSessionFactory() {
if(sessionFactory == null) sessionFactory = buildSessionFactory();
return sessionFactory;
}
4. Os modelos
As configurações relacionadas ao mapeamento serão feitas usando anotações JPA nas classes de modelo:
@Entity
@Table(name="CART")
public class Cart {
//...
@OneToMany(mappedBy="cart")
private Set items;
// getters and setters
}
Observe que a anotação@OneToMany é usada para definir a propriedade na classeItems que será usada para mapear a variávelmappedBy. É por isso que temos uma propriedade chamada “cart” na classeItems:
@Entity
@Table(name="ITEMS")
public class Items {
//...
@ManyToOne
@JoinColumn(name="cart_id", nullable=false)
private Cart cart;
public Items() {}
// getters and setters
}
É importante notar que a anotação@ManyToOne está associada à variável de classeCart. A anotação@JoinColumn faz referência à coluna mapeada.
5. Em ação
No programa de teste, estamos criando uma classe com um métodomain () para obter a Sessão do Hibernate e salvar os objetos do modelo no banco de dados implementando a associaçãoone-to-many:
sessionFactory = HibernateAnnotationUtil.getSessionFactory();
session = sessionFactory.getCurrentSession();
System.out.println("Session created");
tx = session.beginTransaction();
session.save(cart);
session.save(item1);
session.save(item2);
tx.commit();
System.out.println("Cart ID=" + cart.getId());
System.out.println("item1 ID=" + item1.getId()
+ ", Foreign Key Cart ID=" + item.getCart().getId());
System.out.println("item2 ID=" + item2.getId()
+ ", Foreign Key Cart ID=" + item.getCart().getId());
Esta é a saída do nosso programa de teste:
Session created
Hibernate: insert into CART values ()
Hibernate: insert into ITEMS (cart_id)
values (?)
Hibernate: insert into ITEMS (cart_id)
values (?)
Cart ID=7
item1 ID=11, Foreign Key Cart ID=7
item2 ID=12, Foreign Key Cart ID=7
Closing SessionFactory
6. A anotação@ManyToOne
Como vimos na seção 2, podemos especificar um relacionamentomany-to-one usando a anotação@ManyToOne. Um mapeamentomany-to-one significa que muitas instâncias desta entidade são mapeadas para uma instância de outra entidade -many items in one cart.
The @ManyToOne annotation lets us create bidirectional relationships, too. Cobriremos isso em detalhes nas próximas subseções.
6.1. Inconsistências e propriedade
Agora, seCart referenciouItems, masItems não referiuCart,our relationship would be unidirectional. The objects would also have a natural consistency.
Em nosso caso, porém, a relação é bidirecional,bringing in the possibility of inconsistency.
Vamos imaginar uma situação em que um desenvolvedor deseja adicionaritem1 acarteitem2 acart2, mas comete um erro para que as referências entrecart2 eitem2 tornam-se inconsistentes:
Cart cart1 = new Cart();
Cart cart2 = new Cart();
Items item1 = new Items(cart1);
Items item2 = new Items(cart2);
Set itemsSet = new HashSet();
itemsSet.add(item1);
itemsSet.add(item2);
cart1.setItems(itemsSet); // wrong!
Como mostrado acima,item2 faz referência acart2 w enquantocart2 não faz referência aitem2 —e isso é ruim.
How should Hibernate save item2 to the database? A chave estrangeiraitem2 fará referência acart1 oucart2?
Resolvemos essa ambigüidade usando a ideia de umowning side do relacionamento - as referências pertencentes ao lado proprietário têm precedência e são salvas no banco de dados.
6.2. items como o lado proprietário
Conforme declarado emJPA specification na seção 2.9,it’s a good practice to mark many-to-one side as the owning side.
Em outras palavras,Items deve serowning side areiaCart o lado inverso, que é exatamente o que fizemos anteriormente.
Então, como conseguimos isso?
Ao incluir o atributomappedBy na classeCart, marcamos como o lado inverso.
Ao mesmo tempo, também anotamos o campoItems.cart com@ManyToOne, tornandoItems o lado proprietário.
Voltando ao nosso exemplo de “inconsistência”, agora o Hibernate sabe que oitem2‘s reference is more important and will save item2‘s reference to the database.
Vamos verificar o resultado:
item1 ID=1, Foreign Key Cart ID=1
item2 ID=2, Foreign Key Cart ID=2
Emboracart faça referência aitem2 em nosso snippet, a referência deitem2 acart2 é salva no banco de dados.
6.3. Cart como o lado proprietário
Também é possível marcar o ladoone-to-many como o lado proprietário e o ladomany-to-one como o lado inverso.
Embora essa não seja uma prática recomendada, vamos prosseguir e tentar.
O snippet de código abaixo mostra a implementação do ladoone-to-many como o lado proprietário:
public class ItemsOIO {
// ...
@ManyToOne
@JoinColumn(name = "cart_id", insertable = false, updatable = false)
private CartOIO cart;
//..
}
public class CartOIO {
//..
@OneToMany
@JoinColumn(name = "cart_id") // we need to duplicate the physical information
private Set items;
//..
}
Observe como removemos o elementomappedBy e definimosmany-to-one @JoinColumn comoinsertableeupdatable parafalse.
Se executarmos o mesmo código, o resultado será o oposto:
item1 ID=1, Foreign Key Cart ID=1
item2 ID=2, Foreign Key Cart ID=1
Como mostrado acima, agoraitem2 pertence acart.
7. Conclusão
Vimos como é fácil implementar o relacionamentoone-to-many com o banco de dados Hibernate ORM e H2 usando anotações JPA.
Além disso, aprendemos sobre relacionamentos bidirecionais e como implementar a noção de possuir lado.
O código-fonte deste tutorial pode ser encontradoover on GitHub.