Hibernate.org Community Documentation

- Précédent
- Suivant

## Chapitre 15. Traitement par lot

Une approche naïve pour insérer 100 000 lignes dans la base de données en utilisant Hibernate ressemblerait à :

    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();

Ceci devrait s'écrouler avec une `OutOfMemoryException` quelque part aux alentours de la 50 000ème ligne. C'est parce que Hibernate cache toutes les instances de `Customer` nouvellement insérées dans le cache de second niveau. Dans ce chapitre, nous allons vous montrer comment éviter ce problème.

Dans ce chapitre nous montrerons comment éviter ce problème. Mais tout d'abord, si vous faites des traitements par lot, il est absolument indispensable d'activer l'utilisation des lots JDBC, pour obtenir des performances raisonnables. Configurez la taille du lot JDBC à un nombre raisonnable (disons, 10-50) :

    hibernate.jdbc.batch_size 20

Notez que Hibernate désactive, de manière transparente, l'insertion par lot au niveau JDBC si vous utilisez un générateur d'identifiant de type `identity`.

Vous désirez peut-être effectuer ce genre de tâche dans un traitement où l'interaction avec le cache de second niveau est complètement désactivée :

    hibernate.cache.use_second_level_cache false

Toutefois ce n'est pas absolument nécessaire puisque nous pouvons configurer le `CacheMode` de façon à désactiver l'interaction avec le cache de second niveau.

## 15.1. Insertions en lot

Lorsque vous rendez des nouveaux objets persistants, vous devez régulièrement appeler `flush()` et puis `clear()` sur la session, pour contrôler la taille du cache de premier niveau.

    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();

## 15.2. Mise à jour des lots

Pour récupérer et mettre à jour des données les mêmes idées s'appliquent. De plus, vous devez utiliser `scroll()` pour tirer partie des curseurs côté serveur pour les requêtes qui retournent beaucoup de lignes de données.

    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();

## 15.3. L'interface StatelessSession

Alternativement, Hibernate fournit une API orientée commande qui peut être utilisée avec des flux de données vers et en provenance de la base de données sous la forme d'objets détachés. Une `StatelessSession` n'a pas de contexte de persistance associé et ne fournit pas beaucoup de sémantique de cycle de vie de haut niveau. En particulier, une session sans état n'implémente pas de cache de premier niveau et n'interagit pas non plus avec un cache de seconde niveau ou un cache de requêtes. Elle n'implémente pas les transactions ou la vérification sale automatique (automatic dirty checking). Les opérations réalisées avec une session sans état ne sont jamais répercutées en cascade sur les instances associées. Les collections sont ignorées par une session sans état. Les opérations exécutées via une session sans état outrepassent le modèle d'événements de Hibernate et les intercepteurs. Les sessions sans état sont vulnérables aux effets de réplication des données, ceci est dû au manque de cache de premier niveau. Une session sans état est une abstraction bas niveau, plus proche de la couche JDBC sous-jacente.

    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();

Notez que dans le code de l'exemple, les instances de `Customer` retournées par la requête sont immédiatement détachées. Elles ne sont jamais associées à un contexte de persistance.

Les opérations `insert()`, `update()` et `delete()` définies par l'interface `StatelessSession` sont considérées comme des opérations d'accès direct aux lignes de la base de données, ce qui résulte en une exécution immédiate du SQL `INSERT`, `UPDATE` ou `DELETE` respectivement. Ainsi, elles ont des sémantiques très différentes des opérations `save()`, `saveOrUpdate()` et `delete()` définies par l'interface `Session`.

## 15.4. Opérations de style DML

As already discussed, automatic and transparent object/relational mapping is concerned with the management of the object state. The object state is available in memory. This means that manipulating data directly in the database (using the SQL `Data Manipulation Language` (DML) the statements: `INSERT`, `UPDATE`, `DELETE`) will not affect in-memory state. However, Hibernate provides methods for bulk SQL-style DML statement execution that is performed through the Hibernate Query Language (HQL).

La pseudo-syntaxe pour les expressions `UPDATE` et `DELETE` est : `( UPDATE | DELETE ) FROM? EntityName (WHERE where_conditions)?`.

Certains points à noter :

- Dans la clause from, le mot-clef FROM est optionnel

- Il ne peut y avoir qu'une seule entité nommée dans la clause from ; elle peut optionnellement avoir un alias. Si le nom de l'entité a un alias, alors n'importe quelle référence de propriété doit être qualifiée en utilisant un alias ; si le nom de l'entité n'a pas d'alias, il sera illégal pour n'importe quelle référence de propriété d'être qualifiée.

