Hibernate.org Community Documentation

- Anterior
- Próxima

## Capítulo 13. Transações e Concorrência

O fator mais importante sobre o Hibernate e o controle de concorrência é que é muito fácil de ser compreendido. O Hibernate usa diretamente conexões de JDBC e recursos de JTA sem adicionar nenhum comportamento de bloqueio a mais. Recomendamos que você gaste algum tempo com o JDBC, o ANSI e a especificação de isolamento de transação de seu sistema de gerência da base de dados.

O Hibernate não bloqueia objetos na memória. Sua aplicação pode esperar o comportamento tal qual definido de acordo com o nível de isolamento de suas transações de banco de dados. Note que graças ao `Session`, que também é um cache de escopo de transação, o Hibernate procura repetidamente por identificadores e consultas de entidade não consultas de relatórios que retornam valores escalares.

Além do versionamento para o controle automático de concorrência otimista, o Hibernate oferece também uma API (menor) para bloqueio pessimista de linhas usando a sintáxe `SELECT FOR UPDATE`. O controle de concorrência otimista e esta API são discutidos mais tarde neste capítulo.

Nós começamos a discussão do controle de concorrência no Hibernate com a granularidade do `Configuration`, `SessionFactory` e `Session`, além de transações de base de dados e conversações longas.

## 13.1. Sessão e escopos de transações

Um `SessionFactory` é objeto threadsafe com um custo alto de criação, compartilhado por todas as threads da aplicação. É criado uma única vez, no início da execução da aplicação, a partir da instância de uma `Configuration`.

Uma `Session` é um objeto de baixo custo de criação, não é threadsafe, deve ser usado uma vez, para uma única requisição, uma conversação, uma única unidade do trabalho e então deve ser descartado. Um `Session` não obterá um JDBC `Connection`, ou um `Datasource`, a menos que necessite. Isto não consome nenhum recurso até ser usado.

Uma transação precisa ser o mais curta possível, para reduzir a disputa pelo bloqueio na base de dados. Transações longas impedirão que sua aplicação escale a carga altamente concorrente. Por isso, não é bom manter uma transação de base de dados aberta durante o tempo que o usuário pensa, até que a unidade do trabalho esteja completa.

Qual é o escopo de uma unidade de trabalho? Pode uma única `Session` do Hibernate gerenciar diversas transações ou este é um o relacionamento um-para-um dos escopos? Quando você deve abrir e fechar uma `Session` e como você demarca os limites da transação? Estas questões estão endereçadas nas seguintes seções.

### 13.1.1. Unidade de trabalho

First, let's define a unit of work. A unit of work is a design pattern described by Martin Fowler as “ [maintaining] a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems. ”[PoEAA] In other words, its a series of operations we wish to carry out against the database together. Basically, it is a transaction, though fulfilling a unit of work will often span multiple physical database transactions (see Seção 13.1.2, “Longas conversações”). So really we are talking about a more abstract notion of a transaction. The term "business transaction" is also sometimes used in lieu of unit of work.

Primeiro, não use o antipattern sessão-por-operação: isto é, não abra e feche uma `Session` para cada simples chamada ao banco de dados em uma única thread. Naturalmente, o mesmo se aplica às transações do banco de dados. As chamadas ao banco de dados em uma aplicação são feitas usando uma seqüência planejada, elas são agrupadas em unidades de trabalho atômicas. Veja que isso também significa que realizar um auto-commit depois de cada instrução SQL é inútil em uma aplicação, esta modalidade é ideal para o trabalho ad hoc do console do SQL. O Hibernate impede, ou espera que o servidor de aplicação impessa isso, aplique a modalidade auto-commit imediatamente. As transações de banco de dados nunca são opcionais, toda a comunicação com um banco de dados tem que ocorrer dentro de uma transação, não importa se você vai ler ou escrever dados. Como explicado, o comportamento auto-commit para leitura de dados deve ser evitado, uma vez que muitas transações pequenas são improváveis de executar melhor do que uma unidade de trabalho claramente definida. A última opção é também muito mais sustentável e expandida.

O modelo mais comum em uma aplicação de cliente/servidor multi-usuário é sessão-por-requisição. Neste modelo, uma requisição do cliente é enviada ao servidor, onde a camada de persistência do Hibernate é executada. Uma `Session` nova do Hibernate é aberta, e todas as operações da base de dados são executadas nesta unidade do trabalho. Logo que o trabalho for completado, e a resposta para o cliente for preparada, a sessão é descarregada e fechada. Você usaria também uma única transação de base de dados para servir às requisições dos clientes, iniciando e submetendo-o ao abrir e fechar da `Session`. O relacionamento entre os dois é um-para-um e este modelo é um ajuste perfeito para muitas aplicações.

