diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaParameters.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaParameters.java index fc3915bf6..3ba37e210 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaParameters.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaParameters.java @@ -24,6 +24,7 @@ import javax.persistence.TemporalType; import org.springframework.core.MethodParameter; import org.springframework.data.jpa.repository.Temporal; import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter; +import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.Parameters; @@ -74,6 +75,7 @@ public class JpaParameters extends Parameters { static class JpaParameter extends Parameter { private final Temporal annotation; + private final boolean explicitlyNamed; private TemporalType temporalType; /** @@ -86,11 +88,12 @@ public class JpaParameters extends Parameters { super(parameter); this.annotation = parameter.getParameterAnnotation(Temporal.class); + this.explicitlyNamed = parameter.getParameterAnnotation(Param.class) != null; this.temporalType = null; if (!isDateParameter() && hasTemporalParamAnnotation()) { - throw new IllegalArgumentException(Temporal.class.getSimpleName() - + " annotation is only allowed on Date parameter!"); + throw new IllegalArgumentException( + Temporal.class.getSimpleName() + " annotation is only allowed on Date parameter!"); } } @@ -122,6 +125,10 @@ public class JpaParameters extends Parameters { return this.temporalType; } + public boolean isExplicitlyNamed() { + return explicitlyNamed; + } + private boolean hasTemporalParamAnnotation() { return annotation != null; } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java b/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java index 5fe789945..5cbd06a6c 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java @@ -26,6 +26,7 @@ import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.ParameterExpression; import org.springframework.data.jpa.provider.PersistenceProvider; +import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter; import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.ParametersParameterAccessor; @@ -45,7 +46,7 @@ import org.springframework.util.ObjectUtils; class ParameterMetadataProvider { private final CriteriaBuilder builder; - private final Iterator parameters; + private final Iterator parameters; private final List> expressions; private final Iterator bindableParameterValues; private final PersistenceProvider persistenceProvider; @@ -61,8 +62,7 @@ class ParameterMetadataProvider { */ public ParameterMetadataProvider(CriteriaBuilder builder, ParametersParameterAccessor accessor, PersistenceProvider provider) { - - this(builder, accessor.iterator(), accessor.getParameters(), provider); + this(builder, accessor.iterator(), (JpaParameters) accessor.getParameters(), provider); } /** @@ -73,7 +73,7 @@ class ParameterMetadataProvider { * @param parameters must not be {@literal null}. * @param provider must not be {@literal null}. */ - public ParameterMetadataProvider(CriteriaBuilder builder, Parameters parameters, PersistenceProvider provider) { + public ParameterMetadataProvider(CriteriaBuilder builder, JpaParameters parameters, PersistenceProvider provider) { this(builder, null, parameters, provider); } @@ -89,7 +89,7 @@ class ParameterMetadataProvider { * @param provider must not be {@literal null}. */ private ParameterMetadataProvider(CriteriaBuilder builder, Iterator bindableParameterValues, - Parameters parameters, PersistenceProvider provider) { + JpaParameters parameters, PersistenceProvider provider) { Assert.notNull(builder); Assert.notNull(parameters); @@ -120,8 +120,8 @@ class ParameterMetadataProvider { @SuppressWarnings("unchecked") public ParameterMetadata next(Part part) { - Parameter parameter = parameters.next(); - return (ParameterMetadata) next(part, parameter.getType(), parameter.getName()); + JpaParameter parameter = parameters.next(); + return (ParameterMetadata) next(part, parameter.getType(), parameter); } /** @@ -135,9 +135,9 @@ class ParameterMetadataProvider { @SuppressWarnings("unchecked") public ParameterMetadata next(Part part, Class type) { - Parameter parameter = parameters.next(); + JpaParameter parameter = parameters.next(); Class typeToUse = ClassUtils.isAssignable(type, parameter.getType()) ? parameter.getType() : type; - return (ParameterMetadata) next(part, typeToUse, null); + return (ParameterMetadata) next(part, typeToUse, parameter); } /** @@ -149,7 +149,7 @@ class ParameterMetadataProvider { * @param name * @return */ - private ParameterMetadata next(Part part, Class type, String name) { + private ParameterMetadata next(Part part, Class type, JpaParameter parameter) { Assert.notNull(type); @@ -159,8 +159,8 @@ class ParameterMetadataProvider { @SuppressWarnings("unchecked") Class reifiedType = Expression.class.equals(type) ? (Class) Object.class : type; - ParameterExpression expression = name == null ? builder.parameter(reifiedType) : builder.parameter(reifiedType, - name); + ParameterExpression expression = parameter.isExplicitlyNamed() + ? builder.parameter(reifiedType, parameter.getName()) : builder.parameter(reifiedType); ParameterMetadata value = new ParameterMetadata(expression, part.getType(), bindableParameterValues == null ? ParameterMetadata.PLACEHOLDER : bindableParameterValues.next(), this.persistenceProvider); @@ -233,8 +233,8 @@ class ParameterMetadataProvider { case CONTAINING: return String.format("%%%s%%", value.toString()); default: - return Collection.class.isAssignableFrom(expression.getJavaType()) ? persistenceProvider - .potentiallyConvertEmptyCollection(toCollection(value)) : value; + return Collection.class.isAssignableFrom(expression.getJavaType()) + ? persistenceProvider.potentiallyConvertEmptyCollection(toCollection(value)) : value; } } diff --git a/src/test/java/org/springframework/data/jpa/repository/query/EclipseLinkParameterMetadataProviderIntegrationTests.java b/src/test/java/org/springframework/data/jpa/repository/query/EclipseLinkParameterMetadataProviderIntegrationTests.java new file mode 100644 index 000000000..a5208e150 --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/repository/query/EclipseLinkParameterMetadataProviderIntegrationTests.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +import org.springframework.test.context.ContextConfiguration; + +/** + * EclipseLink-specific tests for {@link ParameterMetadataProvider}. + * + * @author Oliver Gierke + * @soundtrack Elephants Crossing - We are (Irrelephant) + */ +@ContextConfiguration("classpath:eclipselink.xml") +public class EclipseLinkParameterMetadataProviderIntegrationTests extends ParameterMetadataProviderIntegrationTests {} diff --git a/src/test/java/org/springframework/data/jpa/repository/query/OpenJpaParameterMetadataProviderIntegrationTests.java b/src/test/java/org/springframework/data/jpa/repository/query/OpenJpaParameterMetadataProviderIntegrationTests.java new file mode 100644 index 000000000..c02130307 --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/repository/query/OpenJpaParameterMetadataProviderIntegrationTests.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +import org.springframework.test.context.ContextConfiguration; + +/** + * OpenJpa-specific tests for {@link ParameterMetadataProvider}. + * + * @author Oliver Gierke + * @soundtrack Elephants Crossing - We are (Irrelephant) + */ +@ContextConfiguration("classpath:openjpa.xml") +public class OpenJpaParameterMetadataProviderIntegrationTests extends ParameterMetadataProviderIntegrationTests {} diff --git a/src/test/java/org/springframework/data/jpa/repository/query/ParameterExpressionProviderTests.java b/src/test/java/org/springframework/data/jpa/repository/query/ParameterExpressionProviderTests.java index 988d639c7..d7126dc82 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/ParameterExpressionProviderTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/ParameterExpressionProviderTests.java @@ -14,7 +14,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.data.jpa.domain.sample.User; import org.springframework.data.jpa.provider.PersistenceProvider; -import org.springframework.data.repository.query.DefaultParameters; import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.data.repository.query.parser.Part; @@ -40,7 +39,7 @@ public class ParameterExpressionProviderTests { public void createsParameterExpressionWithMostConcreteType() throws Exception { Method method = SampleRepository.class.getMethod("findByIdGreaterThan", int.class); - Parameters parameters = new DefaultParameters(method); + Parameters parameters = new JpaParameters(method); ParametersParameterAccessor accessor = new ParametersParameterAccessor(parameters, new Object[] { 1 }); Part part = new Part("IdGreaterThan", User.class); diff --git a/src/test/java/org/springframework/data/jpa/repository/query/ParameterMetadataProviderIntegrationTests.java b/src/test/java/org/springframework/data/jpa/repository/query/ParameterMetadataProviderIntegrationTests.java new file mode 100644 index 000000000..38d46746a --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/repository/query/ParameterMetadataProviderIntegrationTests.java @@ -0,0 +1,98 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import java.lang.reflect.Method; +import java.util.List; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.data.jpa.domain.sample.User; +import org.springframework.data.jpa.provider.PersistenceProvider; +import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata; +import org.springframework.data.repository.query.Param; +import org.springframework.data.repository.query.Parameters; +import org.springframework.data.repository.query.parser.Part; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.util.ReflectionTestUtils; + +/** + * Integration tests for {@link ParameterMetadataProvider}. + * + * @author Oliver Gierke + * @soundtrack Elephants Crossing - We are (Irrelephant) + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("classpath:infrastructure.xml") +public class ParameterMetadataProviderIntegrationTests { + + @PersistenceContext EntityManager em; + + /** + * @see DATAJPA-758 + */ + @Test + public void forwardsParameterNameIfTransparentlyNamed() throws Exception { + + ParameterMetadataProvider provider = createProvider(Sample.class.getMethod("findByFirstname", String.class)); + ParameterMetadata metadata = provider.next(new Part("firstname", User.class)); + + assertThat(metadata.getExpression().getName(), is("name")); + } + + /** + * @see DATAJPA-758 + */ + @Test + public void forwardsParameterNameIfExplicitlyAnnotated() throws Exception { + + ParameterMetadataProvider provider = createProvider(Sample.class.getMethod("findByLastname", String.class)); + ParameterMetadata metadata = provider.next(new Part("lastname", User.class)); + + assertThat(metadata.getExpression().getName(), is(nullValue())); + } + + private ParameterMetadataProvider createProvider(Method method) { + + JpaParameters parameters = new JpaParameters(method); + simulateDiscoveredParametername(parameters, 0, "name"); + + return new ParameterMetadataProvider(em.getCriteriaBuilder(), parameters, + PersistenceProvider.fromEntityManager(em)); + } + + @SuppressWarnings("unchecked") + private static void simulateDiscoveredParametername(Parameters parameters, int index, String name) { + + List list = (List) ReflectionTestUtils.getField(parameters, "parameters"); + Object parameter = ReflectionTestUtils.getField(list.get(0), "parameter"); + ReflectionTestUtils.setField(parameter, "parameterName", name); + } + + interface Sample { + + User findByFirstname(@Param("name") String firstname); + + User findByLastname(String lastname); + } +}