Hibernate/JPA Batch Insert and Batch Update Example
This tutorial shows how to create batch insert and batch update statements using JPA and Hibernate. JDBC offers support for batching together SQL statements that can be represented as a single PreparedStatement
. The JDBC driver will send the batched operation to the server in one call. This can potentially lead in a reduction to network bandwidth usage.
Maven Dependencies
We use Apache Maven to manage the projects dependencies.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.memorynotfound.db.hibernate</groupId>
<artifactId>jpa-named-stored-procedure</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>HIBERNATE - ${project.artifactId}</name>
<url>https://memorynotfound.com</url>
<properties>
<mysql.driver.version>6.0.4</mysql.driver.version>
<hibernate.version>5.2.3.Final</hibernate.version>
</properties>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.driver.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Create Model + Mappings
package com.memorynotfound.hibernate;
import javax.persistence.*;
@Entity
public class Book {
@Id
private Integer id;
private String title;
public Book() {
}
public Book(Integer id, String title) {
this.id = id;
this.title = title;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@Override
public String toString() {
return "Book{" +
"id=" + id +
", title='" + title + '\'' +
'}';
}
}
If you prefer XML over Annotations, you can use the equivalent JPA XML mapping. This file is located in the src/main/resources/META-INF
folder and is named orm.xml
.
<?xml version="1.0" encoding="UTF-8" ?>
<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm
http://xmlns.jcp.org/xml/ns/persistence/orm_2_0.xsd" version="2.1">
<!-- entity mapping -->
<entity class="com.memorynotfound.hibernate.Book">
<attributes>
<id name="id"/>
</attributes>
<attributes>
<basic name="title"/>
</attributes>
</entity>
</entity-mappings>
Hibernate/JPA Configuration
We configure the JPA Persistence Unit using the persistence.xml
file, which is located in the src/main/resources/META-INF
directory.
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="mnf-pu" transaction-type="RESOURCE_LOCAL">
<!-- enable if you want xml mappings
<mapping-file>META-INF/orm.xml</mapping-file>
-->
<properties>
<!-- Configuring JDBC properties -->
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/memorynotfound?serverTimezone=Europe/Brussels"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
<!-- Hibernate properties -->
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
<property name="hibernate.format_sql" value="false"/>
<property name="hibernate.show_sql" value="true"/>
<!-- batching size -->
<property name="hibernate.jdbc.batch_size" value="30"/>
<property name="hibernate.order_inserts" value="true"/>
<property name="hibernate.order_updates" value="true"/>
<property name="hibernate.jdbc.batch_versioned_data" value="true"/>
</properties>
</persistence-unit>
</persistence>
- hibernate.jdbc.batch_size Controls the maximum number of statements Hibernate will batch together before asking the driver to execute the batch. Zero or a negative number disables this feature.
- hibernate.jdbc.batch_versioned_data Some JDBC drivers return incorrect row counts when a batch is executed. If your JDBC driver falls into this category this setting should be set to false. Otherwise, it is safe to enable this which will allow Hibernate to still batch the DML for versioned entities and still use the returned row counts for optimistic lock checks. Since 5.0, it defaults to true. Previously (versions 3.x and 4.x), it used to be false.
- hibernate.order_updates Forces Hibernate to order SQL updates by the entity type and the primary key value of the items being updated. This allows for more batching to be used. It will also result in fewer transaction deadlocks in highly concurrent systems. Comes with a performance hit, so benchmark before and after to see if this actually helps or hurts your application.
- hibernate.order_insertsForces Hibernate to order inserts to allow for more batching to be used. Comes with a performance hit, so benchmark before and after to see if this actually helps or hurts your application.
Create, Run and Test
Use the flush()
and clear()
methods of the EntityManager
regularly, to control the size of the first-level cache. Otherwise an OutOfMemoryException
can occur when the maximum size is reached. To enable batch processing, it is important that you set the FlushMode
to FlushModeType.COMMIT
.
package com.memorynotfound.hibernate;
import javax.persistence.*;
import java.util.List;
public class App {
public static void main (String...args) throws InterruptedException {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("mnf-pu");
EntityManager em = emf.createEntityManager();
em.setFlushMode(FlushModeType.COMMIT);
int batchSize = 30;
// - - - - - - - - - - - - - - Hibernate/JPA Batch Insert example - - - - - - - - - - - -
em.getTransaction().begin();
for (int i = 0; i < 100; i++){
Book book = new Book(i, "Hibernate/JPA Batch Insert Example: " + i);
em.persist(book);
if (i % batchSize == 0 && i > 0) {
em.flush();
em.clear();
}
}
em.getTransaction().commit();
// - - - - - - - - - - - - - - Hibernate/JPA Batch Update example - - - - - - - - - - - -
List<Book> books = em.createQuery("From Book", Book.class).getResultList();
em.getTransaction().begin();
for (int i = 0; i < books.size(); i++){
Book book = books.get(i);
book.setTitle("Hibernate/JPA Batch Update Example: " + i);
book = em.merge(book);
em.persist(book);
if (i % batchSize == 0 && i > 0) {
em.flush();
em.clear();
}
}
em.getTransaction().commit();
emf.close();
}
}