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:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user