Skip to content

Commit 96968e9

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 25594c9 commit 96968e9

File tree

2 files changed

+13
-43
lines changed

2 files changed

+13
-43
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

+5-38
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,7 @@
4040
import java.lang.annotation.Annotation;
4141
import java.lang.reflect.AnnotatedElement;
4242
import java.lang.reflect.Member;
43-
import java.util.ArrayList;
44-
import java.util.Collections;
45-
import java.util.HashMap;
46-
import java.util.HashSet;
47-
import java.util.Iterator;
48-
import java.util.List;
49-
import java.util.Locale;
50-
import java.util.Map;
51-
import java.util.Objects;
52-
import java.util.Set;
43+
import java.util.*;
5344
import java.util.regex.Matcher;
5445
import java.util.regex.Pattern;
5546
import java.util.stream.Collectors;
@@ -292,9 +283,8 @@ public static String applySorting(String query, Sort sort, @Nullable String alia
292283
Set<String> selectionAliases = getFunctionAliases(query);
293284
selectionAliases.addAll(getFieldAliases(query));
294285

295-
String orderClauses = sort.stream()
296-
.map(order -> getOrderClause(joinAliases, selectionAliases, alias, order))
297-
.collect(Collectors.joining(", "));
286+
String orderClauses = sort.stream().map(order -> getOrderClause(joinAliases, selectionAliases, alias, order))
287+
.collect(Collectors.joining(", "));
298288

299289
builder.append(orderClauses);
300290

@@ -532,7 +522,6 @@ private static Integer findClose(final Integer open, final List<Integer> closes,
532522
* @param entityManager must not be {@literal null}.
533523
* @return Guaranteed to be not {@literal null}.
534524
*/
535-
536525
public static <T> Query applyAndBind(String queryString, Iterable<T> entities, EntityManager entityManager) {
537526

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

548537
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-
}
538+
Query query = entityManager.createQuery("%s where %s IN (?1)".formatted(queryString, alias));
539+
query.setParameter(1, entities instanceof Collection<T> ? entities : Streamable.of(entities).toList());
573540

574541
return query;
575542
}

0 commit comments

Comments
 (0)