Skip to content

Commit 60bb150

Browse files
committed
Batch inserts in Spring style and batch per transaction
1 parent 6d60ae9 commit 60bb150

File tree

12 files changed

+363
-0
lines changed

12 files changed

+363
-0
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
**[Batch Inserts In Spring Boot Style](https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootBatchInsertsSpringStyle)**
3+
4+
**Description:** Batch inserts (in MySQL) in Spring Boot style.
5+
6+
**Key points:**
7+
- in `application.properties` set `spring.jpa.properties.hibernate.jdbc.batch_size`
8+
- in `application.properties` set `spring.jpa.properties.hibernate.generate_statistics` (just to check that batching is working)
9+
- in `application.properties` set JDBC URL with `rewriteBatchedStatements=true` (optimization for MySQL)
10+
- in `application.properties` set JDBC URL with `cachePrepStmts=true` (enable caching and is useful if you decide to set `prepStmtCacheSize`, `prepStmtCacheSqlLimit`, etc as well; without this setting the cache is disabled)
11+
- in `application.properties` set JDBC URL with `useServerPrepStmts=true` (this way you switch to server-side prepared statements (may lead to signnificant performance boost))
12+
- in case of using a parent-child relationship with cascade persist (e.g. one-to-many, many-to-many) then consider to set up `spring.jpa.properties.hibernate.order_inserts=true` to optimize the batching by ordering inserts
13+
- in entity, use the [assigned generator](https://vladmihalcea.com/how-to-combine-the-hibernate-assigned-generator-with-a-sequence-or-an-identity-column/) since the Hibernate `IDENTITY` will cause insert batching to be disabled
14+
- if is not needed then ensure that Second Level Cache is disabled via `spring.jpa.properties.hibernate.cache.use_second_level_cache=false`
15+
16+
**Output example:**
17+
![](https://github.com/AnghelLeonard/Hibernate-SpringBoot/blob/master/HibernateSpringBootBatchInsertsSpringStyle/batch%20inserts%20in%20spring%20boot%20style.png)
18+
19+
-------------------------------
20+
21+
**You may like to try as well:**
22+
<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: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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>HibernateSpringBootBatchInsertsSpringStyleBatchPerTransaction</artifactId>
8+
<version>1.0</version>
9+
<packaging>jar</packaging>
10+
11+
<name>HibernateSpringBootBatchInsertsSpringStyleBatchPerTransaction</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+
<maven.compiler.source>12</maven.compiler.source>
26+
<maven.compiler.target>12</maven.compiler.target>
27+
</properties>
28+
29+
<dependencies>
30+
<dependency>
31+
<groupId>org.springframework.boot</groupId>
32+
<artifactId>spring-boot-starter-data-jpa</artifactId>
33+
</dependency>
34+
<dependency>
35+
<groupId>org.springframework.boot</groupId>
36+
<artifactId>spring-boot-starter-jdbc</artifactId>
37+
</dependency>
38+
<dependency>
39+
<groupId>org.springframework.boot</groupId>
40+
<artifactId>spring-boot-starter-web</artifactId>
41+
</dependency>
42+
<dependency>
43+
<groupId>mysql</groupId>
44+
<artifactId>mysql-connector-java</artifactId>
45+
<scope>runtime</scope>
46+
</dependency>
47+
<dependency>
48+
<groupId>org.springframework.boot</groupId>
49+
<artifactId>spring-boot-starter-test</artifactId>
50+
<scope>test</scope>
51+
</dependency>
52+
</dependencies>
53+
54+
<build>
55+
<plugins>
56+
<plugin>
57+
<groupId>org.springframework.boot</groupId>
58+
<artifactId>spring-boot-maven-plugin</artifactId>
59+
</plugin>
60+
</plugins>
61+
</build>
62+
</project>
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.batchAuthors();
29+
};
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.bookstore.entity;
2+
3+
import java.io.Serializable;
4+
import javax.persistence.Entity;
5+
import javax.persistence.Id;
6+
7+
@Entity
8+
public class Author implements Serializable {
9+
10+
private static final long serialVersionUID = 1L;
11+
12+
@Id
13+
private Long id;
14+
15+
private String name;
16+
private String genre;
17+
private int age;
18+
19+
public Long getId() {
20+
return id;
21+
}
22+
23+
public void setId(Long id) {
24+
this.id = id;
25+
}
26+
27+
public String getName() {
28+
return name;
29+
}
30+
31+
public void setName(String name) {
32+
this.name = name;
33+
}
34+
35+
public String getGenre() {
36+
return genre;
37+
}
38+
39+
public void setGenre(String genre) {
40+
this.genre = genre;
41+
}
42+
43+
public int getAge() {
44+
return age;
45+
}
46+
47+
public void setAge(int age) {
48+
this.age = age;
49+
}
50+
51+
}
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.bookstore.impl;
2+
3+
import java.io.Serializable;
4+
import java.util.logging.Logger;
5+
import javax.persistence.EntityManager;
6+
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
7+
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
8+
import org.springframework.transaction.annotation.Transactional;
9+
10+
@Transactional(readOnly = true)
11+
public class BatchRepositoryImpl<T, ID extends Serializable>
12+
extends SimpleJpaRepository<T, ID> implements BatchRepository<T, ID> {
13+
14+
private static final Logger logger = Logger.getLogger(BatchRepositoryImpl.class.getName());
15+
16+
private final EntityManager entityManager;
17+
18+
public BatchRepositoryImpl(JpaEntityInformation entityInformation,
19+
EntityManager entityManager) {
20+
super(entityInformation, entityManager);
21+
22+
this.entityManager = entityManager;
23+
}
24+
25+
@Override
26+
public <S extends T> void saveInBatch(Iterable<S> entities) {
27+
28+
if (entities == null) {
29+
throw new IllegalArgumentException("The given Iterable of entities cannot be null!");
30+
}
31+
32+
BatchExecutor batchExecutor = SpringContext.getBean(BatchExecutor.class);
33+
batchExecutor.saveInBatch(entities);
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.bookstore.impl;
2+
3+
import org.springframework.beans.BeansException;
4+
import org.springframework.context.ApplicationContext;
5+
import org.springframework.context.ApplicationContextAware;
6+
import org.springframework.stereotype.Component;
7+
8+
@Component
9+
public class SpringContext implements ApplicationContextAware {
10+
11+
private static ApplicationContext context;
12+
13+
public static <T extends Object> T getBean(Class<T> beanClass) {
14+
return context.getBean(beanClass);
15+
}
16+
17+
@Override
18+
public void setApplicationContext(ApplicationContext context) throws BeansException {
19+
SpringContext.context = context;
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.bookstore.repository;
2+
3+
import com.bookstore.impl.BatchRepository;
4+
import com.bookstore.entity.Author;
5+
import org.springframework.stereotype.Repository;
6+
7+
@Repository
8+
public interface AuthorRepository extends BatchRepository<Author, Long> {
9+
}
10+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.bookstore.service;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
import com.bookstore.entity.Author;
6+
import com.bookstore.repository.AuthorRepository;
7+
import org.springframework.stereotype.Service;
8+
9+
@Service
10+
public class BookstoreService {
11+
12+
private final AuthorRepository authorRepository;
13+
14+
public BookstoreService(AuthorRepository authorRepository) {
15+
this.authorRepository = authorRepository;
16+
}
17+
18+
public void batchAuthors() {
19+
20+
List<Author> authors = new ArrayList<>();
21+
22+
for (int i = 0; i < 1000; i++) {
23+
Author author = new Author();
24+
author.setId((long) i + 1);
25+
author.setName("Name_" + i);
26+
author.setGenre("Genre_" + i);
27+
author.setAge(18 + i);
28+
29+
authors.add(author);
30+
}
31+
32+
authorRepository.saveInBatch(authors);
33+
}
34+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
spring.datasource.url=jdbc:mysql://localhost:3306/bookstoredb?cachePrepStmts=true&useServerPrepStmts=true&rewriteBatchedStatements=true&createDatabaseIfNotExist=true
2+
spring.datasource.username=root
3+
spring.datasource.password=root
4+
5+
spring.jpa.hibernate.ddl-auto=create
6+
spring.jpa.show-sql=true
7+
8+
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
9+
10+
spring.jpa.open-in-view=false
11+
12+
spring.jpa.properties.hibernate.jdbc.batch_size=30
13+
14+
spring.jpa.properties.hibernate.cache.use_second_level_cache=false
15+
16+
# this is needed for ordering inserts in case of relationships
17+
# spring.jpa.properties.hibernate.order_inserts=true
18+
19+
spring.jpa.properties.hibernate.generate_statistics=true

0 commit comments

Comments
 (0)