You are on page 1of 37

Transações e concorrência

Jobson Ronan {jrjs@cin.ufpe.br}

O que é uma transação?

Uma transação é uma unidade de trabalho que não pode ser dividida. É uma operação atômica. Há dois níveis de granularidade em aplicações corporativas
 

A maior parte da complexidade de se lidar com transações é ocultada pelo sistema (Hibernate, servidor de aplicações, banco de dados)

Transações de banco de dados Transações longas (de aplicação): envolvem várias transações de banco de dados

O trabalho consiste, geralmente, em demarcar o início e fim das transações

Uma transação ou termina com sucesso (commit) ou desfaz todo o processo (rollback)

Transações em servidores

Demarcar transações em uma aplicação JDBC é fácil. Basta configurar a Conexão conn com da seguinte forma

conn.setAutoCommit(false);

  

Os statements executados serão acumulados e só serão tornados definitivos no banco após um conn.commit() Ou serão desfeitos caso ocorra um conn.rollback() Em servidores de aplicação, ou quando é preciso realizar transações entre vários bancos, é preciso usar o protocolo Two-phase commit, que gerencia o processo

Para isto existe a API JTA e a classe UserTransaction que encapsula transações distribuídas

} catch (HibernateException he) { throw he. try { tx = session. tx. Transaction tx = null. concludeAuction(). Executado } catch (Exception e) { if (tx != null) { try { tx. } finally { try { session.openSession().Tratamento de transações em Hibernate Session session = sessions.beginTransaction(). } catch (HibernateException he) { //log he and rethrow e } } throw e. } } dentro da transação .rollback().close().commit().

rollback() ou desfaz imediatamente a transação ou marca a transação para rollback. tx.commit() sincroniza o estado da sessão com o banco de dados. isto inicia uma transação JDBC na conexão.  Em um ambiente gerenciado.Transações no Hibernate   O Hibernate encapsula o sistema de transações do banco (JDBC) ou servidor (ambiente gerenciado) usado A transação começa na Session com uma chamada para session.beginTransaction()  Em um ambiente não gerenciado. . Em uma transação Transaction tx:   tx. inicia uma transação JTA ou une-se à transação existente.  É importante fechar a sessão em um bloco finally para garantir que a conexão JDBC será liberada e retornada ao pool de conexões.  Commit e rollback.

 Flushing é a sincronização da camada de objetos com a camada de dados. Ocorre quando    Uma transação é cometida Às vezes.flush() . antes que uma query é executada Quando a aplicação chama explicitamente session.Flushing (descarregando)  O objeto Session implementa "transparent write behind―   Mudanças ao modelo de domínio não são imediatamente persistidas no banco (para reduzir acesso ao banco) Gravação/sincronização transparente dos dados no final da transação.

É muito caro em termos de escalabilidade da aplicação. isolamento incompleto de uma transação é aceitável .Isolamento  Bancos de dados e sistemas transacionais tentam garantir isolamento entre transações   Isolamento completo é uma utopia.  Bancos de dados fornecem vários graus de flexibilização de isolamento  Variam de isolamento completo a isolamento praticamente inexistente (neste caso. cabe à aplicação lidar com os conflitos)  Para a maior parte das aplicações.

As transações concorrentes não têm isolamento algum.Problemas de isolamento   O padrão ANSI SQL define os níveis de isolamento de transações em termos de fenômenos que podem ou não serem permitidos. Uma transação lê mudanças feitas por transação que ainda não cometeu os dados. Essa mudança pode ser desfeita em um rollback. Os fenômenos são: Update perdido (lost update)   Duas transações ambas atualizam um registro e a segunda transação aborta.  Leitura suja (dirty read)   . fazendo com que as duas mudanças sejam perdidas.

Problemas de isolamento  Leitura não-repetível (unrepeatable read)   Uma transação lê um registro duas vezes e obtém um estado diferente em cada leitura. Novos registros foram inseridos por outra transação entre as consultas. Outra transação pode ter gravado dados e cometido mudanças entre as duas leituras Uma transação executa uma consulta duas vezes.  Leitura fantasma (phantom read)   . e o segundo resultado inclui registros que não estavam na primeira consulta.

. Transações de leitura não bloqueiam o sistema. Este nível de isolamento pode ser implementado com locks de gravação exclusiva. Uma transação não pode gravar em um registro se outra transação não cometida já gravou dados nele. Transação de gravação não cometida impede que outras transações acessem registro.Níveis de isolamento JDBC   JTA usa esses mesmos níveis de isolamento Read uncommitted    Permite dirty reads mas não updates perdidos.  Read committed    Permite unrepeatable reads mas não dirty reads.

Níveis de isolamento JDBC  Repeatable read    Não permite unrepeatable reads nem dirty reads. Transações de leitura bloqueiam transações de gravação (mas não outras transações de leitura) e transações de gravação bloqueiam todas as outras.  Serializable   Fornece o isolamento mais rigoroso. Emula execução em série de transações (em vez de concorrentemente). Podem ocorrer phantom reads. .

.  Portanto. portanto o isolamento serializable não deve ser usado O isolamento read uncommitted é perigoso. usando read committed. e não deve ser usado se houver opções melhores no banco Suporte a versioning (travas otimistas) e uso do cache de segundo nível (por classe) do Hibernate já alcançam a maior parte dos benefícios de um isolamento do tipo repeatable read. devido ao alto custo quanto à escalabilidade (crítica nas aplicações típicas do Hibernate).  Não existe uma regra que sirva para todas as situações. read committed é uma boa opção com o Hibernate.Qual nível de isolamento?  A escolha do nível de isolamento depende do cenário onde a aplicação executa.  Qual um nível razoável de isolamento para aplicações típicas?    Isolamento excessivo geralmente não é aceitável.

cfg.  Só é possível fazer esse controle em ambientes não gerenciados  .isolation = 4  O número refere-se a um dos quatro níveis:     1—Read uncommitted isolation 2—Read committed isolation 4—Repeatable read isolation 8—Serializable isolation Servidores de aplicação têm configuração própria.Como mudar o nível de isolamento default?  É preciso definir uma propriedade no hibernate.isolation=numero onde número é 1. 2.xml. 4 ou 8.connection.connection. Use: hibernate.properties ou hibernate. Exemplo: hibernate.

Estratégias de isolamento locais  Nível de isolamento global afeta todas as conexões  Read committed é um bom isolamento default para aplicações Hibernate   Mas pode ser desejável utilizar travas mais rigorosas para transações específicas Existem duas estratégias   Travas pessimistas (evita colisões entre transações bloqueando totalmente o acesso de outras transações) Travas otimistas (onde o sistema flexibiliza o isolamento mas lida com eventuais colisões) .

Category cat = (Category) session.beginTransaction(). LockMode. tx.beginTransaction(). cat.get(Category.  Em modo read-committed. tx.  .Travas pessimistas  Uma trava pessimista é adquirida quando dados são lidos e mantidos isolados de outras transações até que a sua transação complete.   Classe LockMode  Considere a seguinte transação Uma trava pessimista pode ser obtida da seguinte forma: Transaction tx = session.setName("New Name"). catId).commit().class.get(Category. o banco de dados nunca adquire travas pessimistas a não ser que sejam requisitadas explicitamente Permite a solicitação de uma trava pessimista em um objeto Transaction tx = session. cat. Category cat = (Category) session.commit().setName("New Name").class. catId.UPGRADE).

Só vai ao banco se o objeto não estiver no cache. UPDGRADE . mas desabilita a espera por liberação de travas. faz verificação de versão (se aplicável) e obtém trava pessimista (se suportada). e provoca exceção de locking se a trava não puder ser obtida.Ignora cache.Ignora cache e faz verificação de versão para assegurar-se que o objeto na memória é o mesmo que está no banco. Default em load() e get() READ . WRITE .Mesmo que UPGRADE.Controle de LockMode  Os modos suportados para LockMode são:      NONE . UPDGRADE_NOWAIT .Obtida automaticamente quando Hibernate grava em um registro na transação atual .

beginTransaction().. Transaction tx = session. LockMode..commit().addBid(bid). tx. .Controle de LockMode  Sincronização de objeto desligado se registro não foi alterado por outra transação. Item item = . Bid bid = new Bid().. .. Evite usar LockMode explícito a não ser que realmente seja necessário. Caching é considerada uma solução melhor que travas pessimistas. session. .READ). item.lock(item.

Uma noção mais abrangente da unidade de trabalho.Transações longas (de aplicação)  Processos de negócio    Podem ser consideradas uma única unidade de trabalho do ponto de vista de um usuário. fora de uma transação As modificações são feitas persistentes em uma segunda transação de banco de dados  Exemplo de cenário típico 1) 2) 3) . Transação de baixa granularidade. Dados são recuperados e mostrados na tela em uma primeira transação do banco O usuário tem uma oportunidade de visualizar e modificar os dados.

os dois updates funcionam. e a segunda pode ser aplicada seletivamente pelo usuário. Nenhuma mensagem de erro é mostrada. mas o segundo sobrescreve as alterações do primeiro.Como lidar com as colisões?  Três estratégias    Último commit ganha . . Primeiro commit ganha .  A primeira opção é problemática para várias aplicações    Hibernate ajuda a implementar as outras duas estratégias usando controle de versões e travas otimistas. e o usuário que envia a segunda recebe uma mensagem de erro. Mesclar updates conflitantes . É importante que o usuário pelo menos saiba do erro Acontece por default. Optimistic locking.a primeira modificação é feita persistente.A primeira modificação é persistida.

} }  No arquivo de mapeamento. . <version> vem logo depois de <id> <class name="Comment" table="COMMENTS"> <id ...} int getVersion() {return version. <version name="version" column="VERSION"/> . </class>  O número de versão é só um contador.Uso de managed versioning (controle de versão)  Depende de que um número seja incrementado sempre que um objeto é modificado.. public class Comment { . void setVersion(int version) {this. private int version..  Uma alternativa é usar um timestamp ..version = version.... Não tem outra utilidade.

Exemplo: public class Comment { . void setLastUpdated(Date lastUpdated) { this.... . </class>  Em tese./> <timestamp name="lastUpdated" column="LAST_UPDATED"/> ..lastUpdated = lastUpdated. um timestamp é menos seguro pois duas transações concorrentes poderiam tentar load e update no mesmo milisegundo...} }  Mapeamento <class name="Comment" table="COMMENTS"> <id .Timestamp  Alternativa ao <version>. } public Date getLastUpdated() {return lastUpdated. private Date lastUpdated...

é possível lidar com eles.  Otimistas versus Pessimistas   .   Esses recursos permitem o eficiente gerenciamento de colisões que implementam a estratégia de trava otimista. Garante maior escalabilidade e suporta transações longas. StaleObjectStateException é lançado em caso de inconsistência Enfoque pessimista assume que serão constantes os conflitos e o ideal é bloquear completamente o acesso. Não ultrapassa os limites de uma sessão Enfoque otimista assume que conflitos serão raros e quando eles acontecerem.Travas otimistas  O Hibernate controla a inicialização e gerenciamento de <version> e <timestamp> automaticamente.

Granularidade de uma Sessão  Session-per-request  Uma sessão tem a mesma granularidade de uma transação  Session-per-request-withdetached-objects    Objetos são modificados entre duas sessões Uma transação por sessão Objetos desligados  Session-per-applicationtransaction   Sessão longa Objetos mantêm-se persistentes .

Cache   O cache é uma cópia local dos dados. A aplicação faz uma pesquisa por chave primária ou A camada de persistência resolve uma associação usando estratégia lazy Escopo de transação .cada unidade de trabalho tem seu próprio cache.  Fica entre sua aplicação e o banco de dados. Escopo de processo . O cache evita acesso ao banco sempre que    Podem ser classificados quanto ao escopo:    . vale enquanto a transação está rodando.compartilhado entre processos na mesma máquina ou entre múltiplas máquinas de um cluster.o cache é compartilhado entre transações (há implicações quanto ao isolamento) Escopo de cluster .

Segundo nível é opcional e tem nível de processo ou cluster.Cache no Hibernate  Dois níveis    Primeiro nível tem escopo de transação.  O primeiro nível é a Session. Não pode ser desligada. não instâncias)   É opcional Pode ser configurado por classe ou por associação.    . O segundo nível é cache de estado (valores. Garante identidade do objeto dentro da transação. Uma session ou tem a duração de uma transação de banco de dados ou de uma transação de aplicação longa.

não habilite o cache de segundo nível Requer configuração fina em gerente de cache para melhor performance  Cache de segundo nível     . list(). iterate(). find(). update().Primeiro e segundo cache  Cache de primeiro nível   Automático (Session) Usado sempre que se passa um objeto para save(). saveOrUpdate() ou quando ele é requisitado com load(). mas o algoritmo é mais rápido). Instâncias persistentes são desmontadas (é como serialização. ou filter()  Garante que quando uma aplicação requisita o mesmo objeto persistente duas vezes numa sessão. Requer conhecimento sobre os dados para uso eficiente (não é automático – as classes são mapeadas ao cache uma por uma) Se dados são mais freqüentemente atualizados que lidos. ela recebe de volta a mesma instância.

para sinalizar que uma tabela/objeto está sendo alterada Usado para definir política de cache de segundo nível  <cache>  .Resumo: tags de mapeamento  <version>  Usado em implementação de transações longas.

transaction.CacheProvider) hibernate.xml .<nome> para usar uma implementação disponível.Classe  Usa um cache provider próprio em substituição ao nativo usado pelo Hibernate (implementação de org.Propriedades: transação e cache    hibernate.cache.provider_class=nome. onde <nome> pode ser         JBossTransactionManagerLookup WeblogicTransactionManagerLookup WebSphereTransactionManagerLookup OrionTransactionManagerLookup ResinTransactionManagerLookup JOTMTransactionManagerLookup JOnASTransactionManagerLookup .cfg.. Propriedades para hibernate.cache.hibernate.Classe  Para definir um gerente de transações próprio.transaction.hibernate.da. ou org..properties ou hibernate.da.factory_class=nome.

.mas como englobar várias operações a a vários DAOs em uma transação? .Boas Práticas   Usar sempre o padrão facade ..

sessionFactory = cfg. } .printStackTrace(System. } catch (Throwable ex) { ex. throw new ExceptionInInitializerError(ex).configure()...buildSessionFactory(). static { try { Configuration cfg = new Configuration().Classe utilitária simples public class HibernateUtil { private static final SessionFactory sessionFactory. } } //.out).

openSession(). session = factory. } return session. public static Session getSession() { try { if (session == null || !session. private static Session session.. .isOpen()) { SessionFactory factory = getSessionFactory(). } } //..Classe utilitária simples //.. } catch (Exception e) { throw new RuntimeException(e).

.commit()..beginTransaction(). } public static void commit() { if (transaction != null) transaction.. } public static void rollback() { if (transaction != null) transaction. private static Transaction transaction.. } //.rollback(). public static void beginTransaction() { transaction = getSession().Classe utilitária simples  Suporte transacional //.

quando precisar de uma Sessão.. basta não fecha-la  A fachada pode gerencar a transação com os metodos begin. commit e rollback  Os DAOs não gerencia.Mas se a houver acesso concorrente ..Usando  Todo DAO. mais as transações  . irá obte-la através do getSession()  Para que vários DAOs obtenham a mesma sessão.

. private static final ThreadLocal<Session> localSession = new ThreadLocal<Session>().Classe utilitária com suporte a concorrencia //. } } //.openSession().. if (session == null || !session. } catch (Exception e) { throw new RuntimeException(e). public static Session getSession() { try { Session session = localSession...get().set(session).isOpen()) { SessionFactory factory = getSessionFactory(). localSession. session = factory. } return session.

get().beginTransaction()).get(). public static void beginTransaction() { localTx. } public static void rollback() { if (localTx.. .set(getSession(). private static final ThreadLocal<Transaction> localTx = new ThreadLocal<Transaction>().Classe utilitária com suporte a concorrencia //.get() != null) localTx.. } public static void commit() { if (localTx. } //.commit().get() != null) localTx.rollback()..

sql   Criar criar DAOs e fachada para a aplicação Criar methodos de negício para    Realizar uma reserva Agendar uma reserva Cancelar uma reserva .Exercício  Criar o modelo de Objetos analisando o schema do banco legado script.

br} .Transações e concorrência Jobson Ronan {jrjs@cin.ufpe.