3 dicas para uso eficiente de JPA/Hibernate

jordihofc

Jordi Henrique Silva

Posted on July 15, 2022

3 dicas para uso eficiente de JPA/Hibernate

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

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"
      );

    }


Enter fullscreen mode Exit fullscreen mode

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.



@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"
    );
}


Enter fullscreen mode Exit fullscreen mode

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"
    );
}


Enter fullscreen mode Exit fullscreen mode

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"
    );

}


Enter fullscreen mode Exit fullscreen mode

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), 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();
            }
        }
    }
}


Enter fullscreen mode Exit fullscreen mode

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
    }
}


Enter fullscreen mode Exit fullscreen mode

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

💖 💪 🙅 🚩
jordihofc
Jordi Henrique Silva

Posted on July 15, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related