Skip to content

Commit e62cafb

Browse files
committed
Convert Iterable<ID> to Collection<ID> for deleteAllByIdInBatch.
JpaRepository accepts Iterable<ID> for bulk deletes. But some JPA providers require Collection<ID> instead. To avoid breaking any APIs, convert the incoming argument if it's not already a Collection. See #2242.
1 parent c93aa25 commit e62cafb

File tree

3 files changed

+79
-32
lines changed

3 files changed

+79
-32
lines changed

src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java

+37-27
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,32 @@
1515
*/
1616
package org.springframework.data.jpa.repository.support;
1717

18+
import static org.springframework.data.jpa.repository.query.QueryUtils.*;
19+
20+
import java.util.ArrayList;
21+
import java.util.Collection;
22+
import java.util.Collections;
23+
import java.util.HashMap;
24+
import java.util.List;
25+
import java.util.Map;
26+
import java.util.Optional;
27+
import java.util.function.Function;
28+
import java.util.stream.Collectors;
29+
import java.util.stream.StreamSupport;
30+
31+
import javax.persistence.EntityManager;
32+
import javax.persistence.LockModeType;
33+
import javax.persistence.NoResultException;
34+
import javax.persistence.Parameter;
35+
import javax.persistence.Query;
36+
import javax.persistence.TypedQuery;
37+
import javax.persistence.criteria.CriteriaBuilder;
38+
import javax.persistence.criteria.CriteriaQuery;
39+
import javax.persistence.criteria.ParameterExpression;
40+
import javax.persistence.criteria.Path;
41+
import javax.persistence.criteria.Predicate;
42+
import javax.persistence.criteria.Root;
43+
1844
import org.springframework.dao.EmptyResultDataAccessException;
1945
import org.springframework.data.domain.Example;
2046
import org.springframework.data.domain.Page;
@@ -37,29 +63,6 @@
3763
import org.springframework.transaction.annotation.Transactional;
3864
import org.springframework.util.Assert;
3965

40-
import javax.persistence.EntityManager;
41-
import javax.persistence.LockModeType;
42-
import javax.persistence.NoResultException;
43-
import javax.persistence.Parameter;
44-
import javax.persistence.Query;
45-
import javax.persistence.TypedQuery;
46-
import javax.persistence.criteria.CriteriaBuilder;
47-
import javax.persistence.criteria.CriteriaQuery;
48-
import javax.persistence.criteria.ParameterExpression;
49-
import javax.persistence.criteria.Path;
50-
import javax.persistence.criteria.Predicate;
51-
import javax.persistence.criteria.Root;
52-
import java.util.ArrayList;
53-
import java.util.Collection;
54-
import java.util.Collections;
55-
import java.util.HashMap;
56-
import java.util.List;
57-
import java.util.Map;
58-
import java.util.Optional;
59-
import java.util.function.Function;
60-
61-
import static org.springframework.data.jpa.repository.query.QueryUtils.*;
62-
6366
/**
6467
* Default implementation of the {@link org.springframework.data.repository.CrudRepository} interface. This will offer
6568
* you a more sophisticated interface than the plain {@link EntityManager} .
@@ -237,7 +240,16 @@ public void deleteAllByIdInBatch(Iterable<ID> ids) {
237240
entityInformation.getIdAttribute().getName());
238241

239242
Query query = em.createQuery(queryString);
240-
query.setParameter("ids", ids);
243+
/**
244+
* Some JPA providers require {@code ids} to be a {@link Collection} so we must convert if it's not already.
245+
*/
246+
if (Collection.class.isInstance(ids)) {
247+
query.setParameter("ids", ids);
248+
} else {
249+
Collection<ID> idsCollection = StreamSupport.stream(ids.spliterator(), false)
250+
.collect(Collectors.toCollection(ArrayList::new));
251+
query.setParameter("ids", idsCollection);
252+
}
241253

242254
query.executeUpdate();
243255
}
@@ -325,7 +337,6 @@ public Optional<T> findById(ID id) {
325337
/**
326338
* Returns {@link QueryHints} with the query hints based on the current {@link CrudMethodMetadata} and potential
327339
* {@link EntityGraph} information.
328-
*
329340
*/
330341
protected QueryHints getQueryHints() {
331342
return metadata == null ? NoHints.INSTANCE : DefaultQueryHints.of(entityInformation, metadata);
@@ -563,8 +574,7 @@ public <S extends T> List<S> findAll(Example<S> example) {
563574
*/
564575
@Override
565576
public <S extends T> List<S> findAll(Example<S> example, Sort sort) {
566-
return getQuery(new ExampleSpecification<>(example, escapeCharacter), example.getProbeType(), sort)
567-
.getResultList();
577+
return getQuery(new ExampleSpecification<>(example, escapeCharacter), example.getProbeType(), sort).getResultList();
568578
}
569579

570580
/*

src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkJpaRepositoryTests.java

+9-4
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,20 @@
2222
* Integration tests to execute {@link JpaRepositoryTests} against EclipseLink.
2323
*
2424
* @author Oliver Gierke
25+
* @author Greg Turnquist
2526
*/
2627
@ContextConfiguration("classpath:eclipselink.xml")
2728
class EclipseLinkJpaRepositoryTests extends JpaRepositoryTests {
2829

2930
@Override
30-
/**
31-
* Ignored until https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477 is resolved.
32-
*/
31+
@Disabled("https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477")
3332
void deleteAllByIdInBatch() {
34-
super.deleteAllByIdInBatch();
33+
// disabled
34+
}
35+
36+
@Override
37+
@Disabled("https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477")
38+
void deleteAllByIdInBatchShouldConvertAnIterableToACollection() {
39+
// disabled
3540
}
3641
}

src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryTests.java

+33-1
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,17 @@
1818
import static org.assertj.core.api.Assertions.*;
1919

2020
import java.util.Arrays;
21+
import java.util.Iterator;
22+
import java.util.List;
2123
import java.util.Optional;
2224

2325
import javax.persistence.EntityManager;
2426
import javax.persistence.PersistenceContext;
2527

28+
import org.jetbrains.annotations.NotNull;
2629
import org.junit.jupiter.api.BeforeEach;
2730
import org.junit.jupiter.api.Test;
2831
import org.junit.jupiter.api.extension.ExtendWith;
29-
3032
import org.springframework.data.jpa.domain.sample.PersistableWithIdClass;
3133
import org.springframework.data.jpa.domain.sample.PersistableWithIdClassPK;
3234
import org.springframework.data.jpa.domain.sample.SampleEntity;
@@ -43,6 +45,7 @@
4345
* @author Oliver Gierke
4446
* @author Thomas Darimont
4547
* @author Jens Schauder
48+
* @author Greg Turnquist
4649
*/
4750
@ExtendWith(SpringExtension.class)
4851
@ContextConfiguration({ "classpath:infrastructure.xml" })
@@ -127,6 +130,35 @@ void deleteAllByIdInBatch() {
127130
assertThat(repository.findAll()).containsExactly(two);
128131
}
129132

133+
@Test // GH-2242
134+
void deleteAllByIdInBatchShouldConvertAnIterableToACollection() {
135+
136+
SampleEntity one = new SampleEntity("one", "eins");
137+
SampleEntity two = new SampleEntity("two", "zwei");
138+
SampleEntity three = new SampleEntity("three", "drei");
139+
repository.saveAll(Arrays.asList(one, two, three));
140+
repository.flush();
141+
142+
/**
143+
* Wrap a {@link List} inside an {@link Iterable} to verify that {@link SimpleJpaRepository} can properly convert a
144+
* pure {@link Iterable} to a {@link Collection}.
145+
**/
146+
Iterable<SampleEntityPK> ids = new Iterable<SampleEntityPK>() {
147+
148+
private List<SampleEntityPK> ids = Arrays.asList(new SampleEntityPK("one", "eins"),
149+
new SampleEntityPK("three", "drei"));
150+
151+
@NotNull
152+
@Override
153+
public Iterator<SampleEntityPK> iterator() {
154+
return ids.iterator();
155+
}
156+
};
157+
158+
repository.deleteAllByIdInBatch(ids);
159+
assertThat(repository.findAll()).containsExactly(two);
160+
}
161+
130162
private interface SampleEntityRepository extends JpaRepository<SampleEntity, SampleEntityPK> {
131163

132164
}

0 commit comments

Comments
 (0)