web-dev-qa-db-fra.com

Insert en vrac (batch) JPA / Hibernate

Voici un exemple simple que j'ai créé après avoir lu plusieurs rubriques sur les insertions en masse jpa, j'ai 2 objets persistants utilisateur et site. Un utilisateur peut avoir plusieurs sites, nous avons donc une à plusieurs relations ici. Supposons que je veuille créer un utilisateur et créer/lier plusieurs sites au compte utilisateur. Voici à quoi ressemble le code, compte tenu de ma volonté d'utiliser l'insertion en bloc pour les objets Site.

User user = new User("John Doe");

user.getSites().add(new Site("google.com", user));
user.getSites().add(new Site("yahoo.com", user));

EntityTransaction tx = entityManager.getTransaction();
tx.begin();
entityManager.persist(user);
tx.commit();

Mais lorsque j'exécute ce code (j'utilise hibernate comme fournisseur d'implémentation jpa), je vois la sortie SQL suivante:

Hibernate: insert into User (id, name) values (null, ?)
Hibernate: call identity()
Hibernate: insert into Site (id, url, user_id) values (null, ?, ?)
Hibernate: call identity()
Hibernate: insert into Site (id, url, user_id) values (null, ?, ?)
Hibernate: call identity()

Donc, je veux dire que le "vrai" encart en vrac ne fonctionne pas ou je suis confus?

Voici code source pour cet exemple de projet, il s'agit du projet maven, vous n'avez donc qu'à télécharger et exécuter mvn install pour vérifier la sortie.

ACTUALISÉ:

Après que Ken Liu ait gentiment conseillé, j'ai désactivé la génération automatique de l'ID d'objet du site:

    User user = new User("John Doe");
    user.getSites().add(new Site(1, "google.com", user));
    user.getSites().add(new Site(2, "yahoo.com", user));
    entityManager.setFlushMode(FlushModeType.COMMIT);
    EntityTransaction tx = entityManager.getTransaction();
    tx.begin();
    entityManager.persist(user);
    tx.commit();

Maintenant, j'ai la ligne suivante dans la sortie de débogage:

DEBUG: org.hibernate.jdbc.AbstractBatcher - Exécution de la taille du lot: 2

Ça marche!

25
abovesun

Si vous utilisez la base de données pour générer des identifiants, Hibernate doit exécuter une requête pour générer la clé primaire pour chaque entité.

20
Ken Liu

J'ai trouvé qu'il était beaucoup plus efficace de contourner l'hibernation pour les insertions en vrac. Vous devez supprimer ORM (mappage relationnel objet) mais vous pouvez toujours tirer parti de la connexion associée à la session en cours et à la gestion des transactions.

Bien que vous perdiez temporairement la commodité de votre ORM, le gain est important, surtout si vous avez généré des identifiants natifs car hibernate effectuerait normalement un SELECT pour chaque INSERT.

Session.doWork est très pratique pour faciliter cela.

private MyParentObject saveMyParentObject(final MyParentObject parent, final List<MyChildObject> children)
{
    transaction = session.beginTransaction();
    try
    {
        session.save(parent); // NOTE: parent.parentId assigned and returned here

        session.doWork(new Work()
        {
            public void execute(Connection con) throws SQLException
            {
                // hand written insert SQL - can't use hibernate
                PreparedStatement st = con.prepareStatement("INSERT INTO my_child (parent_id, name, ...) values (?, ?, ...)");

                for (MyChildObject child : children)
                {
                    MyChildObject child = new MyChildObject();
                    child.setParentId(parent.getParentId()); // assign parent id for foreign key

                    // hibernate can't help, determine jdbc parameters manually
                    st.setLong(1, child.getParentId());
                    st.setString(2, child.getName());
                    ...
                    st.addBatch();
                }

                // NOTE: you may want to limit the size of the batch
                st.executeBatch();
            }
        });

        // if your parent has a OneToMany relationship with child(s), refresh will populate this 
        session.refresh(parent);
        transaction.commit();
        return parent;
    }
    catch(Throwable e)
    {
        transaction.rollback();
        throw new RuntimeException(e);
    }   
}
6
pstanton

J'ai écrit un court blog qui parle de gotchas d'insertion par lots et a également un pointeur vers un petit projet qui a toutes les bonnes configurations pour commencer avec l'insertion de lots avec Hibernate. Voir les détails sur http://sensiblerationalization.blogspot.com/2011/03/quick-tip-on-hibernate-batch-operation.html

5
prabhat jha