Hibernate.orgCommunity Documentation

Capítulo 15. Batch processing

15.1. Inserção em lotes
15.2. Atualização em lotes
15.3. A interface de Sessão sem Estado
15.4. Operações no estilo DML

Uma alternativa para inserir 100.000 linhas no banco de dados usando o Hibernate pode ser a seguinte:

Session session = sessionFactory.openSession();

Transaction tx = session.beginTransaction();
for ( int i=0; i<100000; i++ ) {
    Customer customer = new Customer(.....);
    session.save(customer);
}
tx.commit();
session.close();

Isto irá falhar com um OutOfMemoryException em algum lugar próximo a linha 50.000. Isso ocorre devido ao fato do Hibernate fazer cache de todas as instâncias de Customer inseridas num cachê em nível de sessão. Nós demonstraremos neste capitulo como evitar este problema.

Entretanto, se você vai realizar processamento em lotes, é muito importante que você habilite o uso de lotes JDBC, se você pretende obter um desempenho razoável. Defina o tamanho do lote JDBC em um valor razoável (algo entre 10-50, por exemplo):

hibernate.jdbc.batch_size 20

Note que o Hibernate desabilita o loteamento de inserção no nível JDBC de forma transparente se você utilizar um gerador de identificador identiy.

Você também pode querer rodar esse tipo de processamento em lotes com o cache secundário completamente desabilitado:

hibernate.cache.use_second_level_cache false

Mas isto não é absolutamente necessário, desde que possamos ajustar o CacheMode para desabilitar a interação com o cache secundário.

Quando você estiver inserindo novos objetos persistentes, vocês deve executar os métodos flush() e clear() regularmente na sessão, para controlar o tamanho do cache primário.

Session session = sessionFactory.openSession();

Transaction tx = session.beginTransaction();
   
for ( int i=0; i<100000; i++ ) {
    Customer customer = new Customer(.....);
    session.save(customer);
    if ( i % 20 == 0 ) { //20, same as the JDBC batch size
        //flush a batch of inserts and release memory:
        session.flush();
        session.clear();
    }
}
   
tx.commit();
session.close();

Para recuperar e atualizar informações a mesma idéia é válida. Além disso, pode precisará usar o scroll() para usar recursos no lado do servidor em consultas que retornem muita informação.

Session session = sessionFactory.openSession();

Transaction tx = session.beginTransaction();
   
ScrollableResults customers = session.getNamedQuery("GetCustomers")
    .setCacheMode(CacheMode.IGNORE)
    .scroll(ScrollMode.FORWARD_ONLY);
int count=0;
while ( customers.next() ) {
    Customer customer = (Customer) customers.get(0);
    customer.updateStuff(...);
    if ( ++count % 20 == 0 ) {
        //flush a batch of updates and release memory:
        session.flush();
        session.clear();
    }
}
   
tx.commit();
session.close();

Como forma alternativa, o Hibernate provê uma API orientada à comandos, que pode ser usada para transmitir um fluxo de dados de e para o banco de dados na forma de objetos desanexados. Um StatelessSession não tem um contexto persistente associado e não fornece muito das semânticas de alto nível para controle do ciclo de vida. Especialmente uma Sessão sem Estado não implementa um cachê primário e nem interage com o cache secundário ou cachê de consulta. Ela não implementa uma gravação temporária transacional ou checagem suja automática. Operações realizadas usando uma sessão sem estado não fazem nenhum tipo de cascata com as instâncias associadas. As coleções são ignoradas por uma Sessão sem Estado. Operações realizadas com uma Sessão sem Estado ignoram a arquitetura de eventos e os interceptadores. As sessões sem estado são vulneráveis aos efeitos do alias dos dados, devido à falta do cachê primário. Uma Sessão sem Estado é uma abstração de baixo nível, muito mais próxima do JDBC adjacente.

StatelessSession session = sessionFactory.openStatelessSession();

