2010-05-05 21 views
22

Este es un ejemplo simple que he creado después de leer varios temas sobre las inserciones a granel de jpa, tengo 2 objetos persistentes Usuario y Sitio. Un usuario podría tener muchos sitios, por lo que tenemos una relación entre muchos aquí. Supongamos que quiero crear un usuario y crear/vincular varios sitios a la cuenta de usuario. Así es como se ve el código, considerando que estoy dispuesto a usar la inserción masiva para los objetos del Sitio.Inserción masiva de JPA/Hibernate (lote)

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

Pero cuando corro el código (estoy usando Hibernate como proveedor de la implementación JPA) Veo siguiente salida 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() 

Por lo tanto, significa "real" mayor inserción no funciona o me ¿estoy confundido?

Aquí está source code para este proyecto de ejemplo, este es el proyecto de maven así que solo tiene que descargar y ejecutar mvn install para verificar la salida.

Actualizado:

Después de Ken Liu aconseje amablemente, He desactivado objeto de sitio generación de auto ID:

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

Ahora tienen siguiente línea en la salida de depuración:

DEBUG: org. hibernate.jdbc.AbstractBatcher - Ejecutando tamaño de lote: 2

¡Funciona!

Respuesta

19

Si está utilizando la base de datos para generar identificadores, Hibernate debe ejecutar una consulta para generar la clave principal para cada entidad.

+0

¡Oh, esto tiene sentido! Gracias – abovesun

+0

He actualizado la pregunta, ¿podría echarle un vistazo, gracias – abovesun

+5

cómo está generando sus claves ahora? Deberá asegurarse de que sus claves sean únicas. –

5

He encontrado que es mucho más eficiente evitar la hibernación para inserciones masivas. Debe eliminar ORM (asignación relacional de objetos) pero aún puede aprovechar la conexión asociada con la sesión actual y la gestión de transacciones.

Mientras pierde temporalmente la conveniencia de su ORM, la recompensa es significativa, especialmente si ha generado Ids de forma nativa ya que hibernate normalmente realizaría un SELECT por cada INSERT.

Session.doWork es muy útil para facilitar esto.

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); 
    } 
} 
+1

Estoy usando la misma técnica que me proporcionó. Pero todavía hay un problema; este método prepara una declaración diferente para cada inserción, ya que se puede mostrar en sql profiler. Para aumentar el rendimiento que necesita para compilar o preparar una declaración una vez, llame a esa declaración compilada para el resto de las inserciones. – Max

Cuestiones relacionadas