web-dev-qa-db-fra.com

Insert massif avec JPA + Hibernate

Je dois faire une insertion massive en utilisant EJB 3, Hibernate, Spring Data et Oracle. A l'origine, j'utilise Spring Data et le code est ci-dessous:

talaoAITDAO.save(taloes);

Où talaoAITDAO est une source de données JpaRepository subclass et taloes est une entité de la collection TalaoAIT. Dans cette entité, son identifiant respectif a cette forme:

@Id
@Column(name = "ID_TALAO_AIT")
@SequenceGenerator(name = "SQ_TALAO_AIT", sequenceName = "SQ_TALAO_AIT", allocationSize = 1000)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SQ_TALAO_AIT")
private Long id;

De plus, cette entité n'a pas d'entités associées pour effectuer une insertion en cascade.

Mon problème ici est que toutes les entités sont insérées individuellement (comme INSERT INTO TABLE(col1, col2) VALUES (val1, val2)). Parfois, cela peut provoquer un délai d'attente et toutes les insertions seront annulées. Je voudrais convertir ces inserts individuels en inserts par lots (tels que INSERT INTO TABLE(col1, col2) VALUES (val11, val12), (val21, val22), (val31, val32), ...).

En étudiant des alternatives pour améliorer les performances, j’ai trouvé cette page dans la documentation d’Hibernate, au-delà de La confusion dans la taille du lot d’Hibernate et cette autre page . Basé sur eux, j'ai écrit ce code:

Session session = super.getEntityManager().unwrap(Session.class);
int batchSize = 1000;
for (int i = 0; i < taloes.size(); i++) {
    TalaoAIT talaoAIT = taloes.get(i);
    session.save(talaoAIT);
    if(i % batchSize == 0) {
        session.flush();
        session.clear();
    }
    taloes.add(talaoAIT);
}
session.flush();
session.clear();

De plus, dans peristence.xml, j'ai ajouté les propriétés suivantes:

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

Cependant, bien que mes tests aient révélé une différence subtile (principalement avec de grandes collections et de grandes tailles de lots), elle n’était pas aussi grande que souhaitable. Dans la console de journalisation, j'ai constaté qu'Hibernate continuait à effectuer des insertions individuelles, sans les remplacer par des insertions massives. Comme dans mon entité, j'utilise un générateur de séquence. Je crois que ce n'est pas un problème (d'après la documentation d'Hibernate, j'aurais eu un problème si j'utilisais un générateur d'identité).

Donc, ma question est ce qui peut manquer ici. Une configuration? Une méthode non utilisée?

Merci,

Rafael Afonso.

11
Rafael Afonso

Un certain nombre de choses. 

D'abord, vos propriétés de configuration sont incorrectes order_inserts doit être hibernate.order_inserts. Actuellement, votre paramètre est ignoré et vous n'avez rien changé. 

Ensuite, utilisez la variable EntityManager au lieu de faire toutes ces choses vicieuses en veille prolongée. EntityManager a également une méthode flush et clear. Cela devrait au moins nettoyer votre méthode. Sans la commande, cela aide un peu à nettoyer la session et à éviter les contrôles de vérification sur tous les objets qui y sont.

EntityManager em = getEntityManager();
int batchSize = 1000;
for (int i = 0; i < taloes.size(); i++) {
    TalaoAIT talaoAIT = taloes.get(i);
    em.persist(talaoAIT);
    if(i % batchSize == 0) {
        em.flush();
        em.clear();
    }
    taloes.add(talaoAIT);
}
em.flush();
em.clear();

Ensuite, vous ne devriez pas trop grossir vos lots car cela pourrait causer des problèmes de mémoire, commencez par 50 et testez ce qui fonctionne le mieux. Il y a un moment où la vérification en profondeur va prendre plus de temps que la vidange et l'effacement dans la base de données. Vous voulez trouver cet endroit idéal.

15
M. Deinum

La solution publiée par M. Deinum a très bien fonctionné pour moi, à condition que je mette les propriétés Hibernate suivantes dans mon fichier JPA persistence.xml:

<property name="hibernate.jdbc.batch_size" value="50" />
<property name="hibernate.jdbc.batch_versioned_data" value="true" />
<property name="hibernate.order_inserts" value="true" />
<property name="hibernate.order_updates" value="true" />
<property name="hibernate.cache.use_second_level_cache" value="false" />
<property name="hibernate.connection.autocommit" value="false" />

J'utilise une base de données Oracle. J'ai donc également défini celle-ci:

<property name="hibernate.dialect" value="org.hibernate.dialect.Oracle10gDialect" />
1
Jim Tough

J'ai récemment trouvé une petite bibliothèque prometteuse pour la mise en lots d'insertions avec Hibernate et Postgresql. Il s’appelle pedal-dialect et utilise la commande COPY de Postgresql qui, selon de nombreuses personnes, est beaucoup plus rapide que les insertions groupées (références: Manuel Postgresql , Stratégies d’insertion Postgresql - Test de performance , Comment fonctionne la copie et pourquoi est-elle tellement plus rapide que l’insertion? ). pedal-dialect permet d’utiliser COPY sans perdre totalement la facilité d’utilisation de Hibernate. Vous obtenez toujours un mappage automatique des entités et des lignes et n'avez pas à l'implémenter vous-même. 

0
mm759