Transaction tx = session.beginTransaction();
   
ScrollableResults customers = session.getNamedQuery("GetCustomers")
    .scroll(ScrollMode.FORWARD_ONLY);
while ( customers.next() ) {
    Customer customer = (Customer) customers.get(0);
    customer.updateStuff(...);
    session.update(customer);
}
   
tx.commit();
session.close();

Veja neste exempo, as instâncias de Customer retornadas pela consulta, são imediatamente desvinculadas. Elas nunca serão associadas à um contexto persistente.

As operações insert(), update() e delete() definidas pela interface StatelessSession são considerados operações diretas no banco de dados. Isto resulta em uma execução imediata de comandos SQL INSERT, UPDATE ou DELETE respectivamente. Dessa forma, eles possuem uma semântica bem diferente das operações save(), saveOrUpdate() ou delete() definidas na interface Session.

Como já discutido anteriormente, o mapeamento objeto/relacional automático e transparente é adquirido com a gerência do estado do objeto. Com isto o estado daquele objeto fica disponível na memória. Isto significa que a manipulação de dados (usando as instruções SQL Data Manipulation Language (SQL-style DML): INSERT, UPDATE, DELETE) diretamente no banco de dados não irá afetar o estado registrado em memória. Entretanto, o Hibernate provê métodos para executar instruções de volume de SQL-style DML, que são totalmente executados com HQL (Hibernate Query Language - Linguagem de Consulta Hibernate) (HQL).

A pseudo-sintaxe para instruções UPDATE e DELETE é: Algumas observações: ( UPDATE | DELETE ) FROM? EntityName (WHERE where_conditions)?.

Alguns pontos a serem destacados:

  • Na cláusula from, a palavra chave FROM é opcional;

  • Somente uma entidade pode ser chamada na cláusula from. Isto pode, opcionalmente, ser um alias. Se o nome da entidade for um alias, então qualquer referência de propriedade deve ser qualificada usando esse alias. Caso o nome da entidade não for um alias, então será ilegal qualquer das referências de propriedade serem qualificadas.

  • Nenhum joins, tanto implícito ou explícito, pode ser especificado em uma consulta de volume HQL. As Sub-consultas podem ser utilizadas na cláusula onde, em que as subconsultas podem conter uniões.

  • A clausula onde também é opcional.

Como exemplo para executar um HQL UPDATE, use o método Query.executeUpdate(). O método ganhou o nome devido à sua familiaridade com o do JDBC PreparedStatement.executeUpdate()):

Session session = sessionFactory.openSession();

Transaction tx = session.beginTransaction();
String hqlUpdate = "update Customer c set c.name = :newName where c.name = :oldName";
// or String hqlUpdate = "update Customer set name = :newName where name = :oldName";
int updatedEntities = s.createQuery( hqlUpdate )
        .setString( "newName", newName )
        .setString( "oldName", oldName )
        .executeUpdate();
tx.commit();
session.close();

As instruções do HQL UPDATE por padrão não afetam o version ou os valores de propriedade timestamp para as entidades afetadas, de acordo com a especificação EJB3. No entanto, você poderá forçar o Hibernate a redefinir corretamente os valores de propriedade version ou timestamp usando um versioned update. Para tal, adicione uma palavra chave VERSIONED após a palavra chave UPDATE.

Session session = sessionFactory.openSession();

Transaction tx = session.beginTransaction();
String hqlVersionedUpdate = "update versioned Customer set name = :newName where name = :oldName";
int updatedEntities = s.createQuery( hqlUpdate )
        .setString( "newName", newName )
        .setString( "oldName", oldName )
        .executeUpdate();
tx.commit();
session.close();

Note que os tipos de versões padronizadas, org.hibernate.usertype.UserVersionType, não são permitidos junto às instruções update versioned.

Para executar um HQL DELETE, use o mesmo método Query.executeUpdate():

