Skip to content

Commit ca0e4ff

Browse files
committed
Batch inserts (including associations) batch per transaction
1 parent 13d504a commit ca0e4ff

14 files changed

+496
-0
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
**[How To Optimize Batch Inserts of Parent-Child Relationships In MySQL](https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootBatchInsertOrder)**
2+
3+
**Description:** Let's suppose that we have a one-to-many relationship between `Author` and `Book` entities. When we save an author, we save his books as well thanks to cascading all/persist. We want to create a bunch of authors with books and save them in the database (e.g., a MySQL database) using the batch technique. By default, this will result in batching each author and the books per author (one batch for the author and one batch for the books, another batch for the author and another batch for the books, and so on). In order to batch authors and books, we need to **order inserts** as in this application.
4+
5+
**Key points:**
6+
Beside all setting specific to batching inserts in MySQL, we need to set up in `application.properties` the following property: `spring.jpa.properties.hibernate.order_inserts=true`
7+
8+
**Example without ordered inserts:**\
9+
![](https://github.com/AnghelLeonard/Hibernate-SpringBoot/blob/master/HibernateSpringBootBatchInsertOrder/batch%20inserts%20including%20associations%20no%20order%20of%20inserts.png)
10+
11+
**Example with ordered inserts:**\
12+
![](https://github.com/AnghelLeonard/Hibernate-SpringBoot/blob/master/HibernateSpringBootBatchInsertOrder/batch%20inserts%20including%20associations%20ordered%20inserts.png)
13+
14+
-------------------------------
15+
16+
**You may like to try as well:**
17+
<a href="https://leanpub.com/java-persistence-performance-illustrated-guide"><p align="center"><img src="https://github.com/AnghelLeonard/Hibernate-SpringBoot/blob/master/Java%20Persistence%20Performance%20Illustrated%20Guide.jpg" height="410" width="350"/></p></a>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<groupId>com.jpa</groupId>
7+
<artifactId>HibernateSpringBootBatchInsertOrderBatchPerTransaction</artifactId>
8+
<version>1.0</version>
9+
<packaging>jar</packaging>
10+
11+
<name>HibernateSpringBootBatchInsertOrderBatchPerTransaction</name>
12+
<description>JPA project for Spring Boot</description>
13+
14+
<parent>
15+
<groupId>org.springframework.boot</groupId>
16+
<artifactId>spring-boot-starter-parent</artifactId>
17+
<version>2.1.4.RELEASE</version>
18+
<relativePath/> <!-- lookup parent from repository -->
19+
</parent>
20+
21+
<properties>
22+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
23+
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
24+
<java.version>1.8</java.version>
25+
<datasource-proxy.version>1.4.9</datasource-proxy.version>
26+
</properties>
27+
28+
<dependencies>
29+
<dependency>
30+
<groupId>org.springframework.boot</groupId>
31+
<artifactId>spring-boot-starter-data-jpa</artifactId>
32+
</dependency>
33+
<dependency>
34+
<groupId>org.springframework.boot</groupId>
35+
<artifactId>spring-boot-starter-jdbc</artifactId>
36+
</dependency>
37+
<dependency>
38+
<groupId>org.springframework.boot</groupId>
39+
<artifactId>spring-boot-starter-web</artifactId>
40+
</dependency>
41+
<dependency>
42+
<groupId>mysql</groupId>
43+
<artifactId>mysql-connector-java</artifactId>
44+
<scope>runtime</scope>
45+
</dependency>
46+
<dependency>
47+
<groupId>org.springframework.boot</groupId>
48+
<artifactId>spring-boot-starter-test</artifactId>
49+
<scope>test</scope>
50+
</dependency>
51+
</dependencies>
52+
53+
<build>
54+
<plugins>
55+
<plugin>
56+
<groupId>org.springframework.boot</groupId>
57+
<artifactId>spring-boot-maven-plugin</artifactId>
58+
</plugin>
59+
</plugins>
60+
</build>
61+
62+
63+
</project>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.bookstore;
2+
3+
import com.bookstore.impl.BatchRepositoryImpl;
4+
import com.bookstore.service.BookstoreService;
5+
import org.springframework.boot.ApplicationRunner;
6+
import org.springframework.boot.SpringApplication;
7+
import org.springframework.boot.autoconfigure.SpringBootApplication;
8+
import org.springframework.context.annotation.Bean;
9+
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
10+
11+
@SpringBootApplication
12+
@EnableJpaRepositories(repositoryBaseClass = BatchRepositoryImpl.class)
13+
public class MainApplication {
14+
15+
private final BookstoreService bookstoreService;
16+
17+
public MainApplication(BookstoreService bookstoreService) {
18+
this.bookstoreService = bookstoreService;
19+
}
20+
21+
public static void main(String[] args) {
22+
SpringApplication.run(MainApplication.class, args);
23+
}
24+
25+
@Bean
26+
public ApplicationRunner init() {
27+
return args -> {
28+
bookstoreService.batchAuthorsAndBooks();
29+
};
30+
}
31+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package com.bookstore.entity;
2+
3+
import java.io.Serializable;
4+
import java.util.ArrayList;
5+
import java.util.Iterator;
6+
import java.util.List;
7+
import javax.persistence.CascadeType;
8+
import javax.persistence.Entity;
9+
import javax.persistence.Id;
10+
import javax.persistence.OneToMany;
11+
12+
@Entity
13+
public class Author implements Serializable {
14+
15+
private static final long serialVersionUID = 1L;
16+
17+
@Id
18+
private Long id;
19+
20+
private String name;
21+
private String genre;
22+
private int age;
23+
24+
@OneToMany(cascade = CascadeType.ALL,
25+
mappedBy = "author", orphanRemoval = true)
26+
private List<Book> books = new ArrayList<>();
27+
28+
public void addBook(Book book) {
29+
this.books.add(book);
30+
book.setAuthor(this);
31+
}
32+
33+
public void removeBook(Book book) {
34+
book.setAuthor(null);
35+
this.books.remove(book);
36+
}
37+
38+
public void removeBooks() {
39+
Iterator<Book> iterator = this.books.iterator();
40+
41+
while (iterator.hasNext()) {
42+
Book book = iterator.next();
43+
44+
book.setAuthor(null);
45+
iterator.remove();
46+
}
47+
}
48+
49+
public Long getId() {
50+
return id;
51+
}
52+
53+
public void setId(Long id) {
54+
this.id = id;
55+
}
56+
57+
public String getName() {
58+
return name;
59+
}
60+
61+
public void setName(String name) {
62+
this.name = name;
63+
}
64+
65+
public String getGenre() {
66+
return genre;
67+
}
68+
69+
public void setGenre(String genre) {
70+
this.genre = genre;
71+
}
72+
73+
public int getAge() {
74+
return age;
75+
}
76+
77+
public void setAge(int age) {
78+
this.age = age;
79+
}
80+
81+
public List<Book> getBooks() {
82+
return books;
83+
}
84+
85+
public void setBooks(List<Book> books) {
86+
this.books = books;
87+
}
88+
89+
@Override
90+
public String toString() {
91+
return "Author{" + "id=" + id + ", name=" + name
92+
+ ", genre=" + genre + ", age=" + age + '}';
93+
}
94+
95+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package com.bookstore.entity;
2+
3+
import java.io.Serializable;
4+
import javax.persistence.Entity;
5+
import javax.persistence.FetchType;
6+
import javax.persistence.Id;
7+
import javax.persistence.JoinColumn;
8+
import javax.persistence.ManyToOne;
9+
10+
@Entity
11+
public class Book implements Serializable {
12+
13+
private static final long serialVersionUID = 1L;
14+
15+
@Id
16+
private Long id;
17+
18+
private String title;
19+
private String isbn;
20+
21+
@ManyToOne(fetch = FetchType.LAZY)
22+
@JoinColumn(name = "author_id")
23+
private Author author;
24+
25+
public Long getId() {
26+
return id;
27+
}
28+
29+
public void setId(Long id) {
30+
this.id = id;
31+
}
32+
33+
public String getTitle() {
34+
return title;
35+
}
36+
37+
public void setTitle(String title) {
38+
this.title = title;
39+
}
40+
41+
public String getIsbn() {
42+
return isbn;
43+
}
44+
45+
public void setIsbn(String isbn) {
46+
this.isbn = isbn;
47+
}
48+
49+
public Author getAuthor() {
50+
return author;
51+
}
52+
53+
public void setAuthor(Author author) {
54+
this.author = author;
55+
}
56+
57+
@Override
58+
public boolean equals(Object obj) {
59+
60+
if (this == obj) {
61+
return true;
62+
}
63+
64+
if (getClass() != obj.getClass()) {
65+
return false;
66+
}
67+
68+
return id != null && id.equals(((Book) obj).id);
69+
}
70+
71+
@Override
72+
public int hashCode() {
73+
return 2021;
74+
}
75+
76+
@Override
77+
public String toString() {
78+
return "Book{" + "id=" + id + ", title=" + title + ", isbn=" + isbn + '}';
79+
}
80+
81+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.bookstore.impl;
2+
3+
import java.util.logging.Level;
4+
import java.util.logging.Logger;
5+
import javax.persistence.EntityManager;
6+
import javax.persistence.EntityManagerFactory;
7+
import javax.persistence.EntityTransaction;
8+
import org.springframework.beans.factory.annotation.Value;
9+
import org.springframework.stereotype.Component;
10+
11+
@Component
12+
public class BatchExecutor<T> {
13+
14+
private static final Logger logger = Logger.getLogger(BatchExecutor.class.getName());
15+
16+
@Value("${spring.jpa.properties.hibernate.jdbc.batch_size}")
17+
private int batchSize;
18+
19+
private final EntityManagerFactory entityManagerFactory;
20+
21+
public BatchExecutor(EntityManagerFactory entityManagerFactory) {
22+
this.entityManagerFactory = entityManagerFactory;
23+
}
24+
25+
public <S extends T> void saveInBatch(Iterable<S> entities) {
26+
27+
if (entities == null) {
28+
throw new IllegalArgumentException("The given Iterable of entities not be null!");
29+
}
30+
31+
EntityManager entityManager = entityManagerFactory.createEntityManager();
32+
EntityTransaction entityTransaction = entityManager.getTransaction();
33+
34+
try {
35+
entityTransaction.begin();
36+
37+
int i = 0;
38+
for (S entity : entities) {
39+
if (i % batchSize == 0 && i > 0) {
40+
logger.log(Level.INFO,
41+
"Flushing the EntityManager containing {0} entities ...", batchSize);
42+
43+
entityTransaction.commit();
44+
entityTransaction.begin();
45+
46+
entityManager.clear();
47+
}
48+
49+
entityManager.persist(entity);
50+
i++;
51+
}
52+
53+
logger.log(Level.INFO,
54+
"Flushing the remaining entities ...");
55+
56+
entityTransaction.commit();
57+
} catch (RuntimeException e) {
58+
if (entityTransaction.isActive()) {
59+
entityTransaction.rollback();
60+
}
61+
62+
throw e;
63+
} finally {
64+
entityManager.close();
65+
}
66+
}
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.bookstore.impl;
2+
3+
import java.io.Serializable;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
import org.springframework.data.repository.NoRepositoryBean;
6+
7+
@NoRepositoryBean
8+
public interface BatchRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
9+
10+
<S extends T> void saveInBatch(Iterable<S> entites);
11+
}

0 commit comments

Comments
 (0)