Inserto JPA / Hibernate bulk (batch)

Ecco un semplice esempio che ho creato dopo aver letto diversi argomenti sugli inserimenti di massa jpa, ho 2 oggetti persistenti Utente e Sito. Un utente potrebbe avere molti siti, quindi qui abbiamo uno o più rapporti. Supponiamo di voler creare un utente e creare / colbind diversi siti all’account utente. Ecco come appare il codice, considerando la mia volontà di utilizzare l’inserimento di massa per gli oggetti del sito.

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

Ma quando eseguo questo codice (sto usando Hibernate come provider di implementazione di jpa) vedo il seguente output di SQL:

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

Quindi, io intendo l’inserto di massa “reale” non funziona o sono confuso?

Ecco il codice sorgente per questo progetto di esempio, questo è il progetto maven in modo da avere solo scaricare ed eseguire mvn install per controllare l’output.

AGGIORNAMENTO:

Dopo Ken Liu gentilmente di avvisare, ho disabilitato la generazione automatica degli ID degli oggetti del sito:

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

Ora ho la seguente riga in output di debug:

DEBUG: org.hibernate.jdbc.AbstractBatcher – Esecuzione della dimensione del batch: 2

Funziona!

Se stai utilizzando il database per generare id, Hibernate deve eseguire una query per generare la chiave primaria per ogni quadro.

Ho scritto un breve blog che parla dei trucchi dell’inserto batch e ha anche un puntatore al piccolo progetto che ha tutte le giuste configurazioni per iniziare con l’inserimento batch con Hibernate. Vedi i dettagli su http://sensiblerationalization.blogspot.com/2011/03/quick-tip-on-hibernate-batch-operation.html

Ho trovato molto più efficiente bypassare l’ibernazione per gli inserimenti di massa. È necessario eliminare l’ORM (mapping relazionale dell’object) ma è comunque ansible sfruttare la connessione associata alla sessione corrente e alla gestione delle transazioni.

Mentre perdi temporaneamente la convenienza del tuo ORM, il guadagno è significativo, specialmente se hai ID generati in modo nativo poiché l’ibernazione normalmente eseguirà un SELECT per ogni INSERT .

Session.doWork è molto utile per facilitare questo.

 private MyParentObject saveMyParentObject(final MyParentObject parent, final List 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); } }