Session session = sessionFactory.openSession();

Transaction tx = session.beginTransaction();
String hqlDelete = "delete Customer c where c.name = :oldName";
// or String hqlDelete = "delete Customer where name = :oldName";
int deletedEntities = s.createQuery( hqlDelete )
        .setString( "oldName", oldName )
        .executeUpdate();
tx.commit();
session.close();

O valor int retornado pelo método Query.executeUpdate() indica o número de entidade afetadas pela operação. Lembre-se que isso pode estar ou não relacionado ao número de linhas alteradas no banco de dados. Uma operação de volume HQL pode resultar em várias instruções SQL atuais a serem executadas (por exemplo, no caso de subclasses unidas). O número retornado indica a quantidade real de entidades afetadas pela instrução. Voltando ao exemplo da subclasse unida, a exclusão de uma das subclasses pode resultar numa exclusão em outra tabelas, não apenas na tabela para qual a subclasses está mapeada, mas também tabela "root" e possivelmente nas tabelas de subclasses unidas num nível hierárquico imediatamente abaixo.

A pseudo-sintáxe para o comando INSERT é: INSERT INTO EntityName properties_list select_statement. Alguns pontos a observar:

  • Apenas a forma INSERT INTO ... SELECT ... é suportada; INSERT INTO ... VALUES ... não é suportada.

    A lista de propriedade é análoga ao column specification do comando SQL INSERT. Para entidades envolvidas em mapeamentos, apenas as propriedades definidas diretamente em nível da classe podem ser usadas na properties_list. Propriedades da superclasse não são permitidas e as propriedades da subclasse não fazem sentido. Em outras palavras, os comandos INSERT não são polimórficos.

  • selecionar_instruções pode ser qualquer consulta de seleção HQL válida, desde que os tipos de retorno sejam compatíveis com os tipos esperados pela inserção. Atualmente, isto é verificado durante a compilação da consulta, ao invés de permitir que a verificação chegue ao banco de dados. Entretanto, perceba que isso pode causar problemas entre os Tipos de Hibernate que são equivalentes e não iguais. Isso pode causar problemas nas combinações entre a propriedade definida como org.hibernate.type.DateTypee uma propriedade definida como org.hibernate.type.TimestampType, embora o banco de dados não possa fazer uma distinção ou possa ser capaz de manusear a conversão.

  • Para a propriedade id, a instrução insert oferece duas opções. Você pode especificar qualquer propriedade id explicitamente no properties_list (em alguns casos esse valor é obtido diretamente da instrução select) ou pode omitir do properties_list (nesse caso, um valor gerado é usado). Essa última opção só é válida quando são usados geradores de ids que operam no banco de dados; a tentativa de usar essa opção com geradores do tipo "em memória" irá causar um exceção durante a etapa de análise. Note que para a finalidade desta discussão, os seguintes geradores operam com o banco de dados org.hibernate.id.SequenceGenerator (e suas subclasses) e qualquer implementação de org.hibernate.id.PostInsertIdentifierGenerator. Aqui, a exceção mais notável é o org.hibernate.id.TableHiLoGenerator, que não pode ser usado porque ele não dispõe de mecanismos para recuperar os seus valores.

  • Para propriedades mapeadas como version ou timestamp, a instrução insert lhe oferece duas opções. Você pode especificar a propriedade na properties_list, nesse caso o seu valor é obtido a partir da instrução select correspondente, ou ele pode ser omitido da properties_list (neste caso utiliza-se o seed value definido pela classe org.hibernate.type.VersionType).

Segue abaixo o exemplo da execução de um HQL INSERT:

Session session = sessionFactory.openSession();

Transaction tx = session.beginTransaction();
String hqlInsert = "insert into DelinquentAccount (id, name) select c.id, c.name from Customer c where ...";
int createdEntities = s.createQuery( hqlInsert )
        .executeUpdate();
tx.commit();
session.close();