Browse Source

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
pull/4129/head
Mark Paluch 6 months ago
parent
commit
32afca168d
No known key found for this signature in database
GPG Key ID: 55BC6374BAA9D973
  1. 57
      spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java
  2. 2
      spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java
  3. 23
      spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EclipseLinkQueryUtilsIntegrationTests.java
  4. 25
      spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsIntegrationTests.java

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

@ -50,6 +50,7 @@ import org.springframework.dao.InvalidDataAccessApiUsageException; @@ -50,6 +50,7 @@ import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.jpa.domain.JpaSort.JpaOrder;
import org.springframework.data.jpa.provider.PersistenceProvider;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.util.Streamable;
import org.springframework.lang.Nullable;
@ -528,6 +529,21 @@ public abstract class QueryUtils { @@ -528,6 +529,21 @@ public abstract class QueryUtils {
* @return Guaranteed to be not {@literal null}.
*/
public static <T> Query applyAndBind(String queryString, Iterable<T> entities, EntityManager entityManager) {
return applyAndBind(queryString, entities, entityManager, PersistenceProvider.fromEntityManager(entityManager));
}
/**
* Creates a where-clause referencing the given entities and appends it to the given query string. Binds the given
* entities to the query.
*
* @param <T> type of the entities.
* @param queryString must not be {@literal null}.
* @param entities must not be {@literal null}.
* @param entityManager must not be {@literal null}.
* @return Guaranteed to be not {@literal null}.
*/
static <T> Query applyAndBind(String queryString, Iterable<T> entities, EntityManager entityManager,
PersistenceProvider persistenceProvider) {
Assert.notNull(queryString, "Querystring must not be null");
Assert.notNull(entities, "Iterable of entities must not be null");
@ -539,9 +555,46 @@ public abstract class QueryUtils { @@ -539,9 +555,46 @@ public abstract class QueryUtils {
return entityManager.createQuery(queryString);
}
if (persistenceProvider == PersistenceProvider.HIBERNATE) {
String alias = detectAlias(queryString);
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;
}
return applyWhereEqualsAndBind(queryString, entities, entityManager, iterator);
}
private static Query applyWhereEqualsAndBind(String queryString, Iterable<?> entities, EntityManager entityManager,
Iterator<?> iterator) {
String alias = detectAlias(queryString);
Query query = entityManager.createQuery("%s where %s IN (?1)".formatted(queryString, alias));
query.setParameter(1, entities instanceof Collection<T> ? entities : Streamable.of(entities).toList());
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());
}
return query;
}

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

@ -3583,7 +3583,7 @@ class UserRepositoryTests { @@ -3583,7 +3583,7 @@ class UserRepositoryTests {
String getLastname();
}
record UserDto(Integer id, String firstname, String lastname, String emailAddress) {
public record UserDto(Integer id, String firstname, String lastname, String emailAddress) {
}

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

@ -22,11 +22,16 @@ import jakarta.persistence.criteria.CriteriaQuery; @@ -22,11 +22,16 @@ import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Path;
import jakarta.persistence.criteria.Root;
import java.util.List;
import org.eclipse.persistence.internal.jpa.EJBQueryImpl;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.data.jpa.domain.sample.User;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.transaction.annotation.Transactional;
/**
* EclipseLink variant of {@link QueryUtilsIntegrationTests}.
@ -63,4 +68,22 @@ class EclipseLinkQueryUtilsIntegrationTests extends QueryUtilsIntegrationTests { @@ -63,4 +68,22 @@ class EclipseLinkQueryUtilsIntegrationTests extends QueryUtilsIntegrationTests {
assertThat(from.getJoins()).hasSize(1);
}
@Test // GH-3983, GH-2870
@Disabled("Not supported by EclipseLink")
@Transactional
@Override
void applyAndBindOptimizesIn() {}
@Test // GH-3983, GH-2870
@Transactional
@Override
void applyAndBindExpandsToPositionalPlaceholders() {
em.getCriteriaBuilder();
EJBQueryImpl<?> query = (EJBQueryImpl) QueryUtils.applyAndBind("DELETE FROM User u",
List.of(new User(), new User()), em.unwrap(null));
assertThat(query.getDatabaseQuery().getJPQLString()).isEqualTo("DELETE FROM User u where u = ?1 or u = ?2");
}
}

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

@ -44,6 +44,7 @@ import java.util.Set; @@ -44,6 +44,7 @@ import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.hibernate.query.hql.spi.SqmQueryImplementor;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
@ -59,6 +60,7 @@ import org.springframework.data.jpa.infrastructure.HibernateTestUtils; @@ -59,6 +60,7 @@ import org.springframework.data.jpa.infrastructure.HibernateTestUtils;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.transaction.annotation.Transactional;
/**
* Integration tests for {@link QueryUtils}.
@ -384,6 +386,29 @@ class QueryUtilsIntegrationTests { @@ -384,6 +386,29 @@ class QueryUtilsIntegrationTests {
assertThat(root.getJoins()).hasSize(getNumberOfJoinsAfterCreatingAPath());
}
@Test // GH-3983, GH-2870
@Transactional
void applyAndBindOptimizesIn() {
em.getCriteriaBuilder();
SqmQueryImplementor<?> query = (SqmQueryImplementor) QueryUtils
.applyAndBind("DELETE FROM User u", List.of(new User(), new User()), em.unwrap(null));
assertThat(query.getQueryString()).isEqualTo("DELETE FROM User u where u IN (?1)");
}
@Test // GH-3983, GH-2870
@Transactional
void applyAndBindExpandsToPositionalPlaceholders() {
em.getCriteriaBuilder();
SqmQueryImplementor<?> query = (SqmQueryImplementor) QueryUtils
.applyAndBind("DELETE FROM User u", List.of(new User(), new User()), em.unwrap(null),
org.springframework.data.jpa.provider.PersistenceProvider.ECLIPSELINK);
assertThat(query.getQueryString()).isEqualTo("DELETE FROM User u where u = ?1 or u = ?2");
}
int getNumberOfJoinsAfterCreatingAPath() {
return 0;
}

Loading…
Cancel
Save