web-dev-qa-db-fra.com

Insertion en bloc ou mise à jour avec Hibernate?

J'ai besoin de consommer une assez grande quantité de données d'un fichier CSV quotidien. Le fichier CSV contient environ 120 000 enregistrements. Cela ralentit considérablement lorsque vous utilisez le mode hibernation. Fondamentalement, il semble que l'hibernation effectue un SELECT avant chaque INSERT (ou UPDATE) lors de l'utilisation de saveOrUpdate (); Pour chaque instance persistante avec saveOrUpdate (), un SELECT est émis avant le véritable INSERT ou un UPDATE. Je peux comprendre pourquoi il fait cela, mais c'est terriblement inefficace pour le traitement en vrac, et je cherche des alternatives

Je suis convaincu que le problème de performances réside dans la façon dont j'utilise l'hibernation pour cela, car une autre version fonctionne avec du SQL natif (qui analyse le CSV de la même manière) et ses cercles qui tournent littéralement autour de cette nouvelle version.

Donc, à la vraie question, existe-t-il une alternative hibernate à la syntaxe mysqls "INSERT ... ON DUPLICATE"?

Ou, si je choisis de faire du SQL natif pour cela, puis-je le faire en SQL natif dans une transaction hibernate? Cela signifie-t-il qu'il prend en charge les validations/annulations?

19
JustDanyul

Selon une réponse à une question similaire , il est possible de configurer Hibernate d'insérer des objets à l'aide d'une procédure stockée personnalisée qui utilise la fonctionnalité upsert de votre base de données. Ce n'est pas joli, cependant.

5
Tom Anderson

Il existe de nombreux goulots d'étranglement possibles dans les opérations en vrac. La meilleure approche dépend fortement de l'apparence de vos données. Consultez la section Manuel Hibernate sur le traitement par lots.

Au minimum, assurez-vous que vous utilisez le modèle suivant (copié du manuel):

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

Si vous mappez un fichier plat sur un graphe d'objet très complexe, vous devrez peut-être faire preuve de plus de créativité, mais le principe de base consiste à trouver un équilibre entre le transfert d'éléments volumineux de données dans la base de données à chaque vidage/validation et l'évitement exploser la taille du cache de niveau de session.

Enfin, si vous n'avez pas besoin que Hibernate gère les collections ou les cascades pour que vos données soient correctement insérées, envisagez d'utiliser un StatelessSession .

31
jcwayne

De Traitement par lots d’Hibernate Pour la mise à jour, j’utilisais les éléments suivants:

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

ScrollableResults employeeCursor = session.createQuery("FROM EMPLOYEE")
                                   .scroll();
int count = 0;

while ( employeeCursor.next() ) {
   Employee employee = (Employee) employeeCursor.get(0);
   employee.updateEmployee();
   seession.update(employee); 
   if ( ++count % 50 == 0 ) {
      session.flush();
      session.clear();
   }
}
tx.commit();
session.close();

Mais pour insérer je voudrais pour jcwayne répondre

3
shareef

Si vous utilisez un générateur de séquence ou natif, Hibernate utilisera un select pour obtenir l'identifiant

<id name="id" column="ID">
    <generator class="native" />
</id>

Vous devriez utiliser le générateur hilo ou seqHiLo:

<id name="id" type="long" column="id">  
    <generator class="seqhilo">
        <param name="sequence">SEQ_NAME</param>
        <param name="max_lo">100</param>
    </generator>
</id>
1
Gabriel

Si vous souhaitez uniquement importer des données sans traitement ni transformation, un outil tel que PostgreSQL COPY constitue le moyen le plus rapide d’importer des données.

Cependant, si vous devez effectuer la transformation, l'agrégation des données, la corrélation/fusion entre les données existantes et les données entrantes, vous avez besoin du traitement par lots au niveau de l'application.

Dans ce cas, comme je l’ai expliqué dans cet article , vous souhaitez vider-effacer-engager régulièrement:

int entityCount = 50;
int batchSize = 25;

EntityManager entityManager = entityManagerFactory()
    .createEntityManager();

EntityTransaction entityTransaction = entityManager
    .getTransaction();

try {
    entityTransaction.begin();

    for (int i = 0; i < entityCount; i++) {
        if (i > 0 && i % batchSize == 0) {
            entityTransaction.commit();
            entityTransaction.begin();

            entityManager.clear();
        }

        Post post = new Post(
            String.format("Post %d", i + 1)
        );

        entityManager.persist(post);
    }

    entityTransaction.commit();
} catch (RuntimeException e) {
    if (entityTransaction.isActive()) {
        entityTransaction.rollback();
    }
    throw e;
} finally {
    entityManager.close();
}

Assurez-vous également d'activer le traitement par lots JDBC à l'aide des propriétés de configuration suivantes:

<property
    name="hibernate.jdbc.batch_size"
    value="25"
/>

<property
    name="hibernate.order_inserts"  
    value="true"
/>

<property
    name="hibernate.order_updates"  
    value="true"
/>

Pour plus de détails sur ces propriétés de configuration Hibernate, consultez cet article .

1
Vlad Mihalcea

La sélection "extra" consiste à générer l'identifiant unique de vos données.

Basculez sur la génération de séquence HiLo et vous pouvez réduire les allers-retours de séquences à la base de données du nombre de la taille d'allocation. Veuillez noter qu'il y aura un espace dans les clés primaires sauf si vous ajustez la valeur de votre séquence pour le générateur HiLo.

0
szucsz