Quando já temos nossos objetos (POJOs) na nossa aplicação, devemos adicionar marcadores de persistência para tornar esse objeto persistente. Após anotarmos corretamente nossos objetos, eles podem estar em um dos três diferentes estados: transiente, persistente ou separado.

Os objetos transientes existem apenas na memória, eles não são persistidos na base de dados. Portanto, esses objetos não são gerenciados pelo Hibernate.

Os objetos persistentes existem na base de dados e são gerenciados pelo Hibernate. Se alguma propriedade for alterada num objetos persistente ele será também alterado na base de dados (representando uma operação de update).

Por fim, os objetos separados possuem uma representação na base de dados, mas alterações não serão persistidas na base de dados, assim como alterações na base de dados não são refletivas em alterações nos objetos. Essa separação temporária entre o objeto e a base de dados é ilustrada abaixo. Podemos evidenciar que não existe uma troca entre a base de dados e os objetos.

Objetos separados existem, mas não são gerenciados pelo Hibernate

Figura 1. Objetos separados existem, mas não são gerenciados pelo Hibernate

Já a figura abaixo demonstra um objeto persistente sendo gerenciado pelo Hibernate.

Objetos persistentes sendo gerenciados pelo Hibernate

Figura 2. Objetos persistentes sendo gerenciados pelo Hibernate.

Alterações em objetos separados podem ser persistidos somente se a aplicação anexar (reattach) esse objeto para uma sessão válida do Hibernate. Dessa forma, poderíamos tornar um objeto separado persistente. Um objeto separado pode ser associado com uma nova sessão chamando um dos métodos load(), refresh(), merge(), update() ou save() numa nova sessão com uma referência para o objeto separado. Após isso o objeto separado passaria a ser um objeto persistente gerenciado por uma nova sessão do Hibernate.

Entidades Hibernate, Classes Java e Nomes

As Entidades do Hibernate são objetos Java com mapeamentos que permitem que esses objetos possam ser armazenados na base de dados. Esse mapeamento indica como as propriedades do objeto deveriam ser armazenadas.

Normalmente o nome da classe é o nome da entidade, porém, isto também pode ser alterado através de mapeamento, podendo assim ter uma tabela com um nome diferente da classe.

Identificadores

O Hibernate obriga todas as entidades terem ao menos um identificador, que representa a coluna chave-primária da tabela que será persistida.

Entidades e Associações

Entidades podem conter referencias, representados através de chave estrangeira (foreign key), para outras entidades através de propriedades ou coleções.

Quando apenas um dos pares de entidade contém uma referencia para a outra, a associação é dita como unidirecional, se for mútua, então ela é bidirecional. Esse é um dos grandes erros quando estamos projetando modelos de entidades no Hibernate, que é achar que todas as associações são bidirecionais.

Quando temos uma associação bidirecional, devemos sempre indicar quem é o dono do relacionamento. Apenas um lado do relacionamento irá resultar em alterações (updates) na chave estrangeira; e, de fato, o Hibernate nos permite fazer isso marcando uma das extremidades da associação como sendo gerenciada pela outra. Isto é feito através da anotação “mappedBy”. É muito importante sabermos que mappedBy é sobre como relacionamentos de chave estrangeira entre entidades são salvas, e nada tem haver sobre salvar a própria entidade.

No seguinte exemplo demonstrado abaixo podemos imaginar um sistema acadêmico onde um professor dá aula em várias turmas, (um para muitos) e cada turma pode ter vários professores (muitos para muitos). Supondo que o dono do relacionamento seja a classe Professor - um professor tem várias turmas - o mappedBy precisa ser colocado na entidade Turma. Ou seja, sempre na associação que não é o dono da relação.

class Professor {  
@ManyToMany  
private Collection<Turma> turmas;  
}  
      
class Turma {  
@ManyToMany(mappedBy = "turmas")  
private Collection<Professor> professores;  
}  

A tabela abaixo mostra como podemos selecionar o lado do relacionamento que deveria se tornar o dono de uma associação bidirecional.

Tipo da Associação Opção
One-to-one Qualquer um dos lados pode ser o dono, mas apenas um deles deve realmente ser o dono. Se isso não for especificado teremos uma dependência circular.
One-to-many O lado "many" da associação deve ser tornado como o dono da associação.
Many-to-one Este é como o de cima, porém visualizado sob uma perspectiva oposta, mas a mesma regra se aplica - o lado “many” é o dono da associação.
Many-to-many Qualquer um dos lados pode ser o dono da associação.

