From ef226e9bb23460d61d2bcba5bd32a74240252bc9 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 23 Jan 2025 10:46:28 +0100 Subject: [PATCH] =?UTF-8?q?Use=20`IN`=20predicate=20for=20`deleteAllInBatc?= =?UTF-8?q?h(=E2=80=A6)`.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../data/jpa/repository/JpaRepository.java | 13 ++++--- .../data/jpa/repository/query/QueryUtils.java | 38 ++----------------- 2 files changed, 11 insertions(+), 40 deletions(-) diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaRepository.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaRepository.java index a3541460f9..4346d853dc 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaRepository.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaRepository.java @@ -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 @@ * @author Jens Schauder */ @NoRepositoryBean -public interface JpaRepository extends ListCrudRepository, ListPagingAndSortingRepository, QueryByExampleExecutor { +public interface JpaRepository + extends ListCrudRepository, ListPagingAndSortingRepository, QueryByExampleExecutor { /** * Flushes all pending changes to the database. @@ -66,6 +67,8 @@ public interface JpaRepository extends ListCrudRepository, 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. + *

+ * 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 @@ default void deleteInBatch(Iterable entities) { * first level cache and the database out of sync. Consider flushing the {@link EntityManager} before calling this * method. *

- * It will also NOT honor cascade semantics of JPA, nor will it emit JPA lifecycle events. - *

+ * 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 */ diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java index dfe6e739a9..8946fd9a3b 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java @@ -41,16 +41,7 @@ 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; @@ -532,7 +523,6 @@ private static Integer findClose(final Integer open, final List closes, * @param entityManager must not be {@literal null}. * @return Guaranteed to be not {@literal null}. */ - public static Query applyAndBind(String queryString, Iterable entities, EntityManager entityManager) { Assert.notNull(queryString, "Querystring must not be null"); @@ -546,30 +536,8 @@ public static Query applyAndBind(String queryString, Iterable entities, E } 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 ? entities : Streamable.of(entities).toList()); return query; }