370 likes | 742 Views
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 Transações de banco de dados
E N D
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 • Transações de banco de dados • Transações longas (de aplicação): envolvem várias transações de banco de dados • Uma transação ou termina com sucesso (commit) ou desfaz todo o processo (rollback) • A maior parte da complexidade de se lidar com transações é ocultada pelo sistema (Hibernate, servidor de aplicações, banco de dados) • O trabalho consiste, geralmente, em demarcar o início e fim das transações
Transações em servidores • Demarcar transações em uma aplicação JDBC é fácil. Basta configurar a Conexão conncom 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
Executado dentro da transação Tratamento de transações em Hibernate Session session = sessions.openSession(); Transaction tx = null; try { tx = session.beginTransaction(); concludeAuction(); tx.commit(); } catch (Exception e) { if (tx != null) { try { tx.rollback(); } catch (HibernateException he) { //log he and rethrow e } } throw e; } finally { try { session.close(); } catch (HibernateException he) { throw he; } }
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, isto inicia uma transação JDBC na conexão. • Em um ambiente gerenciado, inicia uma transação JTA ou une-se à transação existente. • Commit e rollback. Em uma transação Transaction tx: • tx.commit() sincroniza o estado da sessão com o banco de dados. • tx.rollback() ou desfaz imediatamente a transação ou marca a transação para rollback. • É 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.
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. • Flushing é a sincronização da camada de objetos com a camada de dados. Ocorre quando • Uma transação é cometida • Às vezes, antes que uma query é executada • Quando a aplicação chama explicitamente session.flush()
Isolamento • Bancos de dados e sistemas transacionais tentam garantir isolamento entre transações • Isolamento completo é uma utopia. • É muito caro em termos de escalabilidade da aplicação. • 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, isolamento incompleto de uma transação é aceitável
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. Os fenômenos são: • Update perdido (lost update) • Duas transações ambas atualizam um registro e a segunda transação aborta, fazendo com que as duas mudanças sejam perdidas. • As transações concorrentes não têm isolamento algum. • Leitura suja (dirty read) • 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.
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. • Outra transação pode ter gravado dados e cometido mudanças entre as duas leituras • Leitura fantasma (phantom read) • Uma transação executa uma consulta duas vezes, e o segundo resultado inclui registros que não estavam na primeira consulta. • Novos registros foram inseridos por outra transação entre as consultas.
Níveis de isolamento JDBC • JTA usa esses mesmos níveis de isolamento • Read uncommitted • Permite dirty reads mas não updates perdidos. • Uma transação não pode gravar em um registro se outra transação não cometida já gravou dados nele. • Este nível de isolamento pode ser implementado com locks de gravação exclusiva. • Read committed • Permite unrepeatable reads mas não dirty reads. • Transação de gravação não cometida impede que outras transações acessem registro. • Transações de leitura não bloqueiam o sistema.
Níveis de isolamento JDBC • Repeatable read • Não permite unrepeatable reads nem dirty reads. • Podem ocorrer phantom 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).
Qual nível de isolamento? • A escolha do nível de isolamento depende do cenário onde a aplicação executa. • Não existe uma regra que sirva para todas as situações. • Qual um nível razoável de isolamento para aplicações típicas? • Isolamento excessivo geralmente não é aceitável, devido ao alto custo quanto à escalabilidade (crítica nas aplicações típicas do Hibernate), portanto o isolamento serializable não deve ser usado • O isolamento read uncommitted é perigoso, 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, usando read committed. • Portanto, read committed é uma boa opção com o Hibernate.
Como mudar o nível de isolamento default? • É preciso definir uma propriedade no hibernate.properties ou hibernate.cfg.xml. Use: hibernate.connection.isolation=numero onde número é 1, 2, 4 ou 8. Exemplo: hibernate.connection.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 • Só é possível fazer esse controle em ambientes não gerenciados • Servidores de aplicação têm configuração própria.
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)
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. • Em modo read-committed, o banco de dados nunca adquire travas pessimistas a não ser que sejam requisitadas explicitamente • Classe LockMode • Permite a solicitação de uma trava pessimista em um objeto • Considere a seguinte transação Transaction tx = session.beginTransaction(); Category cat = (Category) session.get(Category.class, catId); cat.setName("New Name"); tx.commit(); • Uma trava pessimista pode ser obtida da seguinte forma: Transaction tx = session.beginTransaction(); Category cat = (Category) session.get(Category.class, catId, LockMode.UPGRADE); cat.setName("New Name"); tx.commit();
Controle de LockMode • Os modos suportados para LockMode são: • NONE - Só vai ao banco se o objeto não estiver no cache. Default em load() e get() • READ - Ignora cache e faz verificação de versão para assegurar-se que o objeto na memória é o mesmo que está no banco. • UPDGRADE - Ignora cache, faz verificação de versão (se aplicável) e obtém trava pessimista (se suportada). • UPDGRADE_NOWAIT- Mesmo que UPGRADE, mas desabilita a espera por liberação de travas, e provoca exceção de locking se a trava não puder ser obtida. • WRITE- Obtida automaticamente quando Hibernate grava em um registro na transação atual
Controle de LockMode • Sincronização de objeto desligado se registro não foi alterado por outra transação. Item item = ... ; Bid bid = new Bid(); item.addBid(bid); ... Transaction tx = session.beginTransaction(); session.lock(item, LockMode.READ); tx.commit(); Caching é considerada uma solução melhor que travas pessimistas. Evite usar LockMode explícito a não ser que realmente seja necessário.
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. • Transação de baixa granularidade. • Uma noção mais abrangente da unidade de trabalho. • Exemplo de cenário típico • 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, fora de uma transação • As modificações são feitas persistentes em uma segunda transação de banco de dados
Como lidar com as colisões? • Três estratégias • Último commit ganha - os dois updates funcionam, mas o segundo sobrescreve as alterações do primeiro. Nenhuma mensagem de erro é mostrada. • Primeiro commit ganha - a primeira modificação é feita persistente, e o usuário que envia a segunda recebe uma mensagem de erro. Optimistic locking. • Mesclar updates conflitantes - A primeira modificação é persistida, e a segunda pode ser aplicada seletivamente pelo usuário. • A primeira opção é problemática para várias aplicações • É importante que o usuário pelo menos saiba do erro • Acontece por default. • Hibernate ajuda a implementar as outras duas estratégias usando controle de versões e travas otimistas.
Uso de managed versioning (controle de versão) • Depende de que um número seja incrementado sempre que um objeto é modificado. public class Comment { ... private int version; ... void setVersion(int version) {this.version = version;} int getVersion() {return version;} } • No arquivo de mapeamento, <version> vem logo depois de <id> <class name="Comment" table="COMMENTS"> <id ... <version name="version" column="VERSION"/> ... </class> • O número de versão é só um contador. Não tem outra utilidade. • Uma alternativa é usar um timestamp
Timestamp • Alternativa ao <version>. Exemplo: public class Comment { ... private Date lastUpdated; void setLastUpdated(Date lastUpdated) { this.lastUpdated = lastUpdated; } public Date getLastUpdated() {return lastUpdated;} } • Mapeamento <class name="Comment" table="COMMENTS"> <id ...../> <timestamp name="lastUpdated" column="LAST_UPDATED"/> ... </class> • Em tese, um timestamp é menos seguro pois duas transações concorrentes poderiam tentar load e update no mesmo milisegundo.
Travas otimistas • O Hibernate controla a inicialização e gerenciamento de <version> e <timestamp> automaticamente. • Esses recursos permitem o eficiente gerenciamento de colisões que implementam a estratégia de trava otimista. • StaleObjectStateException é lançado em caso de inconsistência • Otimistas versus Pessimistas • 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, é possível lidar com eles. Garante maior escalabilidade e suporta transações longas.
Granularidade de uma Sessão • Session-per-request • Uma sessão tem a mesma granularidade de uma transação • Session-per-request-with-detached-objects • Objetos são modificados entre duas sessões • Uma transação por sessão • Objetos desligados • Session-per-application-transaction • Sessão longa • Objetos mantêm-se persistentes
Cache • O cache é uma cópia local dos dados. • Fica entre sua aplicação e o banco de dados. • O cache evita acesso ao banco sempre que • A aplicação faz uma pesquisa por chave primária ou • A camada de persistência resolve uma associação usando estratégia lazy • Podem ser classificados quanto ao escopo: • Escopo de transação - cada unidade de trabalho tem seu próprio cache; vale enquanto a transação está rodando. • Escopo de processo - o cache é compartilhado entre transações (há implicações quanto ao isolamento) • Escopo de cluster - compartilhado entre processos na mesma máquina ou entre múltiplas máquinas de um cluster.
Cache no Hibernate • Dois níveis • Primeiro nível tem escopo de transação. • Segundo nível é opcional e tem nível de processo ou cluster. • O primeiro nível é a Session. • 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 pode ser desligada. • Garante identidade do objeto dentro da transação. • O segundo nível é cache de estado (valores; não instâncias) • É opcional • Pode ser configurado por classe ou por associação.
Primeiro e segundo cache • Cache de primeiro nível • Automático (Session) • Usado sempre que se passa um objeto para save(), update(), saveOrUpdate() ou quando ele é requisitado com load(), find(), list(), iterate(), ou filter() • Garante que quando uma aplicação requisita o mesmo objeto persistente duas vezes numa sessão, ela recebe de volta a mesma instância. • Cache de segundo nível • Instâncias persistentes são desmontadas (é como serialização, mas o algoritmo é mais rápido). • 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, não habilite o cache de segundo nível • Requer configuração fina em gerente de cache para melhor performance
Resumo: tags de mapeamento • <version> • Usado em implementação de transações longas, para sinalizar que uma tabela/objeto está sendo alterada • <cache> • Usado para definir política de cache de segundo nível
hibernate.cache.provider_class=nome.da.Classe Usa um cache provider próprio em substituição ao nativo usado pelo Hibernate (implementação de org.hibernate.cache.CacheProvider) hibernate.transaction.factory_class=nome.da.Classe Para definir um gerente de transações próprio, ou org.hibernate.transaction.<nome> para usar uma implementação disponível, onde <nome> pode ser JBossTransactionManagerLookup WeblogicTransactionManagerLookup WebSphereTransactionManagerLookup OrionTransactionManagerLookup ResinTransactionManagerLookup JOTMTransactionManagerLookup JOnASTransactionManagerLookup ... Propriedades: transação e cache Propriedades para hibernate.properties ou hibernate.cfg.xml
Boas Práticas • Usar sempre o padrão facade • ...mas como englobar várias operações a a vários DAOs em uma transação?
Classe utilitária simples public class HibernateUtil { private static final SessionFactory sessionFactory; static { try { Configuration cfg = new Configuration(); sessionFactory = cfg.configure().buildSessionFactory(); } catch (Throwable ex) { ex.printStackTrace(System.out); throw new ExceptionInInitializerError(ex); } } //... }
Classe utilitária simples //.. private static Session session; public static Session getSession() { try { if (session == null || !session.isOpen()) { SessionFactory factory = getSessionFactory(); session = factory.openSession(); } return session; } catch (Exception e) { throw new RuntimeException(e); } } //...
Classe utilitária simples • Suporte transacional //.. private static Transaction transaction; public static void beginTransaction() { transaction = getSession().beginTransaction(); } public static void commit() { if (transaction != null) transaction.commit(); } public static void rollback() { if (transaction != null) transaction.rollback(); } //...
Usando • Todo DAO, quando precisar de uma Sessão, irá obte-la através do getSession() • Para que vários DAOs obtenham a mesma 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, mais as transações • ...Mas se a houver acesso concorrente
Classe utilitária com suporte a concorrencia //.. private static final ThreadLocal<Session> localSession = new ThreadLocal<Session>(); public static Session getSession() { try { Session session = localSession.get(); if (session == null || !session.isOpen()) { SessionFactory factory = getSessionFactory(); session = factory.openSession(); localSession.set(session); } return session; } catch (Exception e) { throw new RuntimeException(e); } } //...
Classe utilitária com suporte a concorrencia //.. private static final ThreadLocal<Transaction> localTx = new ThreadLocal<Transaction>(); public staticvoid beginTransaction() { localTx.set(getSession().beginTransaction()); } public static void commit() { if (localTx.get() != null) localTx.get().commit(); } public static void rollback() { if (localTx.get() != null) localTx.get().rollback(); } //...
Exercício • Criar o modelo de Objetos analisando o schema do banco legado script.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
Transações e concorrência Jobson Ronan {jrjs@cin.ufpe.br}