Jakarta Persistence (JPA)

Entendendo o Lock Otimista do JPA

Alguma aplicação sua é acessada por mais de um usuário ao mesmo tempo? Provavelmente sim. Mas a pergunta principal é, eles alteram os mesmos registros no banco de dados ao mesmo tempo? Se a resposta for sim, então esse post pode te ajudar a resolver um problema de concorrência de uma forma muito simples.

Imagine a situação: se dois usuários buscam uma entidade para editar ao mesmo tempo, fazem as alterações e então salvam, o pode acontecer é: a segunda edição irá sobrepor a primeira, e o primeiro usuário perderá todo seu trabalho, ou até pior, dependendo do que o primeiro alterou, quando o segundo tentar salvar, um erro inesperado pode aparecer na tela, deixando o usuário furioso.

Essa situação é muito simples de simular em um sistema web. Para este post eu criei uma aplicação em JSF usando PrimeFaces, CDI e JPA 2 com Hibernate e o deploy é feito no Tomcat 7. A aplicação mantém o cadastro de um usuário de uma forma muito simples, somente para demonstrar o problema e como resolvê-lo com a estratégia do Lock Otimista.

Acesse ou baixe o código-fonte completo deste artigo no GitHub.

A entidade é representada pela classe Usuario, que por enquanto tem o seguinte código:

@Entity
@Table(name = "usuario")
public class Usuario implements Serializable {

    private static final long serialVersionUID = 1L;
  
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long codigo;
    private String nome;

    // getters e setters
    // equals e hashCode

}

A tela para salvar e editar esse usuário é mostrada abaixo:

Tela salvar usuário

Se você salvar um usuário, informando apenas o nome e depois clicando em “Salvar”, irá ver a mensagem de sucesso e também o código que foi gerado para esse usuário, neste caso, 2.

Salvando usuário

Informe apenas o código 2 e clique em “Pesquisar”. Faça o mesmo em outro browser e utilizando o mesmo código 2.

Agora que entra a parte interessante. Altere no primeiro browser o nome para “Ricardo Silva Junior”, clique em salvar. No outro browser (atenção – não faça a pesquisa novamente) altere o nome para “Ricardo Silva Santos” e clique em “Salvar”.

Faça novamente a pesquisa no primeiro browser, onde você havia alterado para “Ricardo Silva Junior”, verá que a saída será como abaixo:

Pesquisa usuário

Uma situação razoavelmente comum, certo? Então para resolver é muito simples, vamos utilizar a anotação @Version que implementa o Lock Otimista para salvar essa entidade.

Mas o que é esse Lock Otimista?

Acompanhe o que vou fazer agora que você irá entender em 1 minuto.

Nós iremos ter uma nova coluna na entidade Usuario do tipo Integer com o nome versao. E anotá-lo com @Version do pacote javax.persistence.

// outros imports
import javax.persistence.Version;

@Entity
@Table(name = "usuario")
public class Usuario implements Serializable {

    private static final long serialVersionUID = 1L;
  
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long codigo;
    private String nome;

    @Version
    private Integer versao; 

    // getters e setters
    // equals e hashCode

}

É necessário adicionar essa coluna no banco de dados. No caso desse exemplo, o persistence.xml está configurado para atualizar o banco com qualquer alteração no modelo.

Também adicionei essa coluna na interface web apenas para vermos os valores que ali serão gerados, isso mesmo, você não vai settar esse valor em nenhum momento na mão.

Vamos criar um novo usuário, agora com o nome “Maria Silva”. Repare na tela abaixo o valor gerado para a versão.

Salvando usuário com versão

Nesse momento foi gerado um novo usuário de código 6 (não está na sequência, pois vou fazendo testes enquanto escrevo o post) e o valor da versão é 0 (zero).

Altere o nome e clique em salvar novamente:

Salvando usuário e atualizando a versão

Repare que foi incrementado para 1. E não fui eu que coloquei esse valor lá, foi tudo automático, o Hibernate tomou conta disso para mim.

Tá legal, mas e aí? O que é Lock Otimista mesmo?

O Lock Otimista agora é mais fácil de se explicar.

Toda vez que um novo registro for inserido no banco de dados, o valor dessa coluna anotada com @Version será incrementado por 1 (um).

Então, imagine que em um determinado momento o valor esteja 5 nessa coluna. Quando eu faço a pesquisa, o Hibernate traz esse valor atribuído na instância da entidade. Quando eu tento atualizar, ele irá fazer uma pesquisa no banco de dados para ver se a versão que eu tenho é a mesma que está no banco de dados, neste caso 5. Se for, o Hibernate irá atualizar o registro e incrementará o valor da versão de 5 para 6.

E caso esse valor esteja diferente, o Hibernate irá lançar uma exceção do tipo OptimisticLockException e você saberá o problema. Veja o que eu fiz no sistema quando tento alterar uma entidade ao mesmo tempo que outra:

Erro salvando entidade

Ele é chamado de Lock Otimista pois existe um certo otimismo nessa operação toda, pois a não ser que dois usuários tentem atualizar uma mesma entidade no mesmo instante, eles não terão problemas. E no caso de existir uma concorrência, apenas a primeira alteração que será efetivada (não corre risco de perder dados).

Deixe seu comentário sobre o que você achou e me fale em quais situações você já usou o Lock Otimista em seus projetos.

Acesse ou baixe o código-fonte completo deste artigo no GitHub.

Para aprender mais sobre JPA, conheça nosso curso online de Persistência de dados com JPA e Hibernate, que é completo e substitui a necessidade de cursos presenciais.

Graduado em Engenharia Elétrica pela Universidade Federal de Uberlândia e detentor das certificações LPIC-1, SCJP e SCWCD.

Olá,

o que você achou deste conteúdo? Conte nos comentários.

Junte-se a mais de 100.000 pessoas

Entre para nossa lista e receba conteúdos exclusivos e com prioridade

Você se Inscreveu com Sucesso!