O desafio encontra-se na implementação. O Hibernate fornece gerenciamento integrado da "sessão atual" para simplificar este modelo. Tudo que você tem a fazer é iniciar uma transação quando uma requisição precisa ser processada e terminar a transação antes que a resposta seja enviada ao cliente. Você pode fazer onde quiser, soluções comuns são `ServletFilter`, interceptador AOP com um pointcut (ponto de corte) nos métodos de serviço ou em um recipiente de proxy/interceptação. Um recipiente de EJB é uma maneira padronizada de implementar aspectos cross-cutting, tais como a demarcação da transação em beans de sessão EJB, declarativamente com CMT. Se você se decidir usar demarcação programática de transação, dê preferência à API `Transaction` do Hibernate mostrada mais adiante neste capítulo, para facilidade no uso e portabilidade de código.

Your application code can access a "current session" to process the request by calling `sessionFactory.getCurrentSession()`. You will always get a `Session` scoped to the current database transaction. This has to be configured for either resource-local or JTA environments, see Seção 2.3, “Sessões Contextuais”.

Ás vezes, é conveniente estender o escopo de uma `Session` e de uma transação do banco de dados até que a "visão esteja renderizada". É especialmente útil em aplicações servlet que utilizam uma fase de renderização separada depois da requisição ter sido processada. Estender a transação até que a renderização da visão esteja completa é fácil de fazer se você implementar seu próprio interceptador. Entretanto, não será fácil se você confiar em EJBs com transações gerenciadas por recipiente, porque uma transação será terminada quando um método de EJB retornar, antes que a renderização de toda visão possa começar. Veja o website e o fórum do Hibernate para dicas e exemplos em torno deste modelo de Sessão Aberta na Visualização.

### 13.1.2. Longas conversações

O modelo sessão-por-requisição não é o único conceito útil que você pode usar ao projetar unidades de trabalho. Muitos processos de negócio requerem uma totalidade de séries de interações com o usuário, intercaladas com acessos a uma base de dados. Em aplicações da web e corporativas não é aceitável que uma transação atrapalhe uma interação do usuário. Considere o seguinte exemplo:

- A primeira tela de um diálogo se abre e os dados vistos pelo usuário são carregados em uma `Session` e transação de banco de dados particulares. O usuário está livre para modificar os objetos.

- O usuário clica em "Salvar" após 5 minutos e espera suas modificações serem persistidas. O usuário também espera que ele seja a única pessoa que edita esta informação e que nenhuma modificação conflitante possa ocorrer.

Nós chamamos esta unidade de trabalho, do ponto da visão do usuário, uma conversação de longa duração (ou transação da aplicação). Há muitas maneiras de você implementar em sua aplicação.

Uma primeira implementação simples pode manter a `Session` e a transação aberta durante o tempo de interação do usuário, com bloqueios na base de dados para impedir a modificação concorrente e para garantir o isolamento e a atomicidade. Esse é naturalmente um anti-pattern, uma vez que a disputa do bloqueio não permitiria o escalonameneto da aplicação com o número de usuários concorrentes.

Claramente, temos que usar diversas transações para implementar a conversação. Neste caso, manter o isolamento dos processos de negócio, torna-se responsabilidade parcial da camada da aplicação. Uma única conversação geralmente usa diversas transações. Ela será atômica se somente uma destas transações (a última) armazenar os dados atualizados, todas as outras simplesmente leram os dados (por exemplo em um diálogo do estilo wizard que mede diversos ciclos de requisição/resposta). Isto é mais fácil de implementar do parece, especialmente se você usar as características do Hibernate:

- Versionamento automático: o Hibernate pode fazer o controle automático de concorrência otimista para você, ele pode automaticamente detectar se uma modificação concorrente ocorreu durante o tempo de interação do usuário. Geralmente nós verificamos somente no fim da conversação.

- Objetos Desanexados: se você se decidir usar o já discutido pattern sessão-por-solicitação, todas as instâncias carregadas estarão no estado destacado durante o tempo em que o usuário estiver pensando. O Hibernate permite que você re-anexe os objetos e persista as modificações, esse pattern é chamado sessão-por-solicitação-com-objetos-desanexados. Utiliza-se versionamento automático para isolar as modificações concorrentes.

- Sessão Estendida (ou Longa) A `Session` do Hibernate pode ser desligada da conexão adjacente do JDBC depois que a transação foi submetida, e ser reconectada quando uma nova requisição do cliente ocorrer. Este pattern é conhecido como sessão-por-conversação e faz o reatamento uniforme desnecessário. Versionamento automático é usado para isolar modificações concorrentes e a sessão-por-conversação geralmente pode ser nivelada automaticamente, e sim explicitamente.

Tanto a sessão-por-solicitação-com-objetos-desanexados quanto a sessão-por-conversação possuem vantagens e desvantagens. Estas desvantagens serão discutidas mais tarde neste capítulo no contexto do controle de concorrência otimista.

### 13.1.3. Considerando a identidade do objeto

Uma aplicação pode acessar concorrentemente o mesmo estado persistente em duas `Session`s diferentes. Entretanto, uma instância de uma classe persistente nunca é compartilhada entre duas instâncias `Session`. Portanto, há duas noções diferentes da identidade:

Identidade da base de dados

  `foo.getId().equals( bar.getId() )`

Identidade da JVM

  `foo==bar`

