Trabalhar de maneira eficiente com o EntityManager
pode parecer um pouco complexo caso você esteja iniciando agora sua jornada com JPA/Hibernate. Um dos motivos que corroboram essa complexidade é a maneira que estamos acostumados a pensar sobre Entidades.
Antes entendemos a entidade como uma tabela, e seus relacionamentos, e para cada alteração, inserção ou remoção somos obrigados a disparar uma operação de SQL.
Já ao trabalhar com JPA/Hibernate devemos colocar a entidade no estado MANAGED
, utilizar os métodos que contém as regras de negócio, para que estes alterem os valores de cada atributo, e a JPA e Hibernate decidam quais as operações SQL devem ser geradas para sincronizar os valores com Banco de Dados (BD).
1. Conheça a EntityManager
A EntityManager
é uma abstração que representa uma sessão com banco de dados, é através desta abstração que delegamos ao Hibernate a responsabilidade de sincronizar os dados da memória ao Banco de Dados (BD).
Isto significa que a cada sessão com BD, pode ser criado uma instância da EntityManager
, e esta sessão sera responsável por gerenciar as entidades que estão estado MANAGED
.
EntityManager
também é chamada de Contexto de Persistência, ou Cache de primeiro nível, isto porque quando uma entidade é transitada ao estado MANAGED
ela é armazenada em memória para otimizar o acesso à mesma.
Enquanto uma entidade esteja presente ao Contexto de Persistência as alterações dos valores de seus atributos serão propagados ao BD.
2. Domine os estados das Entidades
Uma entidade no decorrer do seu ciclo de vida pode transitar sobre os seguintes estados:
Transient ou New: É quando o objeto foi instânciado, porém, não possui um ID ou primary key, ou seja, não está em sincronia com Banco de dados.
Managed: É quando a entidade em memória esta associada a uma linha da tabela no banco de dados, ou seja, ela possui um ID. Quando a entidade esta neste estado ela é gerenciada pelo Contexto de Persistência, e qualquer alteração feita nesta entidade é detectada pelo Hibernate que por sua vez traduz sincroniza com o BD através de comandos SQL.
Detached: É quando a entidade é removida do Contexto de Persistência, então a entidade não tem suas alterações em memoria propagadas ao BD.
Removed: É quando a entidade tem um agendamento para exclusão, ou seja, no momento oportuno o Hibernate irá propagar a operação SQL DELETE.
Para que uma entidade transite de um estado para outro devemos propagar as operações do EntityManger, observe o diagrama abaixo.
Fonte: Vlad Mihalcea - A beginner’s guide to entity state transitions with JPA and Hibernate
Em seguida será apresentado algumas maneiras de transitar uma entidade sobre cada um dos estados. Caso queria entender melhor como os testes foram criados pode acessar este repositorio no Github.
2.1 Transitando uma entidade para o estado managed
Dado que instanciamos uma entidade, a mesma nasce em estado new
, ou seja, a mesma não referência um registro no BD, para que isto aconteça é necessário que a entidade esteja em estado managed
, a transição pode ser feita dado que exista um contexto transacional aberto e que a operação de persist da EntityManager
seja aplicado a entidade.
@Test
void deveTransitarDeTransientParaManaged() {
Aluno aluno = new Aluno("Jordi H.");
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
entityManager.persist(aluno);
transaction.commit();
assertTrue(
entityManager.contains(aluno),
"Esta entidade deveria estar no Contexto de Persistencia"
);
}
A transição feita no teste acima pode ser resumida aos seguintes passos:
- É criado uma instância da entidade Aluno, que esta em estado
transient
ounew
. - É solicitado uma
Transaction
ao Contexto de Persistência, que é aberta na próxima linha. - Dado que a
Transaction
esta aberta, é disparado a operação de persist, que transita a entidade para o estadomanaged
. - Após a transação ser confirmada, e os dados serem sincronizados ao banco, a
EntityManager
continua aberta, isto significa que a entidade ainda deve pertencer ao Contexto de Persistência. - É provado que a entidade existe no Contexto de Persistência quando o método
assertTrue
é chamado.
2.2 Transitando uma entidade de managed
para detached
Dado que uma entidade esteja no estado managed
uma das formas de transita-la para detached
é aplicar a operação detach
da EntityManager
.
@Test
void deveTransitarDeManagedParaDetached() {
Aluno aluno = new Aluno("Jordi H.");
EntityTransaction transaction = this.manager.getTransaction();
transaction.begin();
manager.persist(aluno);
transaction.commit();
this.manager.detach(aluno);
assertFalse(
manager.contains(aluno),
"esta entidade não deve participar do contexto de persistencia"
);
}
Observamos que no teste acima, que dado uma entidade esteja no estado managed
, não é necessário que contenha uma Transaction
aberta para transitar a entidade para detached
. No método assertFalse
provamos que após aplicação da operação detach
, a entidade aluno não faz mais parte do Contexto de Persistência.
2.3 Transitando uma entidade managed
para removed
Dado que existe uma entidade esta managed
transita-la para removed
é possivel caso seja aplicado a operação remove da EntityManager
.
@Test
void deveTransitarDeManagedParaRemoved() {
Aluno aluno = new Aluno("Jordi H.");
EntityTransaction transaction = this.manager.getTransaction();
transaction.begin();
manager.persist(aluno);
manager.remove(aluno);
transaction.commit();
assertFalse(
manager.contains(aluno),
"esta entidade não deveria pertencer ao Contexto de Persistencia"
);
assertNull(
manager.find(Aluno.class,aluno.getId()),
"Não deveria existir registro para este id"
);
}
No teste acima é possivel observar que dado que exista uma Transaction
aberta, podemos agendar a exclusão da entidade, que é realizado no momento do commit. Então primeiro garantimos que a entidade não pertence ao Contexto de Persistência no método assertFalse
. O método assertNull
prova que não existe registro de entidade para o id informado.
2.4 Transitando uma entidade detached
para managed
Dado que exista uma entidade em estado detached
e seja necessário transita-la para managed
é possivel através da aplicação da operação merge da EntityManager
.
@Test
void deveTransitarDeDetachedParaManaged() {
Aluno aluno = new Aluno("Jordi H.");
EntityTransaction transaction = this.manager.getTransaction();
transaction.begin();
manager.persist(aluno);
transaction.commit();
this.manager.detach(aluno);
assertFalse(
manager.contains(aluno),
"esta entidade não deve participar do Contexto de Persistencia"
);
aluno.setNome("Yuri Matheus");
transaction.begin();
Aluno alunoAposMerge = manager.merge(aluno);
transaction.commit();
assertTrue(
manager.contains(alunoAposMerge),
"esta entidade deve pertencer ao estado Contexto de Persistencia"
);
}
O teste acima pode ser resumido no seguintes passos:
- É criado uma entidade Aluno, que é movida para o estado
managed
após a aplicação da operação de persist. - Entidade Aluno é movida para o estado
detached
após a operação detach, e através doassertFalse
provamos que a mesma não pertence ao Contexto de Persistência. - Entidade tem uma alteração de valor em um dos seus atributos.
- É aberto uma nova
Transaction
e a operação de merge é aplicada, movimentando a entidade para o estadomanaged
. - O método
assertTrue
prova que a entidade pertence ao Contexto de Persistência.
3. Tenha um controle Transacional bem definido
O Contexto de Persistência está intimamente relacionado a transação (Transaction), isto significa que enquanto uma transação estiver aberta as entidades permaneceram em estado MANAGED
, ou seja, as alterações de valores serão propagados ao banco.
Outra vantagem de manter uma transação aberta é que todas as operações no escopo do método, se tornam uma única unidade de processamento, caso uma das operações falhe, todas as outras iram falhar junto, deixando nosso método atômico.
3.1 Definindo um controle Transacional
Uma transação é composta por uma ou mais operações que devem ser confirmadas (commit) ou revertidas (rollback) juntas. Isto porque se caso uma destas operações falhar e as demais prosseguirem pode causar inconsistência aos dados. Os fluxos de negócio seguem a mesma filosofia, um único erro deve invalidar todas as alterações que façam parte deste fluxo.
Sabe-se que grande parcela do mercado utiliza as Exceções não checadas para delimitar um erro no fluxo de negócio. E caso seu projeto siga esta metodologia, pode-se favorecer o uso destas exceções como um ponto para reversão (rollback) de transação.
3.1.1 Criando um Controle Transacional com JPA/Hibernate
Caso seu projeto não utilize nenhuma biblioteca ou framework para delimitar o controle trasacional, você pode implementá-lo apenas com JPA/Hibernate.
A EntityManager
oferece através de sua API um método para solicitar uma Transaction
, representada pela abstração EntityTransaction
.
A EntityTransaction
oferece através de seus métodos, mecanismos para controle de transacional, e é através destes métodos que podemos abrir uma transaction, realizar commit
ou rollback
, e até verificar se determinada transaction ainda esta ativa.
Utilizando a implementação da EntityTransaction
podemos implementar um componente para gerenciar as transações, vamos chama-lo de TransactionManager
.
public class TransactionManager{
private EntityManagerFactory managerFactory;
public TransactionManager(EntityManagerFactory managerFactory) {
this.managerFactory = managerFactory;
}
public void executa(Consumer<EntityManager> acao) {
EntityManager manager = null;
EntityTransaction transaction = null;
try {
manager = managerFactory.createEntityManager();
transaction = manager.getTransaction();
transaction.begin();
acao.accept(manager);
transaction.commit();
} catch (Exception e) {
if (transaction != null && transaction.isActive()) {
transaction.rollback();
}
throw new RuntimeException(e);
} finally {
if (manager != null) {
manager.close();
}
}
}
}
Na implementação acima definimos que caso qualquer exceção seja lançada no fluxo de negócio, automaticamente todas alterações serão revertidas.
3.1.2 Utilizando Controle Transactional com Spring e JPA/Hibernate
Spring oferece transações de maneira declarativa através da anotação @Transactional
. Caso um bean seja anotado a nível de classe cada método publico receberá uma transação. Caso um método publico de um bean seja anotado, o escopo deste método sera executado em uma transação, ou seja, o COMMIT
sera feito ao fim do método.
@Service
public class MeuService{
@Autowired
private EntityManager manager;
@Transactional
public void executa(){
//logica de negocio
}
}
E caso uma exceção seja lançada no escopo do método executa()
qual o comportamento da transação?
Automaticamente o Spring cuidara de indicar para EntityManager
reverter a transação, caso qualquer exceção a partir de RunTimeException seja lançada e não tratada.
Top comments (4)
Excelente artigo!!! Que venham mais ótimos artigos!!! Abraços!!!
Esse artigo é excelente, parabéns, ficou muito fácil de compreender, e esclareceu muitas das dúvidas que eu tinha.
Que interessante! Confesso que achei que sabia suficiente sobre os estados do hibernate, mas com os seus exemplos de teste ficou muito mais claro aqui. Obrigada por compartilhar.
Excelentes artigo, Jordi. Ficou muito bom e ele demonstra seu conhecimento sobre JPA e Hibernate.
Parabéns e que venham mais artigos 👏🏻👏🏻👊🏻👊🏻