Skip to content

Commit 71f15d8

Browse files
committed
Reinstate parameter per entity for batch deletes using EclipseLink.
EclipseLink doesn't support WHERE e IN (:entities) and requires e = ?1 OR e = ?2 OR … style. Closes #3983
1 parent 64c5743 commit 71f15d8

File tree

4 files changed

+107
-7
lines changed

4 files changed

+107
-7
lines changed

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

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import org.springframework.data.domain.Sort;
5353
import org.springframework.data.domain.Sort.Order;
5454
import org.springframework.data.jpa.domain.JpaSort.JpaOrder;
55+
import org.springframework.data.jpa.provider.PersistenceProvider;
5556
import org.springframework.data.mapping.PropertyPath;
5657
import org.springframework.data.util.Streamable;
5758
import org.springframework.util.Assert;
@@ -516,6 +517,21 @@ private static Integer findClose(final Integer open, final List<Integer> closes,
516517
* @return Guaranteed to be not {@literal null}.
517518
*/
518519
public static <T> Query applyAndBind(String queryString, Iterable<T> entities, EntityManager entityManager) {
520+
return applyAndBind(queryString, entities, entityManager, PersistenceProvider.fromEntityManager(entityManager));
521+
}
522+
523+
/**
524+
* Creates a where-clause referencing the given entities and appends it to the given query string. Binds the given
525+
* entities to the query.
526+
*
527+
* @param <T> type of the entities.
528+
* @param queryString must not be {@literal null}.
529+
* @param entities must not be {@literal null}.
530+
* @param entityManager must not be {@literal null}.
531+
* @return Guaranteed to be not {@literal null}.
532+
*/
533+
static <T> Query applyAndBind(String queryString, Iterable<T> entities, EntityManager entityManager,
534+
PersistenceProvider persistenceProvider) {
519535

520536
Assert.notNull(queryString, "Querystring must not be null");
521537
Assert.notNull(entities, "Iterable of entities must not be null");
@@ -527,9 +543,46 @@ public static <T> Query applyAndBind(String queryString, Iterable<T> entities, E
527543
return entityManager.createQuery(queryString);
528544
}
529545

546+
if (persistenceProvider == PersistenceProvider.HIBERNATE) {
547+
548+
String alias = detectAlias(queryString);
549+
Query query = entityManager.createQuery("%s where %s IN (?1)".formatted(queryString, alias));
550+
query.setParameter(1, entities instanceof Collection<T> ? entities : Streamable.of(entities).toList());
551+
552+
return query;
553+
}
554+
555+
return applyWhereEqualsAndBind(queryString, entities, entityManager, iterator);
556+
}
557+
558+
private static Query applyWhereEqualsAndBind(String queryString, Iterable<?> entities, EntityManager entityManager,
559+
Iterator<?> iterator) {
560+
530561
String alias = detectAlias(queryString);
531-
Query query = entityManager.createQuery("%s where %s IN (?1)".formatted(queryString, alias));
532-
query.setParameter(1, entities instanceof Collection<T> ? entities : Streamable.of(entities).toList());
562+
StringBuilder builder = new StringBuilder(queryString);
563+
builder.append(" where");
564+
565+
int i = 0;
566+
567+
while (iterator.hasNext()) {
568+
569+
iterator.next();
570+
571+
builder.append(String.format(" %s = ?%d", alias, ++i));
572+
573+
if (iterator.hasNext()) {
574+
builder.append(" or");
575+
}
576+
}
577+
578+
Query query = entityManager.createQuery(builder.toString());
579+
580+
iterator = entities.iterator();
581+
i = 0;
582+
583+
while (iterator.hasNext()) {
584+
query.setParameter(++i, iterator.next());
585+
}
533586

534587
return query;
535588
}

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3656,7 +3656,7 @@ private interface UserProjectionInterfaceBased {
36563656
String getLastname();
36573657
}
36583658

3659-
record UserDto(Integer id, String firstname, String lastname, String emailAddress) {
3659+
public record UserDto(Integer id, String firstname, String lastname, String emailAddress) {
36603660

36613661
}
36623662

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EclipseLinkQueryUtilsIntegrationTests.java

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,16 @@
2222
import jakarta.persistence.criteria.Path;
2323
import jakarta.persistence.criteria.Root;
2424

25+
import java.util.List;
26+
27+
import org.eclipse.persistence.internal.jpa.EJBQueryImpl;
2528
import org.junit.jupiter.api.Disabled;
2629
import org.junit.jupiter.api.Test;
2730

2831
import org.springframework.data.jpa.domain.sample.User;
2932
import org.springframework.data.mapping.PropertyPath;
3033
import org.springframework.test.context.ContextConfiguration;
34+
import org.springframework.transaction.annotation.Transactional;
3135

3236
/**
3337
* EclipseLink variant of {@link QueryUtilsIntegrationTests}.
@@ -64,25 +68,43 @@ void prefersFetchOverJoin() {
6468
assertThat(from.getJoins()).hasSize(1);
6569
}
6670

67-
6871
@Test // GH-3349
6972
@Disabled
7073
@Override
7174
void doesNotCreateJoinForRelationshipSimpleId() {
72-
//eclipse link produces join for path.get(relationship)
75+
// eclipse link produces join for path.get(relationship)
7376
}
7477

7578
@Test // GH-3349
7679
@Disabled
7780
@Override
7881
void doesNotCreateJoinForRelationshipEmbeddedId() {
79-
//eclipse link produces join for path.get(relationship)
82+
// eclipse link produces join for path.get(relationship)
8083
}
8184

8285
@Test // GH-3349
8386
@Disabled
8487
@Override
8588
void doesNotCreateJoinForRelationshipIdClass() {
86-
//eclipse link produces join for path.get(relationship)
89+
// eclipse link produces join for path.get(relationship)
90+
}
91+
92+
@Test // GH-3983, GH-2870
93+
@Disabled("Not supported by EclipseLink")
94+
@Transactional
95+
@Override
96+
void applyAndBindOptimizesIn() {}
97+
98+
@Test // GH-3983, GH-2870
99+
@Transactional
100+
@Override
101+
void applyAndBindExpandsToPositionalPlaceholders() {
102+
103+
em.getCriteriaBuilder();
104+
EJBQueryImpl<?> query = (EJBQueryImpl) QueryUtils.applyAndBind("DELETE FROM User u",
105+
List.of(new User(), new User()), em.unwrap(null));
106+
107+
assertThat(query.getDatabaseQuery().getJPQLString()).isEqualTo("DELETE FROM User u where u = ?1 or u = ?2");
87108
}
109+
88110
}

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsIntegrationTests.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import java.util.function.Consumer;
4646
import java.util.stream.Collectors;
4747

48+
import org.hibernate.query.sqm.internal.SqmQueryImpl;
4849
import org.junit.jupiter.api.Test;
4950
import org.junit.jupiter.api.extension.ExtendWith;
5051
import org.mockito.Mockito;
@@ -62,6 +63,7 @@
6263
import org.springframework.data.mapping.PropertyPath;
6364
import org.springframework.test.context.ContextConfiguration;
6465
import org.springframework.test.context.junit.jupiter.SpringExtension;
66+
import org.springframework.transaction.annotation.Transactional;
6567

6668
/**
6769
* Integration tests for {@link QueryUtils}.
@@ -428,6 +430,29 @@ void doesNotCreateJoinForRelationshipIdClass() {
428430
assertThat(from.getJoins()).isEmpty();
429431
}
430432

433+
@Test // GH-3983, GH-2870
434+
@Transactional
435+
void applyAndBindOptimizesIn() {
436+
437+
em.getCriteriaBuilder();
438+
SqmQueryImpl<?> query = (SqmQueryImpl) QueryUtils
439+
.applyAndBind("DELETE FROM User u", List.of(new User(), new User()), em.unwrap(null));
440+
441+
assertThat(query.getQueryString()).isEqualTo("DELETE FROM User u where u IN (?1)");
442+
}
443+
444+
@Test // GH-3983, GH-2870
445+
@Transactional
446+
void applyAndBindExpandsToPositionalPlaceholders() {
447+
448+
em.getCriteriaBuilder();
449+
SqmQueryImpl<?> query = (SqmQueryImpl) QueryUtils
450+
.applyAndBind("DELETE FROM User u", List.of(new User(), new User()), em.unwrap(null),
451+
org.springframework.data.jpa.provider.PersistenceProvider.ECLIPSELINK);
452+
453+
assertThat(query.getQueryString()).isEqualTo("DELETE FROM User u where u = ?1 or u = ?2");
454+
}
455+
431456
int getNumberOfJoinsAfterCreatingAPath() {
432457
return 0;
433458
}

0 commit comments

Comments
 (0)