13
JPA Demonstração das estratégias optimistic locking e pessimistic locking Locking é uma técnica para tratamento de concorrência em transações em bases de dados. Quando duas ou mais transações em bases de dados acedem ao mesmo dado, locking permite assegurar que apenas uma transação de cada vez consegue mudar esse dado. Em JPA há duas estratégias para tratar o locking: optimistic locking pessimistic locking. Como exemplo vamos considerar as atualizações do saldo de uma conta após dois depósitos realizados aproximadamente ao mesmo tempo. Quando dois programas podem modificar o mesmo dado, se ambos os programas realizam a sequência ler-modificar-escrever de um modo intercalado podem corromper esse dado. Só o último depósito afeta o saldo perdendo-se a primeira modificação. programa1 Inicia transação Efetua depósito 1 Efetua commit Lê objeto programa2 Inicia transação Efetua depósito 2 Efetua commit Lê objeto

JPA Demonstração das estratégias optimistic locking e ... · JPA Demonstração das estratégias optimistic locking e pessimistic locking Locking é uma técnica para tratamento

  • Upload
    others

  • View
    38

  • Download
    0

Embed Size (px)

Citation preview

Page 1: JPA Demonstração das estratégias optimistic locking e ... · JPA Demonstração das estratégias optimistic locking e pessimistic locking Locking é uma técnica para tratamento

JPA

Demonstração das estratégias optimistic locking e pessimistic locking

Locking é uma técnica para tratamento de concorrência em transações em bases de

dados.

Quando duas ou mais transações em bases de dados acedem ao mesmo dado, locking

permite assegurar que apenas uma transação de cada vez consegue mudar esse dado.

Em JPA há duas estratégias para tratar o locking:

optimistic locking

pessimistic locking.

Como exemplo vamos considerar as atualizações do saldo de uma conta após dois

depósitos realizados aproximadamente ao mesmo tempo.

Quando dois programas podem modificar o mesmo dado, se ambos os programas

realizam a sequência ler-modificar-escrever de um modo intercalado podem

corromper esse dado. Só o último depósito afeta o saldo perdendo-se a primeira

modificação.

programa1

Inicia transação

Efetua depósito 1

Efetua commit

Lê objeto

programa2

Inicia transação

Efetua depósito 2

Efetua commit

Lê objeto

Page 2: JPA Demonstração das estratégias optimistic locking e ... · JPA Demonstração das estratégias optimistic locking e pessimistic locking Locking é uma técnica para tratamento

Setup

1. H2 em modo servidor para permitir múltiplas ligações simultâneas – configurar driver na Unidade de Persistência

Configurar h2 para modo Servidor preencher JDBC URL com JDBC URL: jdbc:h2:tcp://localhost/~/bd/Contas Ficheiro persistence.xml:

<?xml version="1.0" encoding="UTF-8"?> <persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> <persistence-unit name="JPA2PU" transaction-type="RESOURCE_LOCAL"> <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider> <class>Conta</class> <properties> <property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/bd/Contas"/> <property name="javax.persistence.jdbc.user" value=""/> <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/> <property name="javax.persistence.jdbc.password" value=""/> <property name="javax.persistence.schema-generation.database.action" value="create"/> <property name="eclipselink.logging.level.sql" value="FINE"/> <property name="eclipselink.logging.parameters" value="true"/> </properties> </persistence-unit> </persistence>

2. Criar as classes Conta, Iniciar, Util, UtilizadorA e UtilizadorB

@Entity

public class Conta {

// @Version

// private int versionId = 1;

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

private int id;

private double saldo;

private Conta() {

}

public Conta(double depositoInicial) {

this.saldo = depositoInicial;

}

public int getId() {

return id;

}

public double getSaldo() {

return saldo;

}

public void depositar(double deposito) {

saldo += deposito;

}

}

Page 3: JPA Demonstração das estratégias optimistic locking e ... · JPA Demonstração das estratégias optimistic locking e pessimistic locking Locking é uma técnica para tratamento

public class Iniciar {

public static void main(String[] args) throws InterruptedException {

EntityManagerFactory emf

= Persistence.createEntityManagerFactory("JPA2PU");

EntityManager em = emf.createEntityManager();

em.getTransaction().begin();

Conta conta = new Conta(100);

em.persist(conta);

em.getTransaction().commit();

em.close();

emf.close();

}

}

public class Util {

public static void parar(String msg) {

try {

System.out.println(msg);

System.in.read();

}

catch (Exception e) {}

}

}

