Skip to content

Commit ef226e9

Browse files
committed
Use IN predicate for deleteAllInBatch(…).
We now use an IN (?1) predicate to avoid repeated OR alias = … variants to ease on JPQL parsing. With a sufficient number of predicates, parsers dive into a very deep parsing tree risking a StackOverflowError. Closes #2870
1 parent 90d7d69 commit ef226e9

File tree

2 files changed

+11
-40
lines changed

2 files changed

+11
-40
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaRepository.java

+8-5
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
*/
1616
package org.springframework.data.jpa.repository;
1717

18-
import java.util.List;
19-
2018
import jakarta.persistence.EntityManager;
2119

20+
import java.util.List;
21+
2222
import org.springframework.data.domain.Example;
2323
import org.springframework.data.domain.Sort;
2424
import org.springframework.data.repository.ListCrudRepository;
@@ -38,7 +38,8 @@
3838
* @author Jens Schauder
3939
*/
4040
@NoRepositoryBean
41-
public interface JpaRepository<T, ID> extends ListCrudRepository<T, ID>, ListPagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
41+
public interface JpaRepository<T, ID>
42+
extends ListCrudRepository<T, ID>, ListPagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
4243

4344
/**
4445
* Flushes all pending changes to the database.
@@ -66,6 +67,8 @@ public interface JpaRepository<T, ID> extends ListCrudRepository<T, ID>, ListPag
6667
* Deletes the given entities in a batch which means it will create a single query. This kind of operation leaves JPAs
6768
* first level cache and the database out of sync. Consider flushing the {@link EntityManager} before calling this
6869
* method.
70+
* <p>
71+
* It will also NOT honor cascade semantics of JPA, nor will it emit JPA lifecycle events.
6972
*
7073
* @param entities entities to be deleted. Must not be {@literal null}.
7174
* @deprecated Use {@link #deleteAllInBatch(Iterable)} instead.
@@ -80,8 +83,8 @@ default void deleteInBatch(Iterable<T> entities) {
8083
* first level cache and the database out of sync. Consider flushing the {@link EntityManager} before calling this
8184
* method.
8285
* <p>
83-
* It will also NOT honor cascade semantics of JPA, nor will it emit JPA lifecycle events.
84-
*</p>
86+
* It will also NOT honor cascade semantics of JPA, nor will it emit JPA lifecycle events.
87+
*
8588
* @param entities entities to be deleted. Must not be {@literal null}.
8689
* @since 2.5
8790
*/

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java

+3-35
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,7 @@
4141
import java.lang.annotation.Annotation;
4242
import java.lang.reflect.AnnotatedElement;
4343
import java.lang.reflect.Member;
44-
import java.util.ArrayList;
45-
import java.util.Collections;
46-
import java.util.HashMap;
47-
import java.util.HashSet;
48-
import java.util.Iterator;
49-
import java.util.List;
50-
import java.util.Locale;
51-
import java.util.Map;
52-
import java.util.Objects;
53-
import java.util.Set;
44+
import java.util.*;
5445
import java.util.regex.Matcher;
5546
import java.util.regex.Pattern;
5647
import java.util.stream.Collectors;
@@ -532,7 +523,6 @@ private static Integer findClose(final Integer open, final List<Integer> closes,
532523
* @param entityManager must not be {@literal null}.
533524
* @return Guaranteed to be not {@literal null}.
534525
*/
535-
536526
public static <T> Query applyAndBind(String queryString, Iterable<T> entities, EntityManager entityManager) {
537527

538528
Assert.notNull(queryString, "Querystring must not be null");
@@ -546,30 +536,8 @@ public static <T> Query applyAndBind(String queryString, Iterable<T> entities, E
546536
}
547537

548538
String alias = detectAlias(queryString);
549-
StringBuilder builder = new StringBuilder(queryString);
550-
builder.append(" where");
551-
552-
int i = 0;
553-
554-
while (iterator.hasNext()) {
555-
556-
iterator.next();
557-
558-
builder.append(String.format(" %s = ?%d", alias, ++i));
559-
560-
if (iterator.hasNext()) {
561-
builder.append(" or");
562-
}
563-
}
564-
565-
Query query = entityManager.createQuery(builder.toString());
566-
567-
iterator = entities.iterator();
568-
i = 0;
569-
570-
while (iterator.hasNext()) {
571-
query.setParameter(++i, iterator.next());
572-
}
539+
Query query = entityManager.createQuery("%s where %s IN (?1)".formatted(queryString, alias));
540+
query.setParameter(1, entities instanceof Collection<T> ? entities : Streamable.of(entities).toList());
573541

574542
return query;
575543
}

0 commit comments

Comments
 (0)