Hibernate Mapping: Mapeando Relacionamentos entre Entidades

Veja neste artigo como podemos fazer os relacionamentos entre entidades no Hibernate. Veja também quais são os principais atributos utilizados, quais são obrigatórios e quais são opcionais.

Hibernate facilita o armazenamento e a recuperação de objetos Java através do Mapeamento Objeto-Relacional (Object/Relational Mapping - ORM). O Hibernate oferece aos desenvolvedores a opção de criar mapeamentos entre modelos de base de dados e modelos de objetos através de duas formas: arquivos XML ou através de anotações no código fonte dos objetos. A opção preferível é a segunda.

Quando utilizamos anotações temos algumas vantagens como: código mais intuitivo do que arquivos baseados em XML, menos detalhes para se preocupar, facilidade de visualização das configurações, etc.

O Hibernate usa e suporta anotações JPA 2 que por sua vez suportam o mapeamento entre entidades. JPA 2 suporta as associações One-to-one (Um para Um), One-to-Many (Um para Muitos), Many-to-one (Muitos para Um) e Many-to-Many (Muitos para Muitos).

No restante do artigo estaremos estudando como podemos definir relacionamentos entre as nossas entidades.

1. Mapeando Associações One-to-One Embutidas

Neste relacionamento teremos os atributos das entidades relacionadas que serão persistidas na mesma tabela. Por exemplo, uma classe Pessoa que tem um relacionamento One-to-One com a classe endereço, conforme exemplo da Listagem 1.

Listagem 1. Definindo uma tabela com uma anotação @Embeddable