Então para os objetos acoplados a uma `Session` específica (ex.: isto está no escopo de uma `Session`), as duas noções são equivalentes e a identidade da JVM para a identidade da base de dados é garantida pelo Hibernate. Entretanto, embora a aplicação possa acessar concorrentemente o "mesmo" objeto do negócio (identidade persistente) em duas sessões diferentes, as duas instâncias serão realmente "diferentes" (identidade de JVM). Os conflitos são resolvidos usando (versionamento automático) no flush/commit, usando uma abordagem otimista.

Este caminho deixa o Hibernate e o banco de dados se preocuparem com a concorrência. Ele também fornece uma escalabilidade melhor, garantindo que a identidade em unidades de trabalho single-threaded não necessite de bloqueio dispendioso ou de outros meios de sincronização. A aplicação nunca necessita sincronizar qualquer objeto de negócio tão longo que transpasse uma única thread por `Session`. Dentro de uma `Session` a aplicação pode usar com segurança o `==` para comparar objetos.

No entanto, uma aplicação que usa `==` fora de uma `Session`, pode ver resultados inesperados. Isto pode ocorrer mesmo em alguns lugares inesperados, por exemplo, se você colocar duas instâncias desacopladas em um mesmo `Set`. Ambas podem ter a mesma identidade na base de dados (ex.: elas representam a mesma linha), mas a identidade da JVM não é, por definição, garantida para instâncias em estado desacoplado. O desenvolvedor tem que substituir os métodos `equals()` e `hashCode()` em classes persistentes e implementar sua própria noção da igualdade do objeto. Advertência: nunca use o identificador da base de dados para implementar a igualdade, use atributos de negócio, uma combinação única, geralmente imutável. O identificador da base de dados mudará se um objeto transiente passar para o estado persistente. Se a instância transiente (geralmente junto com instâncias desacopladas) for inserida em um `Set`, a mudança do hashcode quebrará o contrato do `Set`. As funções para chaves de negócio não têm que ser tão estável quanto às chaves primárias da base de dados, você somente tem que garantir a estabilidade durante o tempo que os objetos estiverem no mesmo `Set`. Veja o website do Hibernate para uma discussão mais completa sobre o assunto. Note também que esta não é uma característica do Hibernate, mas simplesmente a maneira como a identidade e a igualdade do objeto de Java têm que ser implementadas.

### 13.1.4. Edições comuns

Nunca use o anti-patterns sessão-por-usuário-sessão ou sessão-por-aplicação (naturalmente, existem exceções raras para essa regra). Note que algumas das seguintes edições podem também aparecer com modelos recomendados, certifique-se que tenha compreendido as implicações antes de fazer uma decisão de projeto:

- Uma `Session` não é threadsafe. As coisas que são supostas para trabalhar concorrentemente, como requisições HTTP, beans de sessão, ou Swing, causarão condições de disputa se uma instância `Session` for compartilhada. Se você mantiver sua `Session` do Hibernate em seu `HttpSession` (discutido mais tarde), você deverá considerar sincronizar o acesso a sua sessão do HTTP. Caso contrário, um usuário que clica em recarga rápido demais, pode usar o mesmo `Session` em duas threads executando simultaneamente.

- Uma exceção lançada pelo Hibernate significa que você tem que dar rollback na sua transação no banco de dados e fechar a `Session` imediatamente (discutido mais tarde em maiores detalhes). Se sua `Session` é limitada pela aplicação, você tem que parar a aplicação. Fazer o rollback na transação no banco de dados não retorna seus objetos do negócio ao estado que estavam no início da transação. Isto significa que o estado da base de dados e os objetos de negócio perdem a sincronização. Geralmente, não é um problema porque as exceções não são recuperáveis e você tem que iniciar após o rollback de qualquer maneira.

- The `Session` caches every object that is in a persistent state (watched and checked for dirty state by Hibernate). If you keep it open for a long time or simply load too much data, it will grow endlessly until you get an OutOfMemoryException. One solution is to call `clear()` and `evict()` to manage the `Session` cache, but you should consider a Stored Procedure if you need mass data operations. Some solutions are shown in Capítulo 15, Batch processing. Keeping a `Session` open for the duration of a user session also means a higher probability of stale data.

## 13.2. Demarcação de transações de bancos de dados

Os limites de uma transação de banco de dados, ou sistema, são sempre necessários. Nenhuma comunicação com o banco de dados pode ocorrer fora de uma transação de banco de dados (isto parece confundir muitos desenvolvedores que estão acostumados ao modo auto-commit). Sempre use os limites desobstruídos da transação, até mesmo para operações somente leitura. Dependendo de seu nível de isolamento e capacidade da base de dados isto pode não ser requerido, mas não há nenhum aspecto negativo se você sempre demarcar transações explicitamente. Certamente, uma única transação será melhor executada do que muitas transações pequenas, até mesmo para dados de leitura.

Uma aplicação do Hibernate pode funcionar em ambientes não gerenciados (isto é, aplicações standalone, Web simples ou Swing) e ambientes gerenciados J2EE. Em um ambiente não gerenciado, o Hibernate é geralmente responsável pelo seu próprio pool de conexões. O desenvolvedor, precisa ajustar manualmente os limites das transaçãos, ou seja, começar, submeter ou efetar rollback nas transações ele mesmo. Um ambiente gerenciado fornece transações gerenciadas por recipiente (CMT), com um conjunto da transações definido declarativamente em descritores de implementação de beans de sessão EJB, por exemplo. A demarcação programática é portanto, não mais necessária.

