From 09a7e7ab935d8e8bf5718944e6dcc0ed1af97a17 Mon Sep 17 00:00:00 2001
From: Julia <5765049+sxhinzvc@users.noreply.github.com>
Date: Mon, 11 Sep 2023 17:03:56 -0400
Subject: [PATCH 1/3] Prepare issue branch.
---
pom.xml | 2 +-
spring-data-envers/pom.xml | 4 ++--
spring-data-jpa-distribution/pom.xml | 2 +-
spring-data-jpa/pom.xml | 4 ++--
4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/pom.xml b/pom.xml
index 1169772efd..f7b7db22d1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-jpa-parent
- 3.2.0-SNAPSHOT
+ 3.2.x-gh-3137-SNAPSHOT
pom
Spring Data JPA Parent
diff --git a/spring-data-envers/pom.xml b/spring-data-envers/pom.xml
index f239d6394b..d03a06e102 100755
--- a/spring-data-envers/pom.xml
+++ b/spring-data-envers/pom.xml
@@ -5,12 +5,12 @@
org.springframework.data
spring-data-envers
- 3.2.0-SNAPSHOT
+ 3.2.x-gh-3137-SNAPSHOT
org.springframework.data
spring-data-jpa-parent
- 3.2.0-SNAPSHOT
+ 3.2.x-gh-3137-SNAPSHOT
../pom.xml
diff --git a/spring-data-jpa-distribution/pom.xml b/spring-data-jpa-distribution/pom.xml
index a458953182..e7544dd8e2 100644
--- a/spring-data-jpa-distribution/pom.xml
+++ b/spring-data-jpa-distribution/pom.xml
@@ -14,7 +14,7 @@
org.springframework.data
spring-data-jpa-parent
- 3.2.0-SNAPSHOT
+ 3.2.x-gh-3137-SNAPSHOT
../pom.xml
diff --git a/spring-data-jpa/pom.xml b/spring-data-jpa/pom.xml
index b21b03c313..11e99051ab 100644
--- a/spring-data-jpa/pom.xml
+++ b/spring-data-jpa/pom.xml
@@ -6,7 +6,7 @@
org.springframework.data
spring-data-jpa
- 3.2.0-SNAPSHOT
+ 3.2.x-gh-3137-SNAPSHOT
Spring Data JPA
Spring Data module for JPA repositories.
@@ -15,7 +15,7 @@
org.springframework.data
spring-data-jpa-parent
- 3.2.0-SNAPSHOT
+ 3.2.x-gh-3137-SNAPSHOT
../pom.xml
From fb6d7a3b6c379cd6fa2397085ebaafb924572de1 Mon Sep 17 00:00:00 2001
From: Julia <5765049+sxhinzvc@users.noreply.github.com>
Date: Mon, 25 Sep 2023 09:02:47 -0400
Subject: [PATCH 2/3] Restrict TypedParameterValue usage to native queries
only.
Use Hibernate parameter accessor for native queries only to avoid affecting JPQL queries.
See #3137
---
...bernateJpaParametersParameterAccessor.java | 4 +-
.../repository/query/AbstractJpaQuery.java | 7 ++-
.../query/ParameterMetadataProvider.java | 18 +++-----
.../query/AbstractJpaQueryTests.java | 45 +++++++++++++++++++
.../ParameterMetadataProviderUnitTests.java | 33 +++++++++++++-
.../QueryWithNullLikeIntegrationTests.java | 15 ++++++-
6 files changed, 105 insertions(+), 17 deletions(-)
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/HibernateJpaParametersParameterAccessor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/HibernateJpaParametersParameterAccessor.java
index d5f153b8ec..ad9cf841b5 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/HibernateJpaParametersParameterAccessor.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/HibernateJpaParametersParameterAccessor.java
@@ -40,7 +40,7 @@
* @author Greg Turnquist
* @since 2.7
*/
-class HibernateJpaParametersParameterAccessor extends JpaParametersParameterAccessor {
+public class HibernateJpaParametersParameterAccessor extends JpaParametersParameterAccessor {
private final BasicTypeRegistry typeHelper;
@@ -51,7 +51,7 @@ class HibernateJpaParametersParameterAccessor extends JpaParametersParameterAcce
* @param values must not be {@literal null}.
* @param em must not be {@literal null}.
*/
- HibernateJpaParametersParameterAccessor(Parameters, ?> parameters, Object[] values, EntityManager em) {
+ public HibernateJpaParametersParameterAccessor(Parameters, ?> parameters, Object[] values, EntityManager em) {
super(parameters, values);
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java
index 32c5f438b1..3aa4e23210 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java
@@ -32,6 +32,7 @@
import java.util.stream.Collectors;
import org.springframework.core.convert.converter.Converter;
+import org.springframework.data.jpa.provider.HibernateJpaParametersParameterAccessor;
import org.springframework.data.jpa.provider.PersistenceProvider;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.query.JpaQueryExecution.CollectionExecution;
@@ -153,7 +154,11 @@ private Object doExecute(JpaQueryExecution execution, Object[] values) {
private JpaParametersParameterAccessor obtainParameterAccessor(Object[] values) {
- return provider.getParameterAccessor(method.getParameters(), values, em);
+ if (method.isNativeQuery() && PersistenceProvider.HIBERNATE.equals(provider)) {
+ return new HibernateJpaParametersParameterAccessor(method.getParameters(), values, em);
+ }
+
+ return new JpaParametersParameterAccessor(method.getParameters(), values);
}
protected JpaQueryExecution getExecution() {
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java
index 0b1d9a0709..5a09c225cb 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java
@@ -233,31 +233,27 @@ public boolean isIsNullParameter() {
/**
* Prepares the object before it's actually bound to the {@link jakarta.persistence.Query;}.
*
- * @param value must not be {@literal null}.
+ * @param value the value to be prepared.
*/
@Nullable
public Object prepare(Object value) {
- Assert.notNull(value, "Value must not be null");
-
- Object unwrapped = PersistenceProvider.unwrapTypedParameterValue(value);
-
- if (unwrapped == null || expression.getJavaType() == null) {
- return unwrapped;
+ if (value == null || expression.getJavaType() == null) {
+ return value;
}
if (String.class.equals(expression.getJavaType()) && !noWildcards) {
switch (type) {
case STARTING_WITH:
- return String.format("%s%%", escape.escape(unwrapped.toString()));
+ return String.format("%s%%", escape.escape(value.toString()));
case ENDING_WITH:
- return String.format("%%%s", escape.escape(unwrapped.toString()));
+ return String.format("%%%s", escape.escape(value.toString()));
case CONTAINING:
case NOT_CONTAINING:
- return String.format("%%%s%%", escape.escape(unwrapped.toString()));
+ return String.format("%%%s%%", escape.escape(value.toString()));
default:
- return unwrapped;
+ return value;
}
}
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractJpaQueryTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractJpaQueryTests.java
index f2ee0c720b..1241019661 100644
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractJpaQueryTests.java
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractJpaQueryTests.java
@@ -16,7 +16,9 @@
package org.springframework.data.jpa.repository.query;
import static org.assertj.core.api.Assumptions.assumeThat;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -36,7 +38,9 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
import org.springframework.data.jpa.domain.sample.User;
+import org.springframework.data.jpa.provider.HibernateJpaParametersParameterAccessor;
import org.springframework.data.jpa.provider.PersistenceProvider;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType;
@@ -65,12 +69,14 @@ class AbstractJpaQueryTests {
private Query query;
private TypedQuery countQuery;
+ private JpaQueryExecution execution;
@BeforeEach
@SuppressWarnings("unchecked")
void setUp() {
query = mock(Query.class);
countQuery = mock(TypedQuery.class);
+ execution = mock(JpaQueryExecution.class);
}
@Test // DATADOC-97
@@ -150,6 +156,37 @@ void shouldAddEntityGraphHintForLoad() throws Exception {
verify(result).setHint("jakarta.persistence.loadgraph", entityGraph);
}
+ @Test // GH-3137
+ void shouldCreateHibernateJpaParameterParametersAccessorForNativeQuery() throws Exception {
+
+ JpaQueryMethod queryMethod = getMethod("findByLastnameNativeQuery", String.class);
+
+ AbstractJpaQuery jpaQuery = new DummyJpaQuery(queryMethod, em);
+
+ jpaQuery.execute(new Object[] {"some last name"});
+
+ ArgumentCaptor captor = ArgumentCaptor.forClass(JpaParametersParameterAccessor.class);
+ verify(execution).execute(eq(jpaQuery), captor.capture());
+ JpaParametersParameterAccessor parameterAccessor = captor.getValue();
+
+ assertThat(parameterAccessor).isInstanceOf(HibernateJpaParametersParameterAccessor.class);
+ }
+
+ @Test // GH-3137
+ void shouldCreateGenericJpaParameterParametersAccessorForNonNativeQuery() throws Exception {
+
+ JpaQueryMethod queryMethod = getMethod("findByFirstname", String.class);
+ AbstractJpaQuery jpaQuery = new DummyJpaQuery(queryMethod, em);
+
+ jpaQuery.execute(new Object[] {"some first name"});
+
+ ArgumentCaptor captor = ArgumentCaptor.forClass(JpaParametersParameterAccessor.class);
+ verify(execution).execute(eq(jpaQuery), captor.capture());
+ JpaParametersParameterAccessor parameterAccessor = captor.getValue();
+
+ assertThat(parameterAccessor).isNotInstanceOf(HibernateJpaParametersParameterAccessor.class);
+ }
+
private JpaQueryMethod getMethod(String name, Class>... parameterTypes) throws Exception {
Method method = SampleRepository.class.getMethod(name, parameterTypes);
@@ -164,6 +201,9 @@ interface SampleRepository extends Repository {
@QueryHints({ @QueryHint(name = "foo", value = "bar") })
List findByLastname(String lastname);
+ @org.springframework.data.jpa.repository.Query(value = "select u from User u where u.lastname = ?1", nativeQuery = true)
+ List findByLastnameNativeQuery(String lastname);
+
@QueryHints(value = { @QueryHint(name = "bar", value = "foo") }, forCounting = false)
List findByFirstname(String firstname);
@@ -186,6 +226,11 @@ class DummyJpaQuery extends AbstractJpaQuery {
super(method, em);
}
+ @Override
+ protected JpaQueryExecution getExecution() {
+ return execution;
+ }
+
@Override
protected Query doCreateQuery(JpaParametersParameterAccessor accessor) {
return query;
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterMetadataProviderUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterMetadataProviderUnitTests.java
index e30d742b76..026738f95d 100644
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterMetadataProviderUnitTests.java
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterMetadataProviderUnitTests.java
@@ -22,17 +22,32 @@
import jakarta.persistence.criteria.CriteriaBuilder;
-import org.junit.jupiter.api.Test;
+import org.eclipse.persistence.internal.jpa.querydef.ParameterExpressionImpl;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.parser.Part;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+
/**
* Unit tests for {@link ParameterMetadataProvider}.
*
* @author Jens Schauder
+ * @author Julia Lee
*/
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.STRICT_STUBS)
class ParameterMetadataProviderUnitTests {
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS) Part part;
+
+ private ParameterExpressionImpl parameterExpression = new ParameterExpressionImpl(null, String.class);
+
@Test // DATAJPA-863
void errorMessageMentionesParametersWhenParametersAreExhausted() {
@@ -49,4 +64,20 @@ void errorMessageMentionesParametersWhenParametersAreExhausted() {
.withMessageContaining("parameter");
}
+ @Test // GH-3137
+ void returnAugmentedValueForStringExpressions() {
+ when(part.getProperty().getLeafProperty().isCollection()).thenReturn(false);
+
+ assertThat(createParameterMetadata(Part.Type.STARTING_WITH).prepare("starting with")).isEqualTo("starting with%");
+ assertThat(createParameterMetadata(Part.Type.ENDING_WITH).prepare("ending with")).isEqualTo("%ending with");
+ assertThat(createParameterMetadata(Part.Type.CONTAINING).prepare("containing")).isEqualTo("%containing%");
+ assertThat(createParameterMetadata(Part.Type.NOT_CONTAINING).prepare("not containing")).isEqualTo("%not containing%");
+ assertThat(createParameterMetadata(Part.Type.LIKE).prepare("%like%")).isEqualTo("%like%");
+ assertThat(createParameterMetadata(Part.Type.IS_NULL).prepare(null)).isEqualTo(null);
+ }
+
+ private ParameterMetadataProvider.ParameterMetadata createParameterMetadata(Part.Type partType) {
+ when(part.getType()).thenReturn(partType);
+ return new ParameterMetadataProvider.ParameterMetadata<>(parameterExpression, part, null, EscapeCharacter.DEFAULT);
+ }
}
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryWithNullLikeIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryWithNullLikeIntegrationTests.java
index 898975ad8d..29b0dc232f 100644
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryWithNullLikeIntegrationTests.java
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryWithNullLikeIntegrationTests.java
@@ -66,7 +66,8 @@ class QueryWithNullLikeIntegrationTests {
void setUp() {
repository.saveAllAndFlush(List.of( //
new EmployeeWithName("Frodo Baggins"), //
- new EmployeeWithName("Bilbo Baggins")));
+ new EmployeeWithName("Bilbo Baggins"),
+ new EmployeeWithName(null)));
}
@Test
@@ -273,7 +274,14 @@ void mismatchedReturnTypeShouldCauseException() {
@Test // GH-1184
void alignedReturnTypeShouldWork() {
assertThat(repository.customQueryWithAlignedReturnType()).containsExactly(new Object[][] {
- { "Frodo Baggins", "Frodo Baggins with suffix" }, { "Bilbo Baggins", "Bilbo Baggins with suffix" } });
+ { "Frodo Baggins", "Frodo Baggins with suffix" }, { "Bilbo Baggins", "Bilbo Baggins with suffix" }, { null, null} });
+ }
+
+ @Test
+ void nullOptionalParameterShouldReturnAllEntries() {
+ List result = repository.customQueryWithOptionalParameter(null);
+
+ assertThat(result).hasSize(3);
}
@Transactional
@@ -291,6 +299,9 @@ public interface EmployeeWithNullLikeRepository extends JpaRepository customQueryWithNullableParamInNative(@Nullable @Param("partialName") String partialName);
+ @Query("select e from EmployeeWithName e where (:partialName is null or e.name like %:partialName%)")
+ List customQueryWithOptionalParameter(@Nullable @Param("partialName") String partialName);
+
List findByNameStartsWith(@Nullable String partialName);
List findByNameEndsWith(@Nullable String partialName);
From d7dafbbf82ed8d88cf982f58c9786fa09f18b08e Mon Sep 17 00:00:00 2001
From: Julia <5765049+sxhinzvc@users.noreply.github.com>
Date: Mon, 25 Sep 2023 10:04:17 -0400
Subject: [PATCH 3/3] Polishing.
Co-locate Hibernate-specific parameters accessor as the same package as JPA parameters accessor.
Remove Parameter Accessor reference from Persistence Provider since it's created in AbstractJpaQuery.
Closes #3137
Original Pull Request: #3173
---
.../data/jpa/provider/PersistenceProvider.java | 11 -----------
.../data/jpa/repository/query/AbstractJpaQuery.java | 2 +-
.../HibernateJpaParametersParameterAccessor.java | 8 ++++----
.../repository/query/ParameterMetadataProvider.java | 4 ++--
.../jpa/repository/query/AbstractJpaQueryTests.java | 2 +-
...ernateJpaParametersParameterAccessorUnitTests.java | 3 ++-
.../query/JpaParametersParameterAccessorTests.java | 6 ++----
.../query/QueryWithNullLikeIntegrationTests.java | 1 +
8 files changed, 13 insertions(+), 24 deletions(-)
rename spring-data-jpa/src/main/java/org/springframework/data/jpa/{provider => repository/query}/HibernateJpaParametersParameterAccessor.java (89%)
rename spring-data-jpa/src/test/java/org/springframework/data/jpa/{provider => repository/query}/HibernateJpaParametersParameterAccessorUnitTests.java (92%)
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java
index fd9eec9e3a..076f447c3d 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java
@@ -106,12 +106,6 @@ public CloseableIterator