public class UtilizadorA {

public static void main(String[] args) throws InterruptedException {

EntityManagerFactory emf

= Persistence.createEntityManagerFactory("JPA2PU");

EntityManager em = emf.createEntityManager();

em.getTransaction().begin();

Conta conta = em.find(Conta.class, 1);

Util.parar("A: Saldo=" + conta.getSaldo() + " Continuar?");

conta.depositar(20);

Util.parar("A: Saldo=" + conta.getSaldo() + " Continuar?");

em.getTransaction().commit();

em.close();

emf.close();

System.out.println("A (Fim) - Saldo: " + conta.getSaldo());

}

}

Page 4: JPA Demonstração das estratégias optimistic locking e ... · JPA Demonstração das estratégias optimistic locking e pessimistic locking Locking é uma técnica para tratamento

public class UtilizadorB {

public static void main(String[] args) throws InterruptedException {

EntityManagerFactory emf

= Persistence.createEntityManagerFactory("JPA2PU");

EntityManager em = emf.createEntityManager();

em.getTransaction().begin();

Conta conta = em.find(Conta.class, 1);

Util.parar("B: Saldo=" + conta.getSaldo() + " Continuar?");

conta.depositar(30);

Util.parar("B: Saldo=" + conta.getSaldo() + " Continuar?");

em.getTransaction().commit();

em.close();

emf.close();

System.out.println("B (Fim) - Saldo: " + conta.getSaldo());

}

}

3. Executar o método main da classe Iniciar

A base de dados é criada.

4. Iniciar h2 em modo servidor Iniciar o servidor de h2: h2\bin\h2.bat

Inspecionar a base de dados criada usando a página H2 Console

Page 5: JPA Demonstração das estratégias optimistic locking e ... · JPA Demonstração das estratégias optimistic locking e pessimistic locking Locking é uma técnica para tratamento

Sem Locking 5. Classe Conta sem atributo version.

Executar intercaladamente os métodos main da classe UtilizadorA e da classe UtilizadorB

@Entity

public class Conta {

// @Version

// private int versionId = 1;

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

private int id;

private double saldo;

. . .

}

Aplicação funciona incorretamente: Só o último depósito afeta o saldo perdendo-se a primeira modificação.

Page 6: JPA Demonstração das estratégias optimistic locking e ... · JPA Demonstração das estratégias optimistic locking e pessimistic locking Locking é uma técnica para tratamento

Com Optimistic Locking Para usar a estratégia otimista é necessário adicionar uma propriedade dedicada à

persistência para guardar na base de dados o número da versão da entidade.

Esse campo de dados versão:

Não deve ser modificado pela aplicação

Pode ser numérico ou timestamp mas um valor numérico é recomendado

É definido pela anotação @Version ou pelo elemento <version>

No commit da transação a coluna versão da entidade:

Na estratégia optimistic é incrementada se o estado da entidade mudou durante a

transação

Na estratégia optimistic_force_increment é incrementada mesmo se o estado da

entidade não mudou durante a transação

Em qualquer uma das duas estratégias otimistas, se no commit da transação, a entidade

tem na base de dados uma versão diferente da versão que tinha quando foi carregada,

uma exceção OptimisticLockingException é lançada, significando que outro thread

entretanto modificou a entidade. Podemos apanhar esta exceção e decidir o que fazer.

O locking otimista deixa transações concorrentes processar simultaneamente,

permitindo ler e atualizar uma entidade, mas deteta colisões, verificando no commit se a

versão foi atualizada na base de dados desde que a entidade foi lido, caso em que lança

uma exceção.

A estratégia otimista deve ser usada quando se assume que a maior parte das transações

concorrentes não entram em conflito. As vantagens desta estratégia são não necessitar

de colocar locks na base de dados o que dá melhor escalabilidade. A desvantagem é que

a aplicação deve refrescar e voltar a tentar atualizações que falhem.

Locking de uma entidade

Existem 5 tipos de locking: OPTIMISTIC

OPTIMISTIC_FORCE_INCREMENT

PESSIMISTIC_READ

PESSIMISTIC_WRITE

PESSIMISTIC_FORCE_INCREMENT

Page 7: JPA Demonstração das estratégias optimistic locking e ... · JPA Demonstração das estratégias optimistic locking e pessimistic locking Locking é uma técnica para tratamento

6. Classe Conta com atributo version Alteração apenas da classe Conta para inclusão do atributo versionId.

Programa termina com exceção lançada Sem try … catch Necessário criar novamente a base de dados porque agora a tabela Conta tem mais uma coluna (versionId)

Executar intercaladamente os métodos main da classe UtilizadorA e da classe UtilizadorB Aplicação funciona incorretamente Método main do UtilizadorB: UPDATE CONTA SET SALDO = ?, VERSIONID = ? WHERE ((ID = ?) AND

(VERSIONID = ?))

bind => [130.0, 2, 1, 1]

O segundo método main termina com exceção lançada: The object [jpa2.Conta@d109c4f] cannot be updated because it has changed or been deleted since it was last read.

Page 8: JPA Demonstração das estratégias optimistic locking e ... · JPA Demonstração das estratégias optimistic locking e pessimistic locking Locking é uma técnica para tratamento

Só o primeiro depósito afeta o saldo perdendo-se a última modificação. Com try … catch public class UtilizadorC {

public static void main(String[] args) throws InterruptedException {

EntityManagerFactory emf

= Persistence.createEntityManagerFactory("JPA2PU");

EntityManager em = emf.createEntityManager();

boolean depositado = false;

do {

try {

em.getTransaction().begin();

Conta conta = em.find(Conta.class, 1);

// em.lock(conta, LockModeType.PESSIMISTIC_READ);

Util.parar("C: Saldo=" + conta.getSaldo() + " Continuar?");

conta.depositar(30);

Util.parar("C: Saldo=" + conta.getSaldo() + " Continuar?");

em.getTransaction().commit();

depositado = true;

} catch (final Exception e) {

System.out.println("Saldo mudou desde que foi lido. Tente

outra vez.");

}

} while (!depositado);

em.close();

emf.close();

}

}

Page 9: JPA Demonstração das estratégias optimistic locking e ... · JPA Demonstração das estratégias optimistic locking e pessimistic locking Locking é uma técnica para tratamento

Aplicação funciona corretamente SELECT ID, SALDO, VERSIONID FROM CONTA WHERE (ID = ?)

bind => [1]

UPDATE CONTA SET SALDO = ?, VERSIONID = ? WHERE ((ID = ?) AND

(VERSIONID = ?))

bind => [150.0, 3, 1, 2]

Com Pessimistic Locking 7. Configurar as aplicaçãoes com a estratégia pessimistic locking

O lock numa entidade é colocado pelo EntityManager (em). Pode ser especificado:

Quando a entidade é carregada da base de dados através do método find Pessoa pessoa =

em.find(Pessoa.class, pessoaPK, LockModeType.OPTIMISTIC);

Através do método refresh em.refresh(person, LockModeType.OPTIMISTIC_FORCE_INCREMENT);

Quando carregada através de uma query: Query query = em.createQuery(...);

query.setLockMode(LockModeType.PESSIMISTIC_FORCE_INCREMENT);

Depois de ser carregada da base de dados: em.lock(entity, LockModeType.PESSIMISTIC_READ);

Page 10: JPA Demonstração das estratégias optimistic locking e ... · JPA Demonstração das estratégias optimistic locking e pessimistic locking Locking é uma técnica para tratamento

O modo lock de uma entidade pode ser obtido: LockModeType modoLock = em.getLockMode(entity);

No locking pessimistic, em vez de esperar até ao commit da transação, na esperança de

que nenhuma outra transação tenha mudado os dados, um lock na base de dados é

obtido imediatamente. Assim a transação nunca falha, contudo também não permite

execução paralela de transações.

O locking pessimistic é efetuado ao nível da base de dados enquanto o locking

optimistic é efetuado ao nível da entidade.

Locks pessimistas são propagados para a base de dados usando queries SQL. Se existe

um lock pessimista, a aplicação espera pela base de dados até o lock ser liberto, não

lançando exceção.

public class UtilizadorA {

public static void main(String[] args) throws InterruptedException {

EntityManagerFactory emf

= Persistence.createEntityManagerFactory("JPA2PU");

EntityManager em = emf.createEntityManager();

em.getTransaction().begin();

Conta conta = em.find(Conta.class, 1);

em.lock(conta, LockModeType.PESSIMISTIC_READ);

Util.parar("A: Saldo=" + conta.getSaldo() + " Continuar?");

conta.depositar(20);

Util.parar("A: Saldo=" + conta.getSaldo() + " Continuar?");

em.getTransaction().commit();

em.close();

emf.close();

}

}

public class UtilizadorB {

public static void main(String[] args) throws InterruptedException {

EntityManagerFactory emf

= Persistence.createEntityManagerFactory("JPA2PU");

EntityManager em = emf.createEntityManager();

em.getTransaction().begin();

Conta conta = em.find(Conta.class, 1);

em.lock(conta, LockModeType.PESSIMISTIC_READ);

Util.parar("B: Saldo=" + conta.getSaldo() + " Continuar?");

conta.depositar(30);

Util.parar("B: Saldo=" + conta.getSaldo() + " Continuar?");

em.getTransaction().commit();

em.close();

emf.close();

}

}

Page 11: JPA Demonstração das estratégias optimistic locking e ... · JPA Demonstração das estratégias optimistic locking e pessimistic locking Locking é uma técnica para tratamento

Executar intercaladamente os métodos main da classe UtilizadorA e da classe UtilizadorB UtilizadorA:

SELECT ID, SALDO, VERSIONID FROM CONTA WHERE (ID = ?) FOR UPDATE

bind => [1]

UtilizadorB:

SELECT ID, SALDO, VERSIONID FROM CONTA WHERE (ID = ?) FOR UPDATE

bind => [1] org.h2.jdbc.JdbcSQLException: Timeout trying to lock table.

Caused by: org.h2.jdbc.JdbcSQLException: Concurrent update in table "CONTA":

another transaction has updated or deleted the same row [90131-194]

Caused by: java.lang.IllegalStateException: Entry is locked [1.4.194/101]

O método main da classe UtilizadorB termina com exceção lançada devido a Timeout.

A ligação (EntityManager) deste programa não consegue obter um lock na base de

dados porque outra ligação (EntityManager de UtilizadorA) mantém o lock. Para obter

o lock seria necessário que a ligação do utilizadorA libertasse o lock durante o “lock

timeout” cujo valor, por omissão, é 1000 ms.

O “lock timeout” pode ser colocado individualmente para cada ligação ou para toda a

aplicação na “Persistence Unity”:

<property name="javax.persistence.jdbc.url"

value="jdbc:h2:tcp://localhost/~/bd/Contas;LOCK_TIMEOUT=80000"/>

Ficheiro persistence.xml:

<?xml version="1.0" encoding="UTF-8"?>

<persistence version="2.1" . . . >

<persistence-unit name="JPA2PU" transaction-type="RESOURCE_LOCAL">

<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>

<class>jpa2.Conta</class>

<properties>

<property name="javax.persistence.jdbc.url"

value="jdbc:h2:tcp://localhost/~/bd/Contas;LOCK_TIMEOUT=80000"/>

<property name="javax.persistence.jdbc.user" value=""/>

<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>

. . .

</properties>

</persistence-unit>

</persistence>

Page 12: JPA Demonstração das estratégias optimistic locking e ... · JPA Demonstração das estratégias optimistic locking e pessimistic locking Locking é uma técnica para tratamento

Agora as aplicações funcionam corretamente Começamos por executar a classe Iniciar para colocar o objeto conta com a versão 1

Em seguida executamos intercaladamente os métodos main da classe UtilizadorA e da classe UtilizadorB, mas a classe UtilizadorB não consegue avançar porque não consegue obter um lock na base de dados UtilizadorA:

SELECT ID, SALDO, VERSIONID FROM CONTA WHERE (ID = ?) FOR UPDATE bind => [1] UPDATE CONTA SET SALDO = ?, VERSIONID = ? WHERE ((ID = ?) AND (VERSIONID = ?)) bind => [120.0, 2, 1, 1]

Page 13: JPA Demonstração das estratégias optimistic locking e ... · JPA Demonstração das estratégias optimistic locking e pessimistic locking Locking é uma técnica para tratamento

Após a ligação do UtilizadorA libertar o lock na base de dados, o programa do UtilizadorB avança. UtilizadorB:

SELECT ID, SALDO, VERSIONID FROM CONTA WHERE (ID = ?) FOR UPDATE bind => [1] UPDATE CONTA SET SALDO = ?, VERSIONID = ? WHERE ((ID = ?) AND (VERSIONID = ?)) bind => [150.0, 3, 1, 2]

A principal vantagem de usar pessimistic locking é a garantia de que, uma vez o lock

obtido, a edição terá sucesso. Útil em aplicações altamente concorrentes nas quais

optimistic locking poderia causar muitos erros de locking optimistic.

Uma desvantagem de usar pessimistic locking é que necessita de recursos adicionais da

base de dados, obrigando a transação e a ligação à base de dados serem mantidas

durante a edição.