- No joins, either implicit or explicit, can be specified in a bulk HQL query. Sub-queries can be used in the where-clause, where the subqueries themselves may contain joins.

- La clause where- est aussi optionnelle.

Par exemple, pour exécuter un HQL `UPDATE`, utilisez la méthode `Query.executeUpdate()` (la méthode est données pour ceux qui connaissent `PreparedStatement.executeUpdate()` de JDBC) :

    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();

In keeping with the EJB3 specification, HQL `UPDATE` statements, by default, do not effect the version or the timestamp property values for the affected entities. However, you can force Hibernate to reset the `version` or `timestamp` property values through the use of a `versioned update`. This is achieved by adding the `VERSIONED` keyword after the `UPDATE` keyword.

    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();

Notez que les types personnalisés (`org.hibernate.usertype.UserVersionType`) ne sont pas permis en conjonction avec la déclaration `update versioned`.

Pour exécuter un HQL `DELETE`, utilisez la même méthode `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();

La valeur du `int` retourné par la méthode `Query.executeUpdate()` indique le nombre d'entités affectées par l'opération. Considérez que cela peut ou non, corréler le nombre de lignes affectées dans la base de données. Une opération HQL pourrait entraîner l'exécution de multiples expressions SQL réelles, pour des classes filles mappées par jointure (join-subclass), par exemple. Le nombre retourné indique le nombre d'entités réelles affectées par l'expression. Si on revient à l'exemple de la classe fille mappée par jointure, un effacement d'une des classes filles peut réellement entraîner des suppressions pas seulement dans la table à laquelle la classe fille est mappée, mais également dans la table "racine" et potentillement dans les tables des classes filles plus bas dans la hiérarchie d'héritage.

La pseudo-syntaxe pour l'expression `INSERT` est : `INSERT INTO EntityName properties_list select_statement`. Quelques points sont à noter :

- Seule la forme INSERT INTO ... SELECT ... est supportée ; pas la forme INSERT INTO ... VALUES ... .

  La properties_list est analogue à la `column speficiation` dans la déclaration SQL `INSERT`. Pour les entités impliquées dans un héritage mappé, seules les propriétés directement définies à ce niveau de classe donné peuvent être utilisées dans properties_list. Les propriétés de la classe mère ne sont pas permises ; et les propriétés des classes filles n'ont pas de sens. En d'autres termes, les expressions `INSERT` sont par nature non polymorphiques.

- select_statement peut être n'importe quelle requête de sélection HQL valide, avec l'avertissement que les types de retour doivent correspondre aux types attendus par l'insertion. Actuellement, cela est vérifié durant la compilation de la requête plutôt que de reléguer la vérification à la base de données. Notez cependant que cela pourrait poser des problèmes entre les `Type`s de Hibernate qui sont équivalents contrairement à égaux. Cela pourrait poser des problèmes avec des disparités entre une propriété définie comme un `org.hibernate.type.DateType` et une propriété définie comme un `org.hibernate.type.TimestampType`, bien que la base de données ne fasse pas de distinction ou ne soit pas capable de gérer la conversion.

- Pour la propriété id, l'expression d'insertion vous donne deux options. Vous pouvez soit spécifier explicitement la propriété id dans properties_list (auquel cas sa valeur est extraite de l'expression de sélection correspondante), soit l'omettre de properties_list (auquel cas une valeur générée est utilisée). Cette dernière option est seulement disponible si vous utilisez le générateur d'identifiant qui opère dans la base de données ; tenter d'utiliser cette option avec n'importe quel type de générateur "en mémoire" causera une exception durant l'analyse. Notez que pour les buts de cette discussion, les générateurs "en base" sont considérés comme `org.hibernate.id.SequenceGenerator` (et ses classes filles) et n'importe quelles implémentations de `org.hibernate.id.PostInsertIdentifierGenerator`. L'exception la plus notable ici est `org.hibernate.id.TableHiLoGenerator`, qui ne peut pas être utilisée parce qu'elle ne propose pas de moyen d'obtenir ses valeurs par un select.

- Pour des propriétés mappées comme `version` ou `timestamp`, l'expression d'insertion vous donne deux options. Vous pouvez soit spécifier la propriété dans properties_list (auquel cas sa valeur est extraite des expressions select correspondantes), soit l'omettre de properties_list (auquel cas la `seed value` définie par le `org.hibernate.type.VersionType` est utilisée).

Un exemple d'exécution d'une expression 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();

Copyright © 2004 Red Hat, Inc.

- PrécédentChapitre 14. Intercepteurs et événements
- Niveau supérieur
- Sommaire
- SuivantChapitre 16. HQL : langage d'interrogation d'Hibe...