Entretanto, é freqüentemente desejável manter sua camada de persistência portável entre ambientes de recurso locais não gerenciados e sistemas que podem confiar em JTA, mas use BMT ao invés de CMT. Em ambos os casos você usaria demarcação de transação programática. O Hibernate oferece uma API chamada Transaction que traduz dentro do sistema de transação nativa de seu ambiente de implementação. Esta API é realmente opcional, mas nós encorajamos fortemente seu uso a menos que você esteja em um bean de sessão CMT.

Geralmente, finalizar uma `Session` envolve quatro fases distintas:

- liberar a sessão

- submeter a transação

- fechar a sessão

- tratar as exceções

A liberação da sessão já foi bem discutida, agora nós daremos uma olhada na demarcação da transação e na manipulação de exceção em ambientes controlados e não controlados.

### 13.2.1. Ambiente não gerenciado

Se uma camada de persistência do Hibernate roda em um ambiente não gerenciado, as conexões do banco de dados são geralmente tratadas pelos pools de conexões simples (ex.: não DataSource) dos quais o Hibernate obtém as conexões assim que necessitar. A maneira de se manipular uma sessão/transação é mais ou menos assim:

    // Non-managed environment idiom

    Session sess = factory.openSession();

    Transaction tx = null;

    try {

        tx = sess.beginTransaction();

        // do some work

        ...

        tx.commit();

    }

    catch (RuntimeException e) {

        if (tx != null) tx.rollback();

        throw e; // or display error message

    }

    finally {

        sess.close();

    }

Você não pode chamar `flush()` da `Session()` explicitamente. A chamada ao `commit()` dispara automaticamente a sincronização para a sessão, dependendo do Seção 11.10, “Limpando a Sessão”. Uma chamada ao `close()` marca o fim de uma sessão. A principal implicação do `close()` é que a conexão JDBC será abandonada pela sessão. Este código Java é portável e funciona em ambientes não gerenciados e de JTA.

Uma solução muito mais flexível é o gerenciamento de contexto "sessão atual" da construção interna do Hibernate, como descrito anteriormente:

    // Non-managed environment idiom with getCurrentSession()

    try {

        factory.getCurrentSession().beginTransaction();

        // do some work

        ...

        factory.getCurrentSession().getTransaction().commit();

    }

    catch (RuntimeException e) {

        factory.getCurrentSession().getTransaction().rollback();

        throw e; // or display error message

    }

Você muito provavelmente nunca verá estes fragmentos de código em uma aplicação regular; as exceções fatais (do sistema) devem sempre ser pegas no "topo". Ou seja, o código que executa chamadas do Hibernate (na camada de persistência) e o código que trata `RuntimeException` (e geralmente pode somente limpar acima e na saída) estão em camadas diferentes. O gerenciamento do contexto atual feito pelo Hibernate pode significativamente simplificar este projeto, como tudo que você necessita é do acesso a um `SessionFactory`. A manipulação de exceção é discutida mais tarde neste capítulo.

Note que você deve selecionar `org.hibernate.transaction.JDBCTransactionFactory`, que é o padrão, e para o segundo exemplo `"thread"` como seu `hibernate.current_session_context_class`.

### 13.2.2. Usando JTA

Se sua camada de persistência funcionar em um servidor de aplicação (por exemplo, dentro dos beans de sessão EJB), cada conexão da fonte de dados obtida pelo Hibernate automaticamente fará parte da transação global de JTA. Você pode também instalar uma implementação standalone de JTA e usá-la sem EJB. O Hibernate oferece duas estratégias para a integração de JTA.

Se você usar transações de bean gerenciado (BMT) o Hibernate dirá ao servidor de aplicação para começar e para terminar uma transação de BMT se você usar a `Transaction` API. Assim, o código de gerência de transação é idêntico ao ambiente não gerenciado.

    // BMT idiom

    Session sess = factory.openSession();

    Transaction tx = null;

    try {

        tx = sess.beginTransaction();

        // do some work

        ...

        tx.commit();

    }

    catch (RuntimeException e) {

        if (tx != null) tx.rollback();

        throw e; // or display error message

    }

    finally {

        sess.close();

    }

Se você quiser usar uma `Session` limitada por transação, isto é, a funcionalidade do `getCurrentSession()` para a propagação fácil do contexto, você terá que usar diretamente a API JTA `UserTransaction`:

    // BMT idiom with getCurrentSession()

    try {

        UserTransaction tx = (UserTransaction)new InitialContext()

                                .lookup("java:comp/UserTransaction");

        tx.begin();

        // Do some work on Session bound to transaction

        factory.getCurrentSession().load(...);

        factory.getCurrentSession().persist(...);

        tx.commit();

    }

    catch (RuntimeException e) {

        tx.rollback();

        throw e; // or display error message

    }

