2011-04-13 25 views
15

Necesito procesar un archivo CSV y para cada registro (línea) persistir una entidad. En este momento, lo hago de esta manera:Cómo persistir muchas entidades (JPA)

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

donde el método save(Entity) es básicamente una llamada EntityManager.merge(). Hay aproximadamente 20,000 entidades (líneas) en el archivo CSV. ¿Es esta una manera efectiva de hacerlo? Parece ser bastante lento. ¿Sería mejor usar EntityManager.persist()? ¿Esta solución está defectuosa de alguna manera?

EDITAR

Es un proceso largo (más de 400s) y yo trataba de ambas soluciones, con persist y merge. Ambos tardan aproximadamente el mismo tiempo en completarse (459s frente a 443s). La pregunta es si guardar las entidades una por una como esta es óptimo. Por lo que sé, Hibernate (que es mi proveedor de JPA) implementa algunas funciones de caché/descarga, por lo que no debería tener que preocuparse por esto.

Respuesta

11

La API JPA no proporciona todas las opciones para que esto sea óptimo. Dependiendo de qué tan rápido quiera hacer esto, tendrá que buscar opciones específicas de ORM: Hibernate en su caso.

Lo que debe verificar:

  1. Compruebe que está utilizando una sola transacción (Sí, al parecer, que está seguro de esto)
  2. Compruebe su proveedor JPA (Hibernate) está utilizando la API de lote JDBC (se refieren: hibernate.jdbc.batch_size)
  3. Comprobar si se puede pasar por alto obtener claves generadas (depende del controlador db/JDBC cuánto beneficio que se obtiene de esto - se refieren: hibernate.jdbc.use_getGeneratedKeys)
  4. Comprobar si se puede pasar por alto la lógica en cascada (solo beneficio de rendimiento mínimo de esto)

Así que en Ebean ORM esto sería:

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

Ah, y si lo hace a través de JDBC en bruto se salta el ORM en cabeza (menos la creación de objetos de recogida/basura, etc) - por lo que wouldn' t ignora esa opción.

Así que sí, esto no responde a su pregunta pero podría ayudarlo a buscar más ajustes de inserción de lote específicos de ORM.

+0

Puede verificar hibernate.jdbc.batch_size e hibernate.jdbc.use_getGeneratedKeys (pero no se puede establecer por transacción). –

3

Puede escribirlos con un clásico SQL Insert Statement directamente en la base de datos.

@see EntityManager.createNativeQuery

+2

gracias por votar, pero ¿por qué? – Ralph

+1

En este caso particular, las consultas nativas no proporcionarán mucha velocidad. Todo lo que puede hacer es agruparlos con lotes, lo que puede hacer a nivel de proveedor JPA o nivel de controlador JDBC. Sin embargo, en mi caso particular, puedo usar INSERT INTO ... SELECT FROM ... combo, que sería una gran velocidad, así que tengo mi +1. –

5

Creo que una forma común de hacerlo es con las transacciones. Si comienza una nueva transacción y luego persiste una gran cantidad de objetos, no se insertarán en el DB hasta que no haya confirmado la transacción. Esto puede obtener algunas eficiencias si tiene una gran cantidad de elementos para comprometer.

Salida EntityManager.getTransaction

+1

Se ejecuta en una transacción (usando Spring @Transactional). –

+0

Puede intentar eliminar la anotación y ver si el rendimiento cambia. También puede confirmar que está utilizando un solo golpe configurando un punto de quiebre y luego de que se haya ejecutado un número de llamadas perist controle la base de datos para confirmar que las filas no están insertadas aún. Puede ser que la primavera se comprometa después de 10 o 100 o más llamadas y hay algunos ajustes que puede hacer para alterar el rendimiento. – dough

3

para hacer que vaya más rápido, por lo menos en hibernación, que haría un flush() y un claro() después de un cierto número de inserciones. He hecho este enfoque para millones de registros y funciona. Todavía es lento, pero es mucho más rápido que no hacerlo. La estructura básica es la siguiente:

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

    dao.save(thingy.toModel()) 

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

} 
Cuestiones relacionadas