@Entity public class Pessoa { @Id private long id; private String nome; @Embedded private Endereco endereco; //get's e set's } @Embeddable public class Endereco { private String logradouro; //get's e set's }

Nesse caso temos todos os campos de uma tabela mantidos dentro de uma mesma tabela como se fosse outra tabela. Os atributos @Embedded e @Embeddable são usados para gerenciar este relacionamento. Uma entidade embutida deve ser composta inteiramente de campos e atributos básicos. As entidades embutidas usam as anotações @Basic, @Column, @Lob, @Temporal, e @Enumerated. Podemos observar que a chave-primária não pode ser mantida pela classe embutida e sim na classe que contém ela. A anotação @Embeddable não tem qualquer atributo adicional, ela é pura. A anotação @Embedded é utilizada para marcar campos ou métodos getter nas entidades que referencia a entidade embutida. A anotação @Embedded nos permite sobrescrever colunas através das tags @AttributeOverride e @AttributeOverrides.

No exemplo da Listagem 2 demonstramos como utilizar @AttributeOverride e @AttributeOverrides para sobrescrever nomes de coluna endereco e pais com os atributos ENDER e PAIS.

Listagem 2. Utilizando @AttributeOverride e @AttributeOverrides

@Embedded @AttributeOverrides({ @AttributeOverride(name="endereco",column=@Column(name="ENDER") ), @AttributeOverride(name="pais",column=@Column(name="PAIS")) }) public Endereco getEndereco() { return this.endereco; }

Como uma última dica, vale ressaltar que o Hibernate e JPA não suportam mapear um objeto embutido em mais de uma tabela.

2. Mapeando Associações One-to-One Convencionais

A anotação One-to-One é utilizada para associar duas entidades onde uma não é componente da outra, ao contrário da definição acima. Numa associação One-to-One também podemos ter um relacionamento bidirecional. Nesse caso, um dos lados precisará ser o dono do relacionamento e ser responsável por atualizar uma coluna com uma chave estrangeira. Para mais informações veja mais sobre o atributo mappedBy já discutido em outro artigo.

A aplicação do One-to-one é simples e possui apenas atributos opcionais. Na Listagem 3 temos um exemplo de como aplicar a anotação.

Listagem 3. Utilizando a anotação @OneToOne.

@OneToOne public Endereco getEndereco() { return this.endereco; }

Os atributos opcionais são os seguintes:

- targetEntity: é a classe da entidade que é o destino da associação. O default é o tipo do campo ou a propriedade que armazena a associação.

- cascade: pode ser configurado para qualquer um dos membros da enumeração javax.persistence.CascadeType.

- fetch: pode ser configurado para EAGER ou LAZY.

- optional: indica se o valor sendo mapeado pode ser null.

- orphanRemoval: indica que se o valor sendo mapeado é deletado, esta entidade também será deletada.

- mappedBy: indica que um relacionamento one-to-one bidirecional é apropriado pela entidade nomeada. O dono possui a chave-primária da entidade subordinada.

3. Mapeando Associações Many-to-One ou One-to-Many

A anotação @OneToMany pode ser aplicada para um campo ou propriedade de uma coleção ou um array representando o "many" da associação.

O atributo mappedBy é obrigatório numa associação bidirecional e opcional numa associação unidirecional. O atributo cascade também é opcional, possuindo um membro da enumeração javax.persistence.CascadeType. O atributo targetEntity também é opcional. Por fim, fetch também é opcional permitindo LAZY ou EAGER. Segue na Listagem 4 um exemplo da utilização.

Listagem 4. Utilizando mappedBy e cascade na anotação @OneToMany.

@OneToMany(cascade = ALL, mappedBy = "publicador") public Set<Livro> getLivros() { return livros; }

A anotação many-to-one deste relacionamento é expresso da mesma forma que a anotação anterior, conforme mostrado na Listagem 5.

Listagem 5. Utilizando a anotação @ManyToOne.

@ManyToOne @JoinColumn(name = "publicador_id") public Publicador getPublicador() { return publicador; }

@JoinColumn é utilizado para nomearmos a coluna que possui a chave-estrangeira requerida pela associação. Se nada for especificado, será utilizado o nome do campo.

Outra anotação bastante importante e utilizada é a @JoinTable que também é encontrada nos relacionamentos @ManyToMany.

Para exemplificar melhor podemos imaginar que tenhamos Usuarios e Perfis, ou seja, temos duas tabelas na nossa base de dados, sendo que cada usuário poderá ter apenas um perfil. Dessa forma, temos além das duas tabelas (Usuário e Perfil) uma terceira tabela para o mapeamento dos perfis para os usuários. Essa última tabela Usuários_Perfil é uma tabela intermediária que possui duas chaves estrangeiras para cada uma das tabelas Usuário e Perfil. Para mapear essa situação podemos usar o JoinTable. O exemplo da Listagem 6 mostra como ficaria uma classe Java mapeando o Usuario.

Listagem 6. Exemplificando a classe Usuario.

@Entity @Table(name="usuario") public class Usuario { @Id @GeneratedValue private Integer id; private String login; private String senha; @OneToOne(cascade=CascadeType.ALL) @JoinTable(name="usuario_perfil", joinColumns={@JoinColumn(name="usuario_id", referencedColumnName="id")}, inverseJoinColumns={@JoinColumn(name="perfil_id", referencedColumnName="id")}) private Perfil perfil; //getters e setters }

Na Listagem 7 temos o mapeamento da classe Perfil.

Listagem 7. Exemplificando a classe Perfil.

@Entity @Table(name="perfil") public class Perfil { @Id @GeneratedValue private Integer id; private String nomePerfil; @OneToMany(cascade=CascadeType.ALL) @JoinTable(name="usuario_perfil", joinColumns={@JoinColumn(name="perfil_id", referencedColumnName="id")}, inverseJoinColumns={@JoinColumn(name="usuario_id", referencedColumnName="id")}) private List<Usuario> usuarioList; //getters e setters }

A anotação @JoinTable indica que estamos interagindo com uma tabela intermediária, neste caso a tabela usuario_perfil, e no exemplo acima também configuramos o relacionamento e o mapeamento de colunas. joinColumns é resposnável pelo mapeamento de colunas do lado que é o dono. O atributo name possui o nome da coluna da tabela intermediária, referencedColumnName contém o nome da coluna chave-primária do lado que é dono (no nosso caso a tabela usuario possui como chave-primária id).

O atributo inverseJoinColumns é responsável por mapear colunas do lado inverso.

Para testar esse código, siga o exemplo da Listagem 8.

Listagem 8. Testando os códigos anteriores.

public class Teste { public static void main(String[] args) { SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); Session session = sessionFactory.openSession(); session.beginTransaction(); Perfil perfil = (Perfil) session.get(Perfil.class, 2); Usuario usuario = new Usuario("testeuser", "senhaqualquer"); usuario.setPerfil(perfil); session.save(usuario); session.getTransaction().commit(); session.close(); } }

Para simplificar ainda mais as ideias as tabelas, temos o exemplo da Listagem 9.

Listagem 9. Sqls utilizados nos exemplos.

CREATE TABLE `perfil` ( `id` int(6) NOT NULL AUTO_INCREMENT, `perfil` varchar(20) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; CREATE TABLE `usuario` ( `id` int(6) NOT NULL AUTO_INCREMENT, `login` varchar(20) NOT NULL, `senha` varchar(20) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8; CREATE TABLE `usuario_perfil` ( `usuario_id` int(6) NOT NULL, `perfil_id` int(6) NOT NULL, KEY `usuario` (`usuario_id`), KEY `perfil` (`perfil_id`), CONSTRAINT `usuario` FOREIGN KEY (`usuario_id`) REFERENCES `usuario` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `perfil` FOREIGN KEY (`perfil_id`) REFERENCES `perfil` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

4. Mapeando Associações Many-to-Many

A anotação @ManyToMany tem os seguintes atributos:

- mappedBy: é o campo que indica o dono do relacionamento. Este atributo só é necessário quando a associação é bidirecional.

- targetEntity: é a classe da entidade que é o destino da associação.

- cascade: indica o comportamento em cascata da associação, o default é none (nenhum).

- fetch: indica o comportamento de busca da associação, sendo que o default é LAZY.

O exemplo da Listagem 10 demonstra a utilização desta anotação.

Listagem 10. Utilizando a anotação @ManyToMany.

@ManyToMany(cascade = ALL) public Set<Autor> getAutores() { return autores; }

Conclusão

Neste artigo vimos como mapear relacionamentos utilizando o Hibernate. Também vimos como omitir certos campos e quais são os principais e mais utilizados atributos para cada uma das anotações vistas.

Até a próxima!

Bibliografia

[1]Hibernate - JBoss Community, disponível em www.hibernate.org/

[2]Documentação de Referência Hibernate, disponível em https://docs.jboss.org/hibernate/core/3.6/reference/pt-BR/html/index.html

[3] Introdução ao Hibernate, disponível em http://www.hibernate.org/hib_docs/v3/reference/en/html/queryhql.html

[4] Jeff Linwood and Dave Minter. An introduction to persistence using Hibernate 3.5, Second Edition. Apress.

[5] Steve Perkins. Hibernate Search by Example: Explore the Hibernate Search system and use its extraordinary search features in your own applications. Packt Publishing.

Artigos relacionados