Com CMT, a demarcação da transação é feita em descritores de implementação de beans de sessão, não programaticamente, conseqüentemente, o código é reduzido a:

    // CMT idiom

     Session sess = factory.getCurrentSession();

     // do some work

     ...

Em um CMT/EJB, até mesmo um rollback acontece automaticamente, desde que uma exceção `RuntimeException` não tratável seja lançada por um método de um bean de sessão que informa ao recipiente ajustar a transação global ao rollback. Isto significa que você não precisa mesmo usar a API `Transaction` do Hibernate com BMT ou CMT e você obterá a propagação automática da Sessão "atual" limitada à transação.

Veja que você deverá escolher `org.hibernate.transaction.JTATransactionFactory` se você usar o JTA diretamente (BMT) e `org.hibernate.transaction.CMTTransactionFactory` em um bean de sessão CMT, quando você configura a fábrica de transação do Hibernate. Lembre-se também de configurar o `hibernate.transaction.manager_lookup_class`. Além disso, certifique-se que seu `hibernate.current_session_context_class` ou não é configurado (compatibilidade com o legado) ou está definido para `"jta"`.

A operação `getCurrentSession()` tem um aspecto negativo em um ambiente JTA. Há uma advertência para o uso do método liberado de conexão `after_statement`, o qual é usado então por padrão. Devido a uma limitação simples da especificação JTA, não é possível para o Hibernate automaticamente limpar quaisquer instâncias `ScrollableResults` ou `Iterator` abertas retornadas pelo `scroll()` ou `iterate()`. Você deve liberar o cursor subjacente da base de dados chamando `ScrollableResults.close()` ou `Hibernate.close(Iterator)` explicitamente de um bloco `finally`. Claro que a maioria das aplicações podem facilmente evitar o uso do `scroll()` ou do `iterate()` em todo código provindo do JTA ou do CMT.

### 13.2.3. Tratamento de Exceção

Se a `Session` levantar uma exceção, incluindo qualquer `SQLException`, você deverá imediatamente dar um rollback na transação do banco, chamando `Session.close()` e descartando a instância da `Session`. Certos métodos da `Session` `não` deixarão a sessão em um estado inconsistente. Nenhuma exceção lançada pelo Hibernate pode ser recuperada. Certifique-se que a `Session` será fechada chamando `close()` no bloco `finally`.

A exceção `HibernateException`, a qual envolve a maioria dos erros que podem ocorrer em uma camada de persistência do Hibernate, é uma exceção não verificada. Ela não constava em versões mais antigas de Hibernate. Em nossa opinião, nós não devemos forçar o desenvolvedor a tratar uma exceção irrecuperável em uma camada mais baixa. Na maioria dos sistemas, as exceções não verificadas e fatais são tratadas em um dos primeiros frames da pilha da chamada do método (isto é, em umas camadas mais elevadas) e uma mensagem de erro é apresentada ao usuário da aplicação (ou alguma outra ação apropriada é feita). Note que Hibernate pode também lançar outras exceções não verificadas que não sejam um `HibernateException`. Estas, também são, irrecuperáveis e uma ação apropriada deve ser tomada.

O Hibernate envolve `SQLException`s lançadas ao interagir com a base de dados em um `JDBCException`. Na realidade, o Hibernate tentará converter a exceção em uma subclasse mais significativa da `JDBCException`. A `SQLException` subjacente está sempre disponível através de `JDBCException.getCause()`. O Hibernate converte a `SQLException` em uma subclasse `JDBCException` apropriada usando `SQLExceptionConverter` associado ao `SessionFactory`. Por padrão, o `SQLExceptionConverter` é definido pelo dialeto configurado. Entretanto, é também possível conectar em uma implementação customizada. Veja o javadoc para mais detalhes da classe `SQLExceptionConverterFactory`. Os subtipos padrão de `JDBCException` são:

- `JDBCConnectionException`: indica um erro com a comunicação subjacente de JDBC.

- `SQLGrammarException`: indica um problema da gramática ou da sintáxe com o SQL emitido.

- `ConstraintViolationException`: indica algum forma de violação de confinamento de integridade.

- `LockAcquisitionException`: indica um erro ao adquirir um nível de bloqueio necessário para realizar a operação de requisição.

- `GenericJDBCException`: uma exceção genérica que não está inclusa em nenhuma das outras categorias.

### 13.2.4. Tempo de espera de Transação

O tempo de espera de transação é uma característica extremamente importante fornecida por um ambiente gerenciado como EJB e que nunca é fornecido pelo código não gerenciado. Os tempos de espera de transação asseguram que nenhuma transação retenha indefinidamente recursos enquanto não retornar nenhuma resposta ao usuário. Fora de um ambiente controlado (JTA), o Hibernate não pode fornecer inteiramente esta funcionalidade. Entretanto, o Hibernate pode afinal controlar as operações do acesso a dados, assegurando que o nível de deadlocks e consultas do banco de dados com imensos resultados definidos sejam limitados pelo tempo de espera. Em um ambiente gerenciado, o Hibernate pode delegar o tempo de espera da transação ao JTA. Esta funcionalidade é abstraída pelo objeto `Transaction` do Hibernate.

    Session sess = factory.openSession();

    try {

        //set transaction timeout to 3 seconds

        sess.getTransaction().setTimeout(3);

        sess.getTransaction().begin();

        // do some work

        ...

        sess.getTransaction().commit()

    }

    catch (RuntimeException e) {

        sess.getTransaction().rollback();

        throw e; // or display error message

    }

    finally {

        sess.close();

    }

