GH-3614: JPA outbound components delete in batch (#3629)

* GH-3614: JPA outbound components delete in batch

Fixes https://github.com/spring-projects/spring-integration/issues/3614

Add an `Iterable` payload support for the
`JpaExecutor.executeOutboundJpaOperationOnPersistentMode(Message<?>)`
and `DELETE` persist mode

* Fix language in the docs after review

Co-authored-by: Gary Russell <grussell@vmware.com>

Co-authored-by: Gary Russell <grussell@vmware.com>
This commit is contained in:
Artem Bilan
2021-09-10 16:34:10 -04:00
committed by GitHub
parent 25ac230be8
commit fe300ebc38
6 changed files with 57 additions and 55 deletions

View File

@@ -190,11 +190,9 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware {
* @param nativeQuery The provided SQL query must neither be null nor empty.
*/
public void setNativeQuery(String nativeQuery) {
Assert.isTrue(this.namedQuery == null && this.jpaQuery == null, "You can define only one of the "
+ "properties 'jpaQuery', 'nativeQuery', 'namedQuery'");
Assert.hasText(nativeQuery, "nativeQuery must neither be null nor empty.");
this.nativeQuery = nativeQuery;
}
@@ -204,10 +202,8 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware {
* @param namedQuery Must neither be null nor empty
*/
public void setNamedQuery(String namedQuery) {
Assert.isTrue(this.jpaQuery == null && this.nativeQuery == null, "You can define only one of the "
+ "properties 'jpaQuery', 'nativeQuery', 'namedQuery'");
Assert.hasText(namedQuery, "namedQuery must neither be null nor empty.");
this.namedQuery = namedQuery;
}
@@ -453,7 +449,12 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware {
case MERGE:
return this.jpaOperations.merge(payload, this.flushSize, this.clearOnFlush); // NOSONAR
case DELETE:
this.jpaOperations.delete(payload);
if (payload instanceof Iterable) {
this.jpaOperations.deleteInBatch((Iterable<?>) payload);
}
else {
this.jpaOperations.delete(payload);
}
if (this.flush) {
this.jpaOperations.flush();
}

View File

@@ -24,6 +24,7 @@
<int:gateway default-reply-channel="studentReplyChannel"
service-interface="org.springframework.integration.jpa.outbound.StudentService" default-reply-timeout="3000">
<int:method name="deleteStudent" request-channel="deleteStudentChannel" />
<int:method name="deleteStudents" request-channel="deleteStudentChannel" />
<int:method name="getStudent" request-channel="getStudentChannel" />
<int:method name="getStudentWithException" request-channel="getStudentEndpointWithExceptionChannel"/>
<int:method name="getStudentWithParameters" request-channel="getStudentWithParametersChannel"/>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,13 +17,13 @@
package org.springframework.integration.jpa.outbound;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import java.util.List;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.jpa.test.JpaTestUtils;
@@ -31,8 +31,7 @@ import org.springframework.integration.jpa.test.entity.StudentDomain;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.messaging.MessagingException;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.transaction.annotation.Transactional;
/**
@@ -43,8 +42,7 @@ import org.springframework.transaction.annotation.Transactional;
*
* @since 2.2
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@SpringJUnitConfig
@Transactional
@DirtiesContext
public class JpaOutboundGatewayTests {
@@ -55,65 +53,49 @@ public class JpaOutboundGatewayTests {
@Autowired
JdbcTemplate jdbcTemplate;
@After
@AfterEach
public void cleanUp() {
this.jdbcTemplate.execute("delete from Student where rollNumber > 1003");
}
@Test
public void getStudent() {
final StudentDomain student = studentService.getStudent(1001L);
StudentDomain student = studentService.getStudent(1001L);
assertThat(student).isNotNull();
}
@Test
public void getAllStudentsStartingFromGivenRecord() {
List<?> students = studentService.getAllStudentsFromGivenRecord(1);
assertThat(students).isNotNull();
assertThat(students.size()).isEqualTo(2);
assertThat(students).isNotNull().hasSize(2);
}
@Test
public void getAllStudentsWithMaxNumberOfRecords() {
List<?> students = studentService.getStudents(1);
assertThat(students).isNotNull();
assertThat(students.size()).isEqualTo(1);
assertThat(students).isNotNull().hasSize(1);
}
@Test
public void deleteNonExistingStudent() {
StudentDomain student = JpaTestUtils.getTestStudent();
student.setRollNumber(3424234234L);
try {
studentService.deleteStudent(student);
fail("IllegalArgumentException is expected");
}
catch (IllegalArgumentException e) {
assertThat(e.getMessage()).startsWith("Removing a detached instance");
}
assertThatIllegalArgumentException()
.isThrownBy(() -> studentService.deleteStudent(student))
.withMessageStartingWith("Removing a detached instance");
}
@Test
public void getStudentWithException() {
try {
studentService.getStudentWithException(1001L);
fail("MessageHandlingException is expected");
}
catch (MessagingException e) {
assertThat(e.getMessage())
.isEqualTo("The Jpa operation returned more than 1 result for expectSingleResult mode.");
}
assertThatExceptionOfType(MessagingException.class)
.isThrownBy(() -> studentService.getStudentWithException(1001L))
.withMessage("The Jpa operation returned more than 1 result for expectSingleResult mode.");
}
@Test
public void getStudentStudentWithPositionalParameters() {
StudentDomain student = studentService.getStudentWithParameters("First Two");
assertThat(student.getFirstName()).isEqualTo("First Two");
assertThat(student.getLastName()).isEqualTo("Last Two");
}
@@ -121,31 +103,27 @@ public class JpaOutboundGatewayTests {
@Test
public void getAllStudents() {
List<StudentDomain> students = studentService.getAllStudents();
assertThat(students).isNotNull();
assertThat(students.size()).isEqualTo(3);
assertThat(students).isNotNull().hasSize(3);
}
@Test
@Transactional
public void persistStudent() {
final StudentDomain studentToPersist = JpaTestUtils.getTestStudent();
StudentDomain studentToPersist = JpaTestUtils.getTestStudent();
assertThat(studentToPersist.getRollNumber()).isNull();
final StudentDomain persistedStudent = studentService.persistStudent(studentToPersist);
StudentDomain persistedStudent = studentService.persistStudent(studentToPersist);
assertThat(persistedStudent).isNotNull();
assertThat(persistedStudent.getRollNumber()).isNotNull();
}
@Test
@Transactional
public void persistStudentUsingMerge() {
final StudentDomain studentToPersist = JpaTestUtils.getTestStudent();
StudentDomain studentToPersist = JpaTestUtils.getTestStudent();
assertThat(studentToPersist.getRollNumber()).isNull();
final StudentDomain persistedStudent = studentService.persistStudentUsingMerge(studentToPersist);
StudentDomain persistedStudent = studentService.persistStudentUsingMerge(studentToPersist);
assertThat(persistedStudent).isNotNull();
assertThat(persistedStudent.getRollNumber()).isNotNull();
@@ -153,18 +131,17 @@ public class JpaOutboundGatewayTests {
@Test
public void testRetrievingGatewayInsideChain() {
final StudentDomain student = studentService.getStudent2(1001L);
StudentDomain student = studentService.getStudent2(1001L);
assertThat(student).isNotNull();
}
@Test
@Transactional
public void testUpdatingGatewayInsideChain() {
final StudentDomain studentToPersist = JpaTestUtils.getTestStudent();
StudentDomain studentToPersist = JpaTestUtils.getTestStudent();
assertThat(studentToPersist.getRollNumber()).isNull();
final StudentDomain persistedStudent = studentService.persistStudent2(studentToPersist);
StudentDomain persistedStudent = studentService.persistStudent2(studentToPersist);
assertThat(persistedStudent).isNotNull();
assertThat(persistedStudent.getRollNumber()).isNotNull();
@@ -173,7 +150,17 @@ public class JpaOutboundGatewayTests {
@Test
public void testJpaRepositoryAsService() {
List<StudentDomain> students = this.studentService.getStudentsUsingJpaRepository("F");
assertThat(students.size()).isEqualTo(2);
assertThat(students).hasSize(2);
}
@Test
@Transactional
public void testDeleteMany() {
List<StudentDomain> allStudents = this.studentService.getAllStudents();
assertThat(allStudents).hasSize(3);
this.studentService.deleteStudents(allStudents);
assertThat(this.studentService.getAllStudents()).isNull();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@ import org.springframework.messaging.handler.annotation.Payload;
/**
* @author Amol Nayak
* @author Artem Bilan
*
* @since 2.2
*/
public interface StudentService {
@@ -33,8 +34,11 @@ public interface StudentService {
StudentDomain getStudentWithException(Long id);
StudentDomain getStudent(Long id);
StudentDomain deleteStudent(StudentDomain student);
List<StudentDomain> deleteStudents(List<StudentDomain> students);
@Payload("new java.util.Date()")
List<StudentDomain> getAllStudents();

View File

@@ -501,6 +501,8 @@ NOTE: As of Spring Integration 3.0, payloads to `PERSIST` or `MERGE` can also be
In that case, each object returned by the `Iterable` is treated as an entity and persisted or merged using the underlying `EntityManager`.
Null values returned by the iterator are ignored.
NOTE: Starting with version 5.5.4, the `JpaOutboundGateway`, with a `JpaExecutor` configured with `PersistMode.DELETE`, can accept an `Iterable` payload to perform a batch removal persistent operation for the provided entities.
[[jpa-using-jpaql]]
==== Using JPA Query Language (JPA QL)

View File

@@ -100,3 +100,10 @@ See <<./mongodb.adoc#mongodb,MongoDb Support>> for more information.
The WebSocket channel adapters based on `ServerWebSocketContainer` can now be registered and removed at runtime.
See <<./web-sockets.adoc#web-sockets,WebSockets Support>> for more information.
[[x5.5-jpa]]
==== JPA Changes
The `JpaOutboundGateway` now supports an `Iterable` message payload for a `PersistMode.DELETE`.
See <<./jpa.adoc#jpa-outbound-channel-adapter,Outbound Channel Adapter>> for more information.