Tutorial de hibernação de um para muitos

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.

Read more

Uma visão geral dos identificadores no Hibernate

Aprenda a mapear identificadores de entidade com o Hibernate.

Read more

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:

image

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

Visite o repositório central Maven para obter as versões mais recentes deHibernatee as dependênciasH2.

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.