Veja que `setTimeout()` não pode ser chamado em um bean CMT, onde o tempo de espera das transações deve ser definido declaradamente.

## 13.3. Controle de concorrência otimista

O único caminho que é consistente com a elevada concorrência e escalabilidade é o controle de concorrência otimista com versionamento. A checagem de versão usa número de versão, ou carimbo de hora (timestamp), para detectar conflitos de atualizações (e para impedir atualizações perdidas). O Hibernate fornece três caminhos possíveis para escrever aplicações que usam concorrência otimista. Os casos de uso que nós mostramos estão no contexto de conversações longas, mas a checagem de versão também tem o benefício de impedir atualizações perdidas em únicas transações.

### 13.3.1. Checagem de versão da aplicação

Em uma implementação sem muita ajuda do Hibernate, cada interação com o banco de dados ocorre em uma nova `Session` e o desenvolvedor é responsável por recarregar todas as instâncias persistentes da base de dados antes de manipulá-las. Este caminho força a aplicação a realizar sua própria checagem de versão para assegurar a conversação do isolamento da transação. Este caminho é menos eficiente em termos de acesso ao banco de dados. É o caminho mais similar à entidade EJBs.

    // foo is an instance loaded by a previous Session

    session = factory.openSession();

    Transaction t = session.beginTransaction();

    int oldVersion = foo.getVersion();

    session.load( foo, foo.getKey() ); // load the current state

    if ( oldVersion != foo.getVersion() ) throw new StaleObjectStateException();

    foo.setProperty("bar");

    t.commit();

    session.close();

A propriedade `version` é mapeada usando `<version>`, e o Hibernate vai incrementá-lá automaticamente durante a liberação se a entidade estiver alterada.

Claro, se você estiver operando em um ambiente de baixa concorrência de dados e não precisar da checagem de versão, você pode usar este caminho e apenas pular a checagem de versão. Nesse caso, o último commit realizado é a estratégia padrão para suas conversações longas. Tenha em mente que isto pode confundir os usuários da aplicação, como também poderão ter atualizações perdidas sem mensagens de erro ou uma possibilidade de ajustar mudanças conflitantes.

Claro que, a checagem manual da versão é somente possível em circunstâncias triviais e não para a maioria de aplicações. Freqüentemente, os gráficoscompletos de objetos modificados têm que ser verificados, não somente únicas instâncias. O Hibernate oferece checagem de versão automática com uma `Session` estendida ou instâncias desatachadas como o paradigma do projeto.

### 13.3.2. Sessão estendida e versionamento automático

Uma única instância de `Session` e suas instâncias persistentes são usadas para a conversação inteira, isto é conhecido como sessão-por-conversação. O Hibernate verifica versões da instância no momento da liberação, lançando uma exceção se a modificação concorrente for detectada. Até o desenvolvedor pegar e tratar essa exceção. As opções comuns são a oportunidade para que o usuário intercale as mudanças ou reinicie a conversação do negócio com dados não antigos.

A `Session` é desconectada de toda a conexão JDBC adjacente enquanto espera a interação do usuário. Este caminho é o mais eficiente em termos de acesso a bancos de dados. A aplicação não precisa se preocupar com a checagem de versão ou com as instâncias destacadas reatadas, nem precisa recarregar instâncias a cada transação.

    // foo is an instance loaded earlier by the old session

    Transaction t = session.beginTransaction(); // Obtain a new JDBC connection, start transaction

    foo.setProperty("bar");

    session.flush();    // Only for last transaction in conversation

    t.commit();         // Also return JDBC connection

    session.close();    // Only for last transaction in conversation

O objeto `foo` sabe que a `Session` já foi carregada. Ao começar uma nova transação ou uma sessão velha, você obterá uma conexão nova e reiniciará a sessão. Submeter uma transação implica em desconectar uma sessão da conexão JDBC e retornar à conexão ao pool. Após a reconexão, para forçar uma checagem de versão em dados que você não esteja atualizando, você poderá chamar `Session.lock()` com o `LockMode.READ` em todos os objetos que possam ter sido atualizados por uma outra transação. Você não precisa bloquear nenhum dado que você está atualizando. Geralmente, você configuraria `FlushMode.NEVER` em uma `Session` estendida, de modo que somente o último ciclo da transação tenha permissão de persistir todas as modificações feitas nesta conversação. Por isso, somente esta última transação incluiria a operação `flush()` e então também iria `close()` a sessão para terminar a conversação.

Este modelo é problemático se a `Session` for demasiadamente grande para ser armazenada durante o tempo de espera do usuário (por exemplo uma `HttpSession` deve ser mantida o menor possível). Como a `Session` é também cache de primeiro nível (imperativo) e contém todos os objetos carregados, nós podemos provavelmente usar esta estratégia somente para alguns ciclos de requisição/resposta. Você deve usar a `Session` somente para uma única conversação, porque ela logo também estará com dados velhos.

