web-dev-qa-db-fra.com

Comment conserver un grand nombre d'entités (JPA)

Je dois traiter un fichier CSV et pour chaque enregistrement (ligne) persister une entité. En ce moment, je le fais de cette façon:

while ((line = reader.readNext()) != null) {
    Entity entity = createEntityObject(line);
    entityManager.save(entity);
    i++;
}

où la méthode save(Entity) est essentiellement juste un appel EntityManager.merge(). Il y a environ 20 000 entités (lignes) dans le fichier CSV. Est-ce un moyen efficace de le faire? Cela semble être assez lent. Serait-il préférable d'utiliser EntityManager.persist()? Cette solution est-elle en quelque sorte défectueuse?

MODIFIER

C'est un long processus (plus de 400 s) et j'ai essayé les deux solutions, avec persist et merge. Les deux prennent à peu près le même temps (459 vs 443). La question est de savoir si la sauvegarde des entités une par une comme celle-ci est optimale. Autant que je sache, Hibernate (qui est mon fournisseur JPA) implémente certaines fonctionnalités de cache/vidage, donc je ne devrais pas avoir à m'inquiéter à ce sujet.

16
John Manak

L'API JPA ne vous fournit pas toutes les options pour rendre cela optimal. En fonction de la vitesse à laquelle vous souhaitez le faire, vous devrez rechercher des options spécifiques à ORM - Hibernate dans votre cas.

Choses à vérifier:

  1. Vérifiez que vous utilisez une seule transaction (Oui, apparemment vous en êtes sûr)
  2. Vérifiez que votre fournisseur JPA (Hibernate) utilise l'API JDBC batch (voir: hibernate.jdbc.batch_size)
  3. Vérifiez si vous pouvez contourner l'obtention des clés générées (dépend du pilote db/jdbc l'avantage que vous en retirez - reportez-vous à: hibernate.jdbc.use_getGeneratedKeys)
  4. Vérifiez si vous pouvez contourner la logique en cascade (seul un avantage minimal en termes de performances)

Donc, dans Ebean ORM, ce serait:

    EbeanServer server = Ebean.getServer(null);

    Transaction transaction = server.beginTransaction();
    try {
        // Use JDBC batch API with a batch size of 100
        transaction.setBatchSize(100);
        // Don't bother getting generated keys
        transaction.setBatchGetGeneratedKeys(false);
        // Skip cascading persist 
        transaction.setPersistCascade(false);

        // persist your beans ...
        Iterator<YourEntity> it = null; // obviously should not be null 
        while (it.hasNext()) {
            YourEntity yourEntity = it.next();
            server.save(yourEntity);
        }

        transaction.commit();
    } finally {
        transaction.end();
    }

Oh, et si vous faites cela via JDBC brut, vous sautez la surcharge ORM (moins de création d'objets/garbage collection, etc.) - donc je n'ignorerais pas cette option.

Donc oui, cela ne répond pas à votre question, mais pourrait vous aider à rechercher plus d'ajustements d'insertion de lots spécifiques à l'ORM.

12
Rob Bygrave

Je pense qu'une façon courante de procéder consiste à effectuer des transactions. Si vous commencez une nouvelle transaction et que vous persistez ensuite un grand nombre d'objets, ils ne seront pas réellement insérés dans la base de données tant que vous n'aurez pas validé la transaction. Cela peut vous permettre de gagner en efficacité si vous avez un grand nombre d'éléments à engager.

Vérifiez EntityManager.getTransaction

5
dough

Pour le rendre plus rapide, au moins dans Hibernate, vous feriez un flush () et un clear () après un certain nombre d'insertions. J'ai fait cette approche pour des millions de disques et ça marche. C'est encore lent, mais c'est beaucoup plus rapide que de ne pas le faire. La structure de base est comme ceci:

int i = 0;
for(MyThingy thingy : lotsOfThingies) {

    dao.save(thingy.toModel())

    if(++i % 20 == 0) {
        dao.flushAndClear();
    }

}
3
egervari

Vous pouvez les écrire avec une instruction d'insertion SQL classique directement dans la base de données.

@see EntityManager.createNativeQuery

3
Ralph