Bloqueio otimista na JPA
1. Introdução
Quando se trata de aplicativos corporativos, é crucial gerenciar o acesso simultâneo a um banco de dados de maneira adequada. Isso significa que devemos ser capazes de lidar com várias transações de maneira eficaz e, o mais importante, à prova de erros.
Além do mais, precisamos garantir que os dados permaneçam consistentes entre leituras e atualizações simultâneas.
Para conseguir isso, podemos usar o mecanismo de bloqueio otimista fornecido pela Java Persistence API. Isso leva a que várias atualizações feitas nos mesmos dados ao mesmo tempo não interfiram entre si.
2. Entendendo o bloqueio otimista
Para usar o bloqueio otimista,we need to have an entity including a property with @Version annotation. Ao usá-lo, cada transação que lê dados mantém o valor da propriedade version.
Antes que a transação queira fazer uma atualização, ela verifica novamente a propriedade da versão.
Se o valor mudou nesse ínterim, umOptimisticLockException é lançado. Caso contrário, a transação confirmará a atualização e incrementará uma propriedade de versão de valor.
3. Bloqueio pessimista vs. Bloqueio otimista
É bom saber que, em contraste com o bloqueio otimista, o JPA nos dá um bloqueio pessimista. É outro mecanismo para lidar com o acesso simultâneo a dados.
Abordamos o bloqueio pessimista em um de nossos artigos anteriores -Pessimistic Locking in JPA. Vamos descobrir qual é a diferença e como podemos nos beneficiar de cada tipo de bloqueio.
Como dissemos antes,optimistic locking is based on detecting changes on entities by checking their version attribute. Se ocorrer alguma atualização simultânea,OptmisticLockException ocorre. Depois disso, podemos tentar atualizar novamente os dados.
Podemos imaginar que esse mecanismo seja adequado para aplicativos que fazem muito mais leituras do que atualizações ou exclusões. Além disso, é útil em situações onde as entidades devem ser desanexadas por algum tempo e os bloqueios não podem ser mantidos.
Ao contrário, o mecanismo de bloqueio pessimista envolve o bloqueio de entidades no nível do banco de dados.
Cada transação pode adquirir um bloqueio nos dados. Enquanto ele mantiver o bloqueio, nenhuma transação poderá ler, excluir ou fazer atualizações nos dados bloqueados. Podemos presumir que o uso de bloqueio pessimista pode resultar em conflitos. No entanto, garante maior integridade dos dados do que o bloqueio otimista.
4. Atributos da versão
Os atributos de versão são propriedades com anotação@Version. They are necessary for enabling optimistic locking. Vamos ver um exemplo de classe de entidade:
@Entity
public class Student {
@Id
private Long id;
private String name;
private String lastName;
@Version
private Integer version;
// getters and setters
}
Existem várias regras que devemos seguir ao declarar atributos de versão:
-
cada classe de entidade deve ter apenas um atributo de versão
-
ele deve ser colocado na tabela principal para uma entidade mapeada para várias tabelas
-
tipo de atributo de versão deve ser um dos seguintes:int,Integer,long,Long,short,Short,java.sql.Timestamp
We should know that we can retrieve a value of the version attribute via entity, but we mustn’t update or increment it. Apenas o provedor de persistência pode fazer isso, portanto, os dados permanecem consistentes.
É importante notar que os provedores de persistência são capazes de oferecer suporte ao bloqueio otimista para entidades que não têm atributos de versão. No entanto, é uma boa ideia sempre incluir atributos de versão ao trabalhar com bloqueio otimista.
Se tentarmos bloquear uma entidade que não contenha tal atributo e o provedor de persistência não o suportar, isso resultará emPersitenceException.
5. Modos de bloqueio
A JPA fornece dois modos de bloqueio otimistas diferentes (e dois aliases):
-
OPTIMISTIC - obtém um bloqueio de leitura otimista para todas as entidades que contêm um atributo de versão
-
OPTIMISTIC_FORCE_INCREMENT - obtém um bloqueio otimista igual aOPTIMISTIC e adicionalmente incrementa o valor do atributo de versão
-
READ - é um sinônimo deOPTIMISTIC
-
WRITE - é um sinônimo deOPTIMISTIC_FORCE_INCREMENT
Podemos encontrar todos os tipos listados acima na classeLockModeType.
5.1. OPTIMISTIC (READ)
Como já sabemos, os modos de bloqueioOPTIMISTICeREAD são sinônimos. No entanto, a especificação JPA nos recomenda usarOPTIMISTIC em novos aplicativos.
Whenever we request the OPTIMISTIC lock mode, a persistence provider will prevent our data from dirty reads as well as non-repeatable reads.
Simplificando, deve garantir que qualquer transação falhe em confirmar qualquer modificação nos dados dessa outra transação:
-
foi atualizado ou excluído, mas não confirmado
-
foi atualizado ou excluído com sucesso enquanto isso
5.2. OPTIMISTIC_INCREMENT (WRITE)
O mesmo que anteriormente,OPTIMISTIC_INCREMENT eWRITE são sinônimos, mas o primeiro é preferível.
OPTIMISTIC_INCREMENT deve atender às mesmas condições do modo de bloqueioOPTIMISTIC. Additionally, it increments the value of a version attribute. No entanto, não é especificado se deve ser feito imediatamente ou pode ser adiado até a confirmação ou liberação.
É importante saber que um provedor de persistência tem permissão para fornecer a funcionalidadeOPTIMISTIC_INCREMENT quando o modo de bloqueioOPTIMISTIC é solicitado.
6. Usando o bloqueio otimista
We should remember that for versioned entities optimistic locking is available by default. No entanto, existem várias maneiras de solicitá-lo explicitamente.
6.1. Find
Para solicitar o bloqueio otimista, podemos passar oLockModeType adequado como um argumento para encontrar o método deEntityManager:
entityManager.find(Student.class, studentId, LockModeType.OPTIMISTIC);
6.2. Inquerir
Outra maneira de habilitar o bloqueio é usando o métodosetLockMode do objetoQuery:
Query query = entityManager.createQuery("from Student where id = :id");
query.setParameter("id", studentId);
query.setLockMode(LockModeType.OPTIMISTIC_INCREMENT);
query.getResultList()
6.3. Bloqueio explícito
Podemos definir um bloqueio chamando o métodolock do EnitityManager:
Student student = entityManager.find(Student.class, id);
entityManager.lock(student, LockModeType.OPTIMISTIC);
6.4. Atualizar
Podemos chamar o métodorefresh da mesma forma que o método anterior:
Student student = entityManager.find(Student.class, id);
entityManager.refresh(student, LockModeType.READ);
6.5. NamedQuery
A última opção é usar @NamedQuery com a propriedadelockMode:
@NamedQuery(name="optimisticLock",
query="SELECT s FROM Student s WHERE s.id LIKE :id",
lockMode = WRITE)
7. OptimisticLockException
When the persistence provider discovers optimistic locking conflicts on entities, it throws OptimisticLockException. Devemos estar cientes de que, devido à exceção, a transação ativa está sempre marcada para rollback.
É bom saber como podemos reagir aOptimisticLockException. Convenientemente, esta exceção contém uma referência à entidade conflitante. However, it’s not mandatory for the persistence provider to supply it in every situation. Não há garantia de que o objeto estará disponível.
Há uma maneira recomendada de lidar com a exceção descrita, no entanto. Devemos recuperar a entidade novamente, recarregando ou atualizando. De preferência em uma nova transação. Depois disso, podemos tentar atualizá-lo mais uma vez.
8. Conclusão
Neste tutorial, nos familiarizamos com uma ferramenta que pode nos ajudar a orquestrar transações simultâneas. Optimistic locking uses version attributes included in entities to control concurrent modifications on them.
Portanto, ele garante que quaisquer atualizações ou exclusões não sejam substituídas ou perdidas silenciosamente. Ao contrário do bloqueio pessimista, ele não bloqueia entidades no nível do banco de dados e, consequentemente, não é vulnerável a deadlocks de banco de dados.
Aprendemos que o bloqueio otimista está habilitado para entidades com versão por padrão. No entanto, existem várias maneiras de solicitá-lo explicitamente usando vários tipos de modo de bloqueio.
Outro fato que devemos lembrar é que cada vez que houver atualizações conflitantes nas entidades, devemos esperar umOptimisticLockException.
Por fim, o código-fonte deste tutorial está disponívelover on GitHub.