## Nota

Note que versões mais atuais de Hibernate requerem a desconexão e reconexão explícitas de uma `Session`. Estes métodos são desatualizados, pois o início e término de uma transação têm o mesmo efeito.

Note também que você deve manter a `Session` desconectada, fechada para a camada de persistência. Ou seja, use um bean de sessão com estado EJB para prender a `Session` em um ambiente de três camadas. Não transfira à camada web, ou até serializá-lo para uma camada separada, para armazená-lo no `HttpSession`.

O modelo da sessão estendida, ou sessão-por-conversação, é mais difícil de implementar com gerenciamento automático de sessão atual. Você precisa fornecer sua própria implementação do `CurrentSessionContext` para isto. Veja o Hibernate Wiki para exemplos.

### 13.3.3. Objetos destacados e versionamento automático

Cada interação com o armazenamento persistente ocorre em uma `Session` nova. Entretanto, as mesmas instâncias persistentes são reusadas para cada interação com o banco de dados. A aplicação manipula o estado das instâncias desatachadas originalmente carregadas em uma outra `Session` e as reata então usando `Session.update()`, `Session.saveOrUpdate()` ou `Session.merge()`.

    // foo is an instance loaded by a previous Session

    foo.setProperty("bar");

    session = factory.openSession();

    Transaction t = session.beginTransaction();

    session.saveOrUpdate(foo); // Use merge() if "foo" might have been loaded already

    t.commit();

    session.close();

Outra vez, o Hibernate verificará versões da instância durante a liberação, lançando uma exceção se ocorrer conflitos de atualizações.

Você pode também chamar o `lock()` em vez de `update()` e usar `LockMode.READ` (executando uma checagem de versão, ignorando todos os caches) se você estiver certo de que o objeto não foi modificado.

### 13.3.4. Versionamento automático customizado

Você pode desabilitar o incremento da versão automática de Hibernate para propriedades e coleções particulares, configurando a função de mapeamento `optimistic-lock` para `false`. O Hibernate então, não incrementará mais versões se a propriedade estiver modificada.

Os esquemas da base de dados legado são freqüentemente estáticos e não podem ser modificados. Ou então, outras aplicações puderam também acessar a mesma base de dados e não sabem tratar a versão dos números ou carimbos de hora. Em ambos os casos, o versionamento não pode confiar em uma coluna particular em uma tabela. Para forçar uma checagem de versão sem uma versão ou mapeamento da propriedade do carimbo de hora com uma comparação do estado de todos os campos em uma linha, configure `optimistic-lock="all"` no mapeamento `<class>`. Note que isto conceitualmente é somente feito em trabalhos se o Hibernate puder comparar o estado velho e novo (ex.: se você usar uma única `Session` longa e não uma sessão-por-solicitação-com-objetos-desanexados).

Às vezes a modificação concorrente pode ser permitida, desde que as mudanças realizadas não se sobreponham. Se você configurar `optimistic-lock="dirty"` ao mapear o `<class>`, o Hibernate comparará somente campos modificados durante a liberação.

Em ambos os casos, com as colunas de versão/carimbo de hora dedicados com comparação de campo cheio/sujo, o Hibernate usa uma única instrução `UPDATE`, com uma cláusula `WHERE` apropriada, por entidade para executar a checagem da versão e atualizar a informação. Se você usar a persistência transitiva para cascatear o reatamento das entidades associadas, o Hibernate pode executar atualizações desnecessárias. Isso não é geralmente um problema, mas os triggers em atualizações num banco de dados pode ser executado mesmo quando nenhuma mudança foi feita nas instâncias desanexadas. Você pode customizar este comportamento configurando `selecionar-antes-de atualizar="verdadeiro"` no mapeamento `<class>`, forçando o Hibernate a fazer um `SELECT` nas instâncias para assegurar-se de que as mudanças realmente aconteceram, antes de atualizar a linha.

## 13.4. Bloqueio Pessimista

Não ha intenção alguma que usuários gastem muitas horas se preocupando com suas estratégias de bloqueio. Geralmente, é o bastante especificar um nível de isolamento para as conexões JDBC e então deixar simplesmente o banco de dados fazer todo o trabalho. Entretanto, os usuários avançados podem às vezes desejar obter bloqueios pessimistas exclusivos, ou re-obter bloqueios no início de uma nova transação.

O Hibernate usará sempre o mecanismo de bloqueio da base de dados, nunca bloquiar objetos na memória.

A classe `LockMode` define os diferentes níveis de bloqueio que o Hibernate pode adquirir. Um bloqueio é obtido pelos seguintes mecanismos:

- `LockMode.WRITE` é adquirido automaticamente quando o Hibernate atualiza ou insere uma linha.

- `LockMode.UPGRADE` pode ser adquirido explicitamente pelo usuário usando `SELECT ... FOR UPDATE` em um banco de dados que suporte essa sintáxe.

- `LockMode.UPGRADE_NOWAIT` pode ser adquirido explicitamente pelo usuário usando `SELECT ... FOR UPDATE NOWAIT` no Oracle.

