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.