diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaExecutor.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaExecutor.java index 5b3c683aea6..dc2ef015350 100644 --- a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaExecutor.java +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaExecutor.java @@ -190,11 +190,9 @@ public void setJpaQuery(String jpaQuery) { * @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 void setNativeQuery(String nativeQuery) { * @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 @@ private Object executeOutboundJpaOperationOnPersistentMode(Message message) { 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(); } diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayTests-context.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayTests-context.xml index cef7b2a9f93..3e65fdb148c 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayTests-context.xml +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayTests-context.xml @@ -24,6 +24,7 @@ + diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayTests.java index acf80f8e0db..62f0d896103 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayTests.java +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayTests.java @@ -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.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 @@ * * @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 void getStudentStudentWithPositionalParameters() { @Test public void getAllStudents() { List 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 void persistStudentUsingMerge() { @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 void testUpdatingGatewayInsideChain() { @Test public void testJpaRepositoryAsService() { List students = this.studentService.getStudentsUsingJpaRepository("F"); - assertThat(students.size()).isEqualTo(2); + assertThat(students).hasSize(2); + } + + + @Test + @Transactional + public void testDeleteMany() { + List allStudents = this.studentService.getAllStudents(); + assertThat(allStudents).hasSize(3); + this.studentService.deleteStudents(allStudents); + assertThat(this.studentService.getAllStudents()).isNull(); } } diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/StudentService.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/StudentService.java index 7e1ba6dc68d..3ecc84e9de8 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/StudentService.java +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/StudentService.java @@ -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 @@ /** * @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 deleteStudents(List students); + @Payload("new java.util.Date()") List getAllStudents(); diff --git a/src/reference/asciidoc/jpa.adoc b/src/reference/asciidoc/jpa.adoc index 1dcc9770190..5279544f01c 100644 --- a/src/reference/asciidoc/jpa.adoc +++ b/src/reference/asciidoc/jpa.adoc @@ -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) diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index cea47a09e7d..1d50e1cdc84 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -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.