- `LockMode.READ` é adquirido automaticamente quando o Hibernate lê dados em um nível de Leitura Repetida ou isolamento Serializável. Pode ser readquirido explicitamente pelo usuário.

- `LockMode.NONE` representa a ausência do bloqueio. Todos os objetos mudam para esse estado de bloqueio no final da `Transaction`. Objetos associados com a sessão através do método `update()` ou `saveOrUpdate()` também são inicializados com esse modo de bloqueio.

O bloqueio obtido "explicitamente pelo usuário" se dá nas seguintes formas:

- Uma chamada a `Session.load()`, especificando o `LockMode`.

- Uma chamada à `Session.lock()`.

- Uma chamada à `Query.setLockMode()`.

Se uma `Session.load()` é invocada com `UPGRADE` ou `UPGRADE_NOWAIT`, e o objeto requisitado ainda não foi carregado pela sessão, o objeto é carregado usando `SELECT ... FOR UPDATE`. Se `load()` for chamado para um objeto que já foi carregado com um bloqueio menos restritivo que o novo bloqueio solicitado, o Hibernate invoca o método `lock()` para aquele objeto.

O `Session.lock()` executa uma verificação no número da versão se o modo de bloqueio especificado for `READ`, `UPGRADE` ou `UPGRADE_NOWAIT`. No caso do `UPGRADE` ou `UPGRADE_NOWAIT`, é usado `SELECT ... FOR UPDATE`.

Se o banco de dados não suportar o modo de bloqueio solicitado, o Hibernate usará um modo alternativo apropriado, ao invés de lançar uma exceção. Isso garante que a aplicação seja portátil.

## 13.5. Modos para liberar a conexão

O comportamento legado do Hibernate 2.x referente ao gerenciamento da conexão via JDBC era que a `Session` precisaria obter uma conexão quando ela precisasse pela primeira vez e depois manteria a conexão enquanto a sessão não fosse fechada. O Hibernate 3.x introduz a idéia de modos para liberar a sessão, para informar a sessão a forma como deve manusear a sua conexão JDBC. Veja que essa discussão só é pertinente para conexões fornecidas com um `ConnectionProvider` configurado. As conexões fornecidas pelo usuário estão fora do escopo dessa discussão. Os diferentes modos de liberação estão definidos pelos valores da enumeração `org.hibernate.ConnectionReleaseMode`:

- `ON_CLOSE`: é o modo legado descrito acima. A sessão do Hibernate obtém a conexão quando precisar executar alguma operação JDBC pela primeira vez e mantém enquanto a conexão não for fechada.

- `AFTER_TRANSACTION`: informa que a conexão deve ser liberada após a conclusão de uma `org.hibernate.Transaction`.

- `AFTER_STATEMENT` (também conhecida como liberação agressiva): informa que a conexão deve ser liberada após a execução de cada instrução. A liberação agressiva não ocorre se a instrução deixa pra trás algum recurso aberto associado com a sessão obtida. Atualmente, a única situação em que isto ocorre é com o uso de `org.hibernate.ScrollableResults`.

O parâmetro de configuração `hibernate.connection.release_mode` é usado para especificar qual modo de liberação deve ser usado. Segue abaixo os valores possíveis:

- `auto` (padrão): essa opção delega ao modo de liberação retornado pelo método `org.hibernate.transaction.TransactionFactory.getDefaultReleaseMode()`. Para JTATransactionFactory, ele retorna ConnectionReleaseMode.AFTER_STATEMENT; para JDBCTransactionFactory, ele retorna ConnectionReleaseMode.AFTER_TRANSACTION. Raramente, é uma boa idéia alterar padrão, pois ao se fazer isso temos falhas que parecem bugs e/ou suposições inválidas no código do usuário.

- `on_close`: indica o uso da ConnectionReleaseMode.ON_CLOSE. Essa opção foi deixada para manter a compatibilidade, mas seu uso é fortemente desencorajado.

- `after_transaction`: indica o uso da ConnectionReleaseMode.AFTER_TRANSACTION. Essa opção não deve ser usada com ambientes JTA. Também note que no caso da ConnectionReleaseMode.AFTER_TRANSACTION, se a sessão foi colocada no modo auto-commit a conexão vai ser liberada de forma similar ao modo AFTER_STATEMENT.

- `after_statement`: indica o uso ConnectionReleaseMode.AFTER_STATEMENT. Além disso, o `ConnectionProvider` configurado é consultado para verificar se suporta essa configuração (`supportsAggressiveRelease()`). Se não suportar, o modo de liberação é redefinido como ConnectionRelease-Mode.AFTER_TRANSACTION. Essa configuração só é segura em ambientes onde podemos tanto readquirir a mesma conexão JDBC adjacente todas as vezes que chamarmos `ConnectionProvider.getConnection()` quanto em um ambiente auto-commit, onde não importa se voltamos para a mesma conexão.

Copyright © 2004 Red Hat, Inc.

- AnteriorCapítulo 12. Read-only entities
- Acima
- Principal
- PróximaCapítulo 14. Interceptadores e Eventos