Para ilustrar isto vamos imaginar um E-mail e uma Mensagem. Um E-mail pode possuir uma Mensagem, e essa Mensagem pertence a um E-mail. Portanto, temos um relacionamento um para um (One-To-One) entre E-mail e Mensagem. Abaixo temos um exemplo salvando um E-mail e uma Mensagem.

Email email = new Email("Teste Email");
Message message = new Message("Teste Message");
email.setMessage(message);
// Incorreto
session.save(email);
session.save(message);
System.out.println(message.getEmail());

Se executarmos esse código teremos como resultado "null".

Para termos o resultado desejado, ambas as entidades devem ser atualizadas. Se a entidade E-mail é a dona da associação, isso apenas assegura a atribuição adequada de um valor da coluna de chave estrangeira. Portanto, apenas adicionamos uma chamada explicita message.setEmail(email);

O nosso exemplo completo fica:

Email email = new Email("Test Email");
Message message = new Message("Test Message");
email.setMessage(message);
message.setEmail(email); // Correto
session.save(email);
session.save(message);
System.out.println(message.getEmail());

Portanto, agora verificamos abaixo um exemplo esclarecedor que muitos desenvolvedores fazem confusão:

//iniciando uma nova sessao
openSession();
beginTransaction();
Email email = new Email("Test Email");
Message message = new Message("Test Message");
email.setMessage(message);
save(email,message);
System.out.println("Armazenado...");
System.out.println(email);
System.out.println(email.getMessage());
System.out.println(message);
System.out.println(message.getEmail());
Serializable emailPrimaryKey = session.getIdentifier(email);
Serializable messagePrimaryKey = session.getIdentifier(message);
endTransaction();
closeSession();
System.out.println();

//iniciando uma nova sessao
openSession();
beginTransaction();
email = (Email)session.get(Email.class,emailPrimaryKey);
message = (Message)session.get(Message.class,messagePrimaryKey);
System.out.println("Retrieved...");
System.out.println(email);
System.out.println(email.getMessage());
System.out.println(message);
System.out.println(message.getEmail());
endTransaction();
closeSession();

Se executarmos este exemplo temos como resposta:

Stored...
Test Email
Test Message
Test Message
null

Retrieved...
Test Email
Test Message
Test Message
Test Emails

Podemos verificar que quando as entidades são inicialmente armazenadas, o objeto Message associada a E-mail é inicialmente null, mesmo após o Hibernate ter armazenado a informação. Isso ocorreu porque a entidade em memória não foi atualizada, dessa forma as alterações não surtiram efeito em memória. Entretanto, após fecharmos a sessão e abrimos uma nova sessão, carregadas as entidades da base de dados, as entidades foram atualizadas.

Devido a sessão ter sido fechada, a sessão é forçada a recarregar as entidades da base de dados. Como a entidade Email é a dona da associação, quando alteramos a entidade Email e salvamos ela, sua chave estrangeira também foi atualizada. Portanto, quando recarregamos as entidades, a entidade associada Message e seus detalhes foram obtidos da mesma chave estrangeira.

Agora, se deixarmos o Email como a dona da associação e alterarmos o pedaço do código anterior para o código conforme mostrado abaixo:

Email email = new Email("Test Email");
Message message = new Message("Test Message");
//email.setMessage(message);
message.setEmail(email);

Se recarregarmos as entidade como fizemos no código anterior teremos o seguinte resultado:

Stored...
Test Email
null
Test Message
Test Email

Retrieved...
Test Email
null
Test Message
null

Como podemos verificar apesar de ambas as entidades terem sido salvas na base de dados, a tentativa de associar Email com Mensage não foi persistida na base de dados, porque Email não é a dona da associação.

Esse exemplo esclarece como podemos usar corretamente a partir de agora o mappedBy nas nossas classes persistentes.

Neste artigo vimos sobre o ciclo de vida de objetos persistentes do Hibernate, alguns conceitos de Entidades, classes, identificadores e associações. Também vimos mais sobre associações bidirecionais através do uso de mappedBy onde devemos explicitar as associações e quem será o dono de uma associação que de fato irá persistir as alterações na base de dados.