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.
This commit is contained in:
Greg L. Turnquist
2022-04-13 11:01:19 -05:00
parent c93aa25fec
commit e62cafb190
3 changed files with 79 additions and 32 deletions

View File

@@ -15,6 +15,32 @@
*/
package org.springframework.data.jpa.repository.support;
import static org.springframework.data.jpa.repository.query.QueryUtils.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import javax.persistence.NoResultException;
import javax.persistence.Parameter;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.ParameterExpression;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
@@ -37,29 +63,6 @@ import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import javax.persistence.NoResultException;
import javax.persistence.Parameter;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.ParameterExpression;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import static org.springframework.data.jpa.repository.query.QueryUtils.*;
/**
* Default implementation of the {@link org.springframework.data.repository.CrudRepository} interface. This will offer
* you a more sophisticated interface than the plain {@link EntityManager} .
@@ -237,7 +240,16 @@ public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T
entityInformation.getIdAttribute().getName());
Query query = em.createQuery(queryString);
query.setParameter("ids", ids);
/**
* Some JPA providers require {@code ids} to be a {@link Collection} so we must convert if it's not already.
*/
if (Collection.class.isInstance(ids)) {
query.setParameter("ids", ids);
} else {
Collection<ID> idsCollection = StreamSupport.stream(ids.spliterator(), false)
.collect(Collectors.toCollection(ArrayList::new));
query.setParameter("ids", idsCollection);
}
query.executeUpdate();
}
@@ -325,7 +337,6 @@ public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T
/**
* Returns {@link QueryHints} with the query hints based on the current {@link CrudMethodMetadata} and potential
* {@link EntityGraph} information.
*
*/
protected QueryHints getQueryHints() {
return metadata == null ? NoHints.INSTANCE : DefaultQueryHints.of(entityInformation, metadata);
@@ -563,8 +574,7 @@ public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T
*/
@Override
public <S extends T> List<S> findAll(Example<S> example, Sort sort) {
return getQuery(new ExampleSpecification<>(example, escapeCharacter), example.getProbeType(), sort)
.getResultList();
return getQuery(new ExampleSpecification<>(example, escapeCharacter), example.getProbeType(), sort).getResultList();
}
/*

View File

@@ -22,15 +22,20 @@ import org.springframework.test.context.ContextConfiguration;
* Integration tests to execute {@link JpaRepositoryTests} against EclipseLink.
*
* @author Oliver Gierke
* @author Greg Turnquist
*/
@ContextConfiguration("classpath:eclipselink.xml")
class EclipseLinkJpaRepositoryTests extends JpaRepositoryTests {
@Override
/**
* Ignored until https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477 is resolved.
*/
@Disabled("https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477")
void deleteAllByIdInBatch() {
super.deleteAllByIdInBatch();
// disabled
}
@Override
@Disabled("https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477")
void deleteAllByIdInBatchShouldConvertAnIterableToACollection() {
// disabled
}
}

View File

@@ -18,15 +18,17 @@ package org.springframework.data.jpa.repository.support;
import static org.assertj.core.api.Assertions.*;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.data.jpa.domain.sample.PersistableWithIdClass;
import org.springframework.data.jpa.domain.sample.PersistableWithIdClassPK;
import org.springframework.data.jpa.domain.sample.SampleEntity;
@@ -43,6 +45,7 @@ import org.springframework.transaction.annotation.Transactional;
* @author Oliver Gierke
* @author Thomas Darimont
* @author Jens Schauder
* @author Greg Turnquist
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration({ "classpath:infrastructure.xml" })
@@ -127,6 +130,35 @@ class JpaRepositoryTests {
assertThat(repository.findAll()).containsExactly(two);
}
@Test // GH-2242
void deleteAllByIdInBatchShouldConvertAnIterableToACollection() {
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();
/**
* Wrap a {@link List} inside an {@link Iterable} to verify that {@link SimpleJpaRepository} can properly convert a
* pure {@link Iterable} to a {@link Collection}.
**/
Iterable<SampleEntityPK> ids = new Iterable<SampleEntityPK>() {
private List<SampleEntityPK> ids = Arrays.asList(new SampleEntityPK("one", "eins"),
new SampleEntityPK("three", "drei"));
@NotNull
@Override
public Iterator<SampleEntityPK> iterator() {
return ids.iterator();
}
};
repository.deleteAllByIdInBatch(ids);
assertThat(repository.findAll()).containsExactly(two);
}
private interface SampleEntityRepository extends JpaRepository<SampleEntity, SampleEntityPK> {
}