3 dicas para uso eficiente de JPA/Hibernate
Tags: java, programming, jpa, hibernate
Published: 2022-07-15 19:47:57

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.


![Entity State](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ckh5zmvbjyfk67vt77qz.png)


> Fonte: [Vlad Mihalcea](https://twitter.com/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](https://github.com/JordiHOFC/jpa-exemple/blob/main/src/test/java/StateCicleJPAEntityTest.java) 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.

```java
  @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:

1. É criado uma instância da entidade Aluno, que esta em estado `transient` ou `new`.
2. É solicitado uma `Transaction` ao Contexto de Persistência, que é aberta na próxima linha.
3. Dado que a `Transaction` esta aberta, é disparado a operação de **persist**, que transita a entidade para o estado `managed`.
4. 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.
5. É 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`.

```java
@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`.

```java
@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`.


```java
@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:

1. É criado uma entidade Aluno, que é movida para o estado `managed` após a aplicação da operação de **persist**.
2. Entidade Aluno é movida para o estado `detached` após a operação **detach**, e através do `assertFalse` provamos que a mesma não pertence ao Contexto de Persistência.
3. Entidade tem uma alteração de valor em um dos seus atributos. 
4. É aberto uma nova `Transaction` e a operação de **merge** é aplicada, movimentando a entidade para o estado `managed`.
5. 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](https://vladmihalcea.com/a-beginners-guide-to-acid-and-database-transactions/)), 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`. 

```java
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.

```java
@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.



## Referências

- [Entendendo Transações Declarativas do Spring](https://docs.spring.io/spring-framework/docs/4.2.x/spring-framework-reference/html/transaction.html#transaction-declarative-rolling-back): Spring Doc
- [Construindo um Controle Transacional para Sistemas Legados](http://blog.triadworks.com.br/controle-transacional-programatico-em-sistemas-legados): [Rafael Ponte](https://twitter.com/rponte)
- [Guia para iniciantes sobre os estados de ciclo de vida de Entidades da JPA e Hibernate](https://vladmihalcea.com/a-beginners-guide-to-jpa-hibernate-entity-state-transitions/): [Vlad Mihalcea](https://twitter.com/vlad_mihalcea)