Merge pull request #6774 from isaolmez/BAEL-2809

BAEL-2809 Hibernate Batch Inserts
This commit is contained in:
Erik Pragt 2019-04-26 21:47:18 +08:00 committed by GitHub
commit b5d95430f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 307 additions and 2 deletions

View File

@ -26,6 +26,12 @@
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>net.ttddyy</groupId>
<artifactId>datasource-proxy</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
@ -35,7 +41,5 @@
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,53 @@
package com.baeldung.batchinserts;
import java.lang.reflect.Method;
import javax.sql.DataSource;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
@Component
@Profile("batchinserts")
public class DatasourceProxyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
if (bean instanceof DataSource) {
ProxyFactory factory = new ProxyFactory(bean);
factory.setProxyTargetClass(true);
factory.addAdvice(new ProxyDataSourceInterceptor((DataSource) bean));
return factory.getProxy();
}
return bean;
}
private static class ProxyDataSourceInterceptor implements MethodInterceptor {
private final DataSource dataSource;
public ProxyDataSourceInterceptor(final DataSource dataSource) {
this.dataSource = ProxyDataSourceBuilder.create(dataSource).name("Batch-Insert-Logger").asJson().countQuery().logQueryToSysOut().build();
}
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
Method proxyMethod = ReflectionUtils.findMethod(dataSource.getClass(), invocation.getMethod().getName());
if (proxyMethod != null) {
return proxyMethod.invoke(dataSource, invocation.getArguments());
}
return invocation.proceed();
}
}
}

View File

@ -0,0 +1,45 @@
package com.baeldung.batchinserts.model;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
@Entity
public class School {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
private String name;
@OneToMany(mappedBy = "school")
private List<Student> students;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Student> getStudents() {
return students;
}
public void setStudents(List<Student> students) {
this.students = students;
}
}

View File

@ -0,0 +1,44 @@
package com.baeldung.batchinserts.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
private String name;
@ManyToOne
private School school;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
}

View File

@ -0,0 +1,6 @@
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.jdbc.batch_size=5
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.order_updates=true
spring.jpa.properties.hibernate.batch_versioned_data=true

View File

@ -0,0 +1,94 @@
package com.baeldung.batchinserts;
import static com.baeldung.batchinserts.TestObjectHelper.createSchool;
import static com.baeldung.batchinserts.TestObjectHelper.createStudent;
import com.baeldung.batchinserts.model.School;
import com.baeldung.batchinserts.model.Student;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
@ActiveProfiles("batchinserts")
public class JpaBatchInsertsIntegrationTest {
@PersistenceContext
private EntityManager entityManager;
private static final int BATCH_SIZE = 5;
@Transactional
@Test
public void whenInsertingSingleTypeOfEntity_thenCreatesSingleBatch() {
for (int i = 0; i < 10; i++) {
School school = createSchool(i);
entityManager.persist(school);
}
}
@Transactional
@Test
public void whenFlushingAfterBatch_ThenClearsMemory() {
for (int i = 0; i < 10; i++) {
if (i > 0 && i % BATCH_SIZE == 0) {
entityManager.flush();
entityManager.clear();
}
School school = createSchool(i);
entityManager.persist(school);
}
}
@Transactional
@Test
public void whenThereAreMultipleEntities_ThenCreatesNewBatch() {
for (int i = 0; i < 10; i++) {
if (i > 0 && i % BATCH_SIZE == 0) {
entityManager.flush();
entityManager.clear();
}
School school = createSchool(i);
entityManager.persist(school);
Student firstStudent = createStudent(school);
Student secondStudent = createStudent(school);
entityManager.persist(firstStudent);
entityManager.persist(secondStudent);
}
}
@Transactional
@Test
public void whenUpdatingEntities_thenCreatesBatch() {
for (int i = 0; i < 10; i++) {
School school = createSchool(i);
entityManager.persist(school);
}
entityManager.flush();
TypedQuery<School> schoolQuery = entityManager.createQuery("SELECT s from School s", School.class);
List<School> allSchools = schoolQuery.getResultList();
for (School school : allSchools) {
school.setName("Updated_" + school.getName());
}
}
@After
public void tearDown() {
entityManager.flush();
}
}

View File

@ -0,0 +1,39 @@
package com.baeldung.batchinserts;
import static com.baeldung.batchinserts.TestObjectHelper.createSchool;
import com.baeldung.batchinserts.model.School;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
@ActiveProfiles("batchinserts")
@TestPropertySource(properties = "spring.jpa.properties.hibernate.jdbc.batch_size=-1")
public class JpaNoBatchInsertsIntegrationTest {
@PersistenceContext
private EntityManager entityManager;
@Test
public void whenNotConfigured_ThenSendsInsertsSeparately() {
for (int i = 0; i < 10; i++) {
School school = createSchool(i);
entityManager.persist(school);
}
}
@After
public void tearDown() {
entityManager.flush();
}
}

View File

@ -0,0 +1,20 @@
package com.baeldung.batchinserts;
import com.baeldung.batchinserts.model.School;
import com.baeldung.batchinserts.model.Student;
public class TestObjectHelper {
public static School createSchool(int nameIdentifier) {
School school = new School();
school.setName("School" + (nameIdentifier + 1));
return school;
}
public static Student createStudent(School school) {
Student student = new Student();
student.setName("Student-" + school.getName());
student.setSchool(school);
return student;
}
}