diff --git a/src/main/java/org/springframework/data/jpa/repository/JpaRepository.java b/src/main/java/org/springframework/data/jpa/repository/JpaRepository.java index 8bf0f5a88..1769b5052 100644 --- a/src/main/java/org/springframework/data/jpa/repository/JpaRepository.java +++ b/src/main/java/org/springframework/data/jpa/repository/JpaRepository.java @@ -84,6 +84,19 @@ public interface JpaRepository extends PagingAndSortingRepository, */ void deleteInBatch(Iterable entities); + + /** + * Deletes the entities identified by the given ids using a single {@link Query}. + * + * This kind of operation leaves JPAs first level cache and the database out of sync. + * Consider flushing the `EntityManager` before calling this method. + * + * @param ids + * + * @since 3.0 + */ + void deleteAllByIdInBatch(Iterable ids); + /** * Deletes all entities in a batch call. */ diff --git a/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java b/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java index f59aeb585..c779073d1 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java @@ -86,6 +86,7 @@ public abstract class QueryUtils { public static final String COUNT_QUERY_STRING = "select count(%s) from %s x"; public static final String DELETE_ALL_QUERY_STRING = "delete from %s x"; + public static final String DELETE_ALL_QUERY_BY_ID_STRING = "delete from %s x where %s in :ids"; // Used Regex/Unicode categories (see https://www.unicode.org/reports/tr18/#General_Category_Property): // Z Separator diff --git a/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java b/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java index f9181959b..c28498b67 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java @@ -218,6 +218,16 @@ public class SimpleJpaRepository implements JpaRepositoryImplementation ids) { + + Assert.notNull(ids, "Ids must not be null!"); + + for (ID id : ids) { + deleteById(id); + } + } + /* * (non-Javadoc) * @see org.springframework.data.jpa.repository.JpaRepository#deleteInBatch(java.lang.Iterable) @@ -236,6 +246,24 @@ public class SimpleJpaRepository implements JpaRepositoryImplementation ids) { + + Assert.notNull(ids, "Ids must not be null!"); + + if (!ids.iterator().hasNext()) { + return; + } + + String queryTemplate = DELETE_ALL_QUERY_BY_ID_STRING; + String queryString = String.format(queryTemplate, entityInformation.getEntityName(), entityInformation.getIdAttribute().getName()); + + Query query = em.createQuery(queryString); + query.setParameter("ids", ids); + + query.executeUpdate(); + } + /* * (non-Javadoc) * @see org.springframework.data.repository.Repository#deleteAll() diff --git a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java index dedd3d546..d2ff18140 100644 --- a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java @@ -15,6 +15,7 @@ */ package org.springframework.data.jpa.repository; +import static java.util.Arrays.*; import static org.assertj.core.api.Assertions.*; import static org.springframework.data.domain.Example.*; import static org.springframework.data.domain.ExampleMatcher.*; @@ -151,7 +152,7 @@ public class UserRepositoryTests { flushTestUsers(); - assertThat(repository.findAllById(Arrays.asList(firstUser.getId(), secondUser.getId()))).contains(firstUser, + assertThat(repository.findAllById(asList(firstUser.getId(), secondUser.getId()))).contains(firstUser, secondUser); } @@ -166,7 +167,7 @@ public class UserRepositoryTests { @Test void savesCollectionCorrectly() throws Exception { - assertThat(repository.saveAll(Arrays.asList(firstUser, secondUser, thirdUser))).hasSize(3).contains(firstUser, + assertThat(repository.saveAll(asList(firstUser, secondUser, thirdUser))).hasSize(3).contains(firstUser, secondUser, thirdUser); } @@ -238,13 +239,13 @@ public class UserRepositoryTests { } @Test - void deleteColletionOfEntities() { + void deleteCollectionOfEntities() { flushTestUsers(); long before = repository.count(); - repository.deleteAll(Arrays.asList(firstUser, secondUser)); + repository.deleteAll(asList(firstUser, secondUser)); assertThat(repository.existsById(firstUser.getId())).isFalse(); assertThat(repository.existsById(secondUser.getId())).isFalse(); @@ -252,13 +253,27 @@ public class UserRepositoryTests { } @Test - void batchDeleteColletionOfEntities() { + void batchDeleteCollectionOfEntities() { flushTestUsers(); long before = repository.count(); - repository.deleteInBatch(Arrays.asList(firstUser, secondUser)); + repository.deleteInBatch(asList(firstUser, secondUser)); + + assertThat(repository.existsById(firstUser.getId())).isFalse(); + assertThat(repository.existsById(secondUser.getId())).isFalse(); + assertThat(repository.count()).isEqualTo(before - 2); + } + + @Test // DATAJPA-1818 + void deleteCollectionOfEntitiesById() { + + flushTestUsers(); + + long before = repository.count(); + + repository.deleteAllById(asList(firstUser.getId(), secondUser.getId())); assertThat(repository.existsById(firstUser.getId())).isFalse(); assertThat(repository.existsById(secondUser.getId())).isFalse(); @@ -603,7 +618,7 @@ public class UserRepositoryTests { firstUser.setManager(secondUser); thirdUser.setManager(firstUser); - repository.saveAll(Arrays.asList(firstUser, thirdUser)); + repository.saveAll(asList(firstUser, thirdUser)); assertThat(repository.findByManagerLastname("Arrasz")).containsOnly(firstUser); assertThat(repository.findByManagerLastname("Gierke")).containsOnly(thirdUser); @@ -616,7 +631,7 @@ public class UserRepositoryTests { firstUser.addColleague(secondUser); thirdUser.addColleague(firstUser); - repository.saveAll(Arrays.asList(firstUser, thirdUser)); + repository.saveAll(asList(firstUser, thirdUser)); assertThat(repository.findByColleaguesLastname(secondUser.getLastname())).containsOnly(firstUser); @@ -1178,7 +1193,7 @@ public class UserRepositoryTests { flushTestUsers(); - List result = repository.findByAttributesIn(new HashSet<>(Arrays.asList("cool", "hip"))); + List result = repository.findByAttributesIn(new HashSet<>(asList("cool", "hip"))); assertThat(result).containsOnly(firstUser, secondUser); } @@ -1731,7 +1746,7 @@ public class UserRepositoryTests { firstUser.setManager(secondUser); thirdUser.setManager(firstUser); - repository.saveAll(Arrays.asList(firstUser, thirdUser)); + repository.saveAll(asList(firstUser, thirdUser)); User manager = new User(); manager.setLastname("Arrasz"); @@ -1854,7 +1869,7 @@ public class UserRepositoryTests { fifthUser.setFirstname(firstUser.getFirstname()); fifthUser.setLastname(firstUser.getLastname()); - repository.saveAll(Arrays.asList(firstUser, fifthUser)); + repository.saveAll(asList(firstUser, fifthUser)); User prototype = new User(); prototype.setFirstname(firstUser.getFirstname()); @@ -2242,7 +2257,7 @@ public class UserRepositoryTests { flushTestUsers(); - List result = repository.findByAttributesIgnoreCaseIn(new HashSet<>(Arrays.asList("cOOl", "hIP"))); + List result = repository.findByAttributesIgnoreCaseIn(new HashSet<>(asList("cOOl", "hIP"))); assertThat(result).containsOnly(firstUser, secondUser); } @@ -2256,7 +2271,7 @@ public class UserRepositoryTests { flushTestUsers(); - List result = repository.findByAttributesIgnoreCaseNotIn(Arrays.asList("CooL", "HIp")); + List result = repository.findByAttributesIgnoreCaseNotIn(asList("CooL", "HIp")); assertThat(result).containsOnly(thirdUser); } @@ -2284,7 +2299,7 @@ public class UserRepositoryTests { flushTestUsers(); - List result = repository.findByAttributesIgnoreCaseIn(Arrays.asList("cOOl", null)); + List result = repository.findByAttributesIgnoreCaseIn(asList("cOOl", null)); assertThat(result).containsOnly(firstUser); } diff --git a/src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkJpaRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkJpaRepositoryTests.java index 62990eee3..a53b2f934 100644 --- a/src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkJpaRepositoryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkJpaRepositoryTests.java @@ -15,6 +15,7 @@ */ package org.springframework.data.jpa.repository.support; +import org.junit.jupiter.api.Disabled; import org.springframework.test.context.ContextConfiguration; /** @@ -25,4 +26,11 @@ import org.springframework.test.context.ContextConfiguration; @ContextConfiguration("classpath:eclipselink.xml") class EclipseLinkJpaRepositoryTests extends JpaRepositoryTests { + @Override + /** + * Ignored until https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477 is resolved. + */ + void deleteAllByIdInBatch() { + super.deleteAllByIdInBatch(); + } } diff --git a/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryTests.java index feb7ae1e3..69e3611ad 100644 --- a/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryTests.java @@ -114,11 +114,24 @@ class JpaRepositoryTests { assertThat(idClassRepository.existsById(id)).isTrue(); } - private static interface SampleEntityRepository extends JpaRepository { + @Test // DATAJPA-1818 + void deleteAllByIdInBatch() { + + SampleEntity one = new SampleEntity("one", "eins"); + SampleEntity two = new SampleEntity("two", "zwei"); + SampleEntity three = new SampleEntity("three", "drei"); + repository.saveAll(Arrays.asList(one, two, three)); + repository.flush(); + + repository.deleteAllByIdInBatch(Arrays.asList(new SampleEntityPK("one", "eins"),new SampleEntityPK("three", "drei"))); + assertThat(repository.findAll()).containsExactly(two); + } + + private interface SampleEntityRepository extends JpaRepository { } - private static interface SampleWithIdClassRepository + private interface SampleWithIdClassRepository extends CrudRepository { }