Spring Data JPA: inserción por lotes para entidades anidadas

Tengo un caso de prueba en el que necesito persistir 100 000 instancias de entidad en la base de datos. El código que estoy usando lo hace, pero demora hasta 40 segundos hasta que todos los datos se conservan en la base de datos. Los datos se leen desde un archivo JSON de aproximadamente 15 MB de tamaño.

Ahora ya había implementado un método de inserción por lotes en un repository personalizado antes para otro proyecto. Sin embargo, en ese caso tuve muchas entidades de nivel superior para persistir, con solo unas pocas entidades anidadas.

En mi caso actual, tengo 5 entidades de Job que contienen una lista de aproximadamente ~ 30 entidades JobDetail . One JobDetail contiene entre 850 y 1100 entidades JobEnvelope .

Al escribir en la base de datos, confirmo la Lista de entidades de Job con el método de interfaz predeterminado para save(Iterable jobs) . Todas las entidades anidadas tienen CascadeType PERSIST. Cada entidad tiene su propia tabla.

La forma habitual de habilitar las inserciones por lotes sería implementar un método personalizado como saveBatch que se saveBatch vez en cuando. Pero mi problema en este caso son las entidades JobEnvelope . No los JobEnvelope con un repository JobEnvelope , sino que dejo que el repository de la entidad Job maneje. Estoy usando MariaDB como servidor de base de datos.

Así que mi pregunta se reduce a lo siguiente: ¿Cómo puedo hacer que JobRepository inserte sus entidades anidadas en lotes?

Estas son mis 3 cualidades en cuestión:

Trabajo

 @Entity public class Job { @Id @GeneratedValue private int jobId; @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST, mappedBy = "job") @JsonManagedReference private Collection jobDetails; } 

Detalles del trabajo

 @Entity public class JobDetail { @Id @GeneratedValue private int jobDetailId; @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST) @JoinColumn(name = "jobId") @JsonBackReference private Job job; @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST, mappedBy = "jobDetail") @JsonManagedReference private List jobEnvelopes; } 

JobEnvelope

 @Entity public class JobEnvelope { @Id @GeneratedValue private int jobEnvelopeId; @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST) @JoinColumn(name = "jobDetailId") private JobDetail jobDetail; private double weight; } 

Asegúrese de configurar correctamente las propiedades relacionadas con los lotes de Hibernate:

 100 true true 

El punto es que las declaraciones sucesivas se pueden agrupar si manipulan la misma tabla. Si llega la instrucción haciendo insertar a otra tabla, la construcción del lote anterior debe ser interrumpida y ejecutada antes de esa statement. Con la propiedad hibernate.order_inserts , está dando permiso a Hibernate para reordenar inserciones antes de construir las instrucciones de lote ( hibernate.order_updates tiene el mismo efecto para las declaraciones de actualización).

jdbc.batch_size es el tamaño máximo de lote que utilizará Hibernate. Pruebe y analice diferentes valores y elija uno que muestre el mejor rendimiento en sus casos de uso.

Tenga en cuenta que el procesamiento por lotes de instrucciones de inserción está deshabilitado si se utiliza el generador de IDENTITY .

Específico para MySQL, debe especificar rewriteBatchedStatements=true como parte de la URL de conexión. Para asegurarse de que el procesamiento por lotes funciona como se espera, agregue profileSQL=true para inspeccionar el SQL que el controlador envía a la base de datos. Más detalles aquí .

Si sus entidades están versionadas (con fines de locking optimista), entonces para utilizar las actualizaciones por lotes (no afecta las inserciones) deberá activar también:

 true 

Con esta propiedad le dice a Hibernate que el controlador JDBC es capaz de devolver el recuento correcto de las filas afectadas al ejecutar la actualización por lotes (necesaria para realizar la verificación de la versión). Debe comprobar si esto funciona correctamente para su controlador de base de datos / jdbc. Por ejemplo, no funciona en Oracle 11 y versiones anteriores de Oracle.

Es posible que también desee vaciar y borrar el contexto de persistencia después de cada lote para liberar memoria, de lo contrario, todos los objetos administrados permanecerán en el contexto de persistencia hasta que se cierre.

Además, puede encontrar útil este blog, ya que explica muy bien los detalles del mecanismo de procesamiento por lotes de Hibernate.