From 96968e91eb641aeaf9c50f5716fe30f94f7adc62 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 | 43 +++---------------- 2 files changed, 13 insertions(+), 43 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 14b2890056..0fb78d4b51 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 @@ -40,16 +40,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; @@ -292,9 +283,8 @@ public static String applySorting(String query, Sort sort, @Nullable String alia Set 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 @@ 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 +535,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; }