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
This commit is contained in:
Mark Paluch
2025-01-23 10:46:28 +01:00
parent 25594c93c5
commit 96968e91eb
2 changed files with 13 additions and 43 deletions

View File

@@ -15,10 +15,10 @@
*/
package org.springframework.data.jpa.repository;
import java.util.List;
import jakarta.persistence.EntityManager;
import java.util.List;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.ListCrudRepository;
@@ -38,7 +38,8 @@ import org.springframework.data.repository.query.QueryByExampleExecutor;
* @author Jens Schauder
*/
@NoRepositoryBean
public interface JpaRepository<T, ID> extends ListCrudRepository<T, ID>, ListPagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
public interface JpaRepository<T, ID>
extends ListCrudRepository<T, ID>, ListPagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
/**
* Flushes all pending changes to the database.
@@ -66,6 +67,8 @@ public interface JpaRepository<T, ID> extends ListCrudRepository<T, ID>, ListPag
* Deletes the given entities in a batch which means it will create a single query. This kind of operation leaves JPAs
* first level cache and the database out of sync. Consider flushing the {@link EntityManager} before calling this
* method.
* <p>
* It will also NOT honor cascade semantics of JPA, nor will it emit JPA lifecycle events.
*
* @param entities entities to be deleted. Must not be {@literal null}.
* @deprecated Use {@link #deleteAllInBatch(Iterable)} instead.
@@ -80,8 +83,8 @@ public interface JpaRepository<T, ID> extends ListCrudRepository<T, ID>, ListPag
* first level cache and the database out of sync. Consider flushing the {@link EntityManager} before calling this
* method.
* <p>
* It will also NOT honor cascade semantics of JPA, nor will it emit JPA lifecycle events.
*</p>
* It will also NOT honor cascade semantics of JPA, nor will it emit JPA lifecycle events.
*
* @param entities entities to be deleted. Must not be {@literal null}.
* @since 2.5
*/

View File

@@ -40,16 +40,7 @@ import jakarta.persistence.metamodel.SingularAttribute;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Member;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -292,9 +283,8 @@ public abstract class QueryUtils {
Set<String> selectionAliases = getFunctionAliases(query);
selectionAliases.addAll(getFieldAliases(query));
String orderClauses = sort.stream()
.map(order -> getOrderClause(joinAliases, selectionAliases, alias, order))
.collect(Collectors.joining(", "));
String orderClauses = sort.stream().map(order -> getOrderClause(joinAliases, selectionAliases, alias, order))
.collect(Collectors.joining(", "));
builder.append(orderClauses);
@@ -532,7 +522,6 @@ public abstract class QueryUtils {
* @param entityManager must not be {@literal null}.
* @return Guaranteed to be not {@literal null}.
*/
public static <T> Query applyAndBind(String queryString, Iterable<T> entities, EntityManager entityManager) {
Assert.notNull(queryString, "Querystring must not be null");
@@ -546,30 +535,8 @@ public abstract class QueryUtils {
}
String alias = detectAlias(queryString);
StringBuilder builder = new StringBuilder(queryString);
builder.append(" where");
int i = 0;
while (iterator.hasNext()) {
iterator.next();
builder.append(String.format(" %s = ?%d", alias, ++i));
if (iterator.hasNext()) {
builder.append(" or");
}
}
Query query = entityManager.createQuery(builder.toString());
iterator = entities.iterator();
i = 0;
while (iterator.hasNext()) {
query.setParameter(++i, iterator.next());
}
Query query = entityManager.createQuery("%s where %s IN (?1)".formatted(queryString, alias));
query.setParameter(1, entities instanceof Collection<T> ? entities : Streamable.of(entities).toList());
return query;
}