JPA é especificação para mapeamento ORM e persistência de dados inspiradíssima no famoso framework Hibernate.
A especificação JPA (1.0) já é amplamente usada e aceita na comunidade de desenvolvedores Java. Há vários provedores de implementação para o padrão ( Hibernate , EclipseLink, TopLink ...)
Um assunto pouco explorado, quando se trata de tutoriais de jpa, é sobre locks (otimista e pessimista). E é sobre esse assunto que tento escrever um pouco aqui.
Tratamento de concorrência em bancos de dados
Há basicamente três tipos de tratamento para concorrência em banco de dados:
O exemplo básico
Começaremos com os perigos do modo Ostrich de agir. Para exemplificar criaremos uma simples classe.
Agora para mudar esse exemplo para o modo otimista de ser basta criarmos uma propriedade no objeto com a anotação @Version para denotar que este campo será usado para o fim da implementação otimista. Esse campo deve ser int, long ou timestamp.
Com a adição do lock otimista quando o usuário B fosse tentar salvar suas modificações ele receberia a execeção OptimisticLockException. Isso já garantiria ao menos a notificação ao usuário que alguém estava trabalhando sobre o mesmo dado e ( lembra das canseiras que o IDE nos livra no versionamento de código) apartir dai você poderia mostrar o que foi mudado para o usuário tomar a decisão de salvar ou não suas alterações.
A parte chocante fica por conta do modo pessimista de ser, a JPA 1.0 não suporta diretamente o modo pessimista de ser (O JPA 2.0 prevê esse modo a mais :)
Então o que fazer?
Imaginem duas contas bancárias: (sempre esse exemplo) aMinhaConta (com saldo de R$ 150,00) e a suaConta (com saldo de R$ 25.000,00) e você quer me presentear com um Wii mas não tem como mandá-lo pelo correio, logo acha mais conviniente realizar uma transferência para que eu compre. O processo (bem simplificado) se resume a isso:
#0 quantiaASerDoada = R$ 1390,00;
#1 se suaConta.saldo > quantiaASerDoada então
#2 suaConta.transerePara(minhaConta, quantiaASerDoada);
#3 fim se
Se entre a linha 1 e 2 você fizer um saque de R$ 25.000,00 (isso pode ocorrer) a transferência não deveria ser realizada. Um modo para que isso ocorra é travar (lock) o dado que pode sofrer com essas concorrências. No JPA 1.0 você pode travar um objeto em dois passos (ai reside el peligro).
#0 Conta suaConta = em.find(Conta.class, 175789);
#1 em.lock(suaConta,LockMode.Write);
Mais uma vez entre a linha 0 e a 1 pode ocorrer um problema de concorrência. (tanto que a especificação JPA 2.0 já prêve um modo similar ao session do hibernate, o travamento no momento da leitura, em.find(Conta.class, 175789, LockMode.PESSIMISTIC);)
Solução fácil para o problema acima
Basta (se estiver usando Hibernate as your JPA provider) :
((Session) ((EntityManagerImpl) em.getDelegate()).getSession())
O session já tem um jeito de select for update session.get(SuaClasse.class, seuId, tipoDeTravamento). ( já perceberam que pra cada problema que você encontra na JPA 1.0 o hibernate quase sempre tem a solução?!)
Referências
http://en.wikibooks.org/wiki/Java_Persistence/Locking
http://www.javaworld.com/jw-07-2001/jw-0713-optimism.html
A especificação JPA (1.0) já é amplamente usada e aceita na comunidade de desenvolvedores Java. Há vários provedores de implementação para o padrão ( Hibernate , EclipseLink, TopLink ...)
Um assunto pouco explorado, quando se trata de tutoriais de jpa, é sobre locks (otimista e pessimista). E é sobre esse assunto que tento escrever um pouco aqui.
Tratamento de concorrência em bancos de dados
Há basicamente três tipos de tratamento para concorrência em banco de dados:
- Otimista - Quando é criado mecanismos para versionar o dado, no momento em que o banco vai efetivar sua operação sua versão é checada para garantir que você está com um dado que não foi alterado.
- Pessimista - O banco simplesmente trava o dado e só aquele que tem a trava consegue trabalhar com os dados.
- Ostrich - Quando não há tratamento nenhum pra concorrência :) , ou seja, a maioria dos casos atuais.
O exemplo básico
Começaremos com os perigos do modo Ostrich de agir. Para exemplificar criaremos uma simples classe.
@EntityAgora considere dois usuários A e B trabalhando sobre o Produto com id = 1( Nintendo Wii) ambos usuários têm o mesmo produto na tela de edição, o usuário A modifica quantidade e salva antes do usuário B, já o usuário B modifica o nome e salva depois. Note que há incosistência, o usuário A acredita (porque ele editou e salvou) que alterou a quantidade de nintendos wii, porém o usuario B (ainda com a quantidade antiga) mudou o nome e salvou o objeto. O que acontece? Depende do seu provedor de JPA, há casos em que o update só é feito para a coluna modificada mas há casos em que o objeto todo é modificado, o que causa claramente inconsistência. Escrever, escrever e escrever pode não ajudar então veja o código abaixo exemplificando esse cenário.
public class Produto implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String nome;
private double quantidade;
//getters and setters omitidos
}
Produto nintendoWiiA = em.find(Produto.class,1L);ps: O código é apenas para demonstrar como isso é feito, não é funcional.
Produto nintendoWiiB = em.find(Produto.class,1L);
/*Usuário A*/
em.getTransaction().begin();
nintendoWiiA.setQuantidade(45.0D);
em.getTransaction().commit();
/*Usuário B*/
em.getTransaction().begin();
nintendoWiiB.setNome("Nintendo WII");
em.getTransaction().commit();
System.out.println(em.find(Produto.class,1L));
Agora para mudar esse exemplo para o modo otimista de ser basta criarmos uma propriedade no objeto com a anotação @Version para denotar que este campo será usado para o fim da implementação otimista. Esse campo deve ser int, long ou timestamp.
Com a adição do lock otimista quando o usuário B fosse tentar salvar suas modificações ele receberia a execeção OptimisticLockException. Isso já garantiria ao menos a notificação ao usuário que alguém estava trabalhando sobre o mesmo dado e ( lembra das canseiras que o IDE nos livra no versionamento de código) apartir dai você poderia mostrar o que foi mudado para o usuário tomar a decisão de salvar ou não suas alterações.
A parte chocante fica por conta do modo pessimista de ser, a JPA 1.0 não suporta diretamente o modo pessimista de ser (O JPA 2.0 prevê esse modo a mais :)
Então o que fazer?
Imaginem duas contas bancárias: (sempre esse exemplo) aMinhaConta (com saldo de R$ 150,00) e a suaConta (com saldo de R$ 25.000,00) e você quer me presentear com um Wii mas não tem como mandá-lo pelo correio, logo acha mais conviniente realizar uma transferência para que eu compre. O processo (bem simplificado) se resume a isso:
#0 quantiaASerDoada = R$ 1390,00;
#1 se suaConta.saldo > quantiaASerDoada então
#2 suaConta.transerePara(minhaConta, quantiaASerDoada);
#3 fim se
Se entre a linha 1 e 2 você fizer um saque de R$ 25.000,00 (isso pode ocorrer) a transferência não deveria ser realizada. Um modo para que isso ocorra é travar (lock) o dado que pode sofrer com essas concorrências. No JPA 1.0 você pode travar um objeto em dois passos (ai reside el peligro).
#0 Conta suaConta = em.find(Conta.class, 175789);
#1 em.lock(suaConta,LockMode.Write);
Mais uma vez entre a linha 0 e a 1 pode ocorrer um problema de concorrência. (tanto que a especificação JPA 2.0 já prêve um modo similar ao session do hibernate, o travamento no momento da leitura, em.find(Conta.class, 175789, LockMode.PESSIMISTIC);)
Solução fácil para o problema acima
Basta (se estiver usando Hibernate as your JPA provider) :
((Session) ((EntityManagerImpl) em.getDelegate()).getSession())
O session já tem um jeito de select for update session.get(SuaClasse.class, seuId, tipoDeTravamento). ( já perceberam que pra cada problema que você encontra na JPA 1.0 o hibernate quase sempre tem a solução?!)
Referências
http://en.wikibooks.org/wiki/Java_Persistence/Locking
http://www.javaworld.com/jw-07-2001/jw-0713-optimism.html
2 comentários:
Bom post!
Modo Ostrich foi massa! uauahuahuahua
Valeu bróder! Me ajudou bastante. :-)
Postar um comentário