DATAJPA-107 - Add support to specify TemporalType for Date query parameters.

Introduced JpaParameters abstraction to support custom jpa-specific annotations on query parameters. Adjusted Parameter binders to use the JpaParameters abstraction. Added special handling for temporal JpaParameters to ParameterBinder.bind(…) and CriteriaQueryParameterBinder.bind(…).

Adapted changes introduced in DATACMNS-350. Original pull request: #31.
This commit is contained in:
Thomas Darimont
2013-07-26 11:31:50 +02:00
committed by Oliver Gierke
parent fbb88e8c76
commit 619c1e6a55
17 changed files with 443 additions and 74 deletions

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2013 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 javax.persistence.TemporalType.*;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.lang.reflect.Method;
import java.util.Date;
import javax.persistence.TemporalType;
import org.junit.Test;
import org.springframework.data.jpa.repository.Temporal;
import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
/**
* Unit tests for {@link JpaParameters}.
*
* @author Oliver Gierke
*/
public class JpaParametersUnitTests {
@Test
public void findsTemporalParameterConfiguration() throws Exception {
Method method = SampleRepository.class.getMethod("foo", Date.class, String.class);
JpaParameters parameters = new JpaParameters(method);
JpaParameter parameter = parameters.getBindableParameter(0);
assertThat(parameter.isSpecialParameter(), is(false));
assertThat(parameter.isTemporalParameter(), is(true));
assertThat(parameter.getTemporalType(), is(TemporalType.TIMESTAMP));
parameter = parameters.getBindableParameter(1);
assertThat(parameter.isTemporalParameter(), is(false));
}
interface SampleRepository {
void foo(@Temporal(TIMESTAMP) Date date, String firstname);
}
}

View File

@@ -34,6 +34,7 @@ import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.query.JpaQueryExecution.ModifyingExecution;
import org.springframework.data.jpa.repository.query.JpaQueryExecution.PagedExecution;
import org.springframework.data.repository.query.DefaultParameters;
import org.springframework.data.repository.query.Parameters;
/**
@@ -44,17 +45,12 @@ import org.springframework.data.repository.query.Parameters;
@RunWith(MockitoJUnitRunner.class)
public class JpaQueryExecutionUnitTests {
@Mock
EntityManager em;
@Mock
AbstractStringBasedJpaQuery jpaQuery;
@Mock
Query query;
@Mock
JpaQueryMethod method;
@Mock EntityManager em;
@Mock AbstractStringBasedJpaQuery jpaQuery;
@Mock Query query;
@Mock JpaQueryMethod method;
@Mock
TypedQuery<Long> countQuery;
@Mock TypedQuery<Long> countQuery;
@Test(expected = IllegalArgumentException.class)
public void rejectsNullQuery() {
@@ -121,7 +117,7 @@ public class JpaQueryExecutionUnitTests {
@Test
public void pagedExecutionDoesNotRetrieveObjectsForPageableOutOfRange() throws Exception {
Parameters parameters = new Parameters(getClass().getMethod("sampleMethod", Pageable.class));
Parameters<?, ?> parameters = new DefaultParameters(getClass().getMethod("sampleMethod", Pageable.class));
when(jpaQuery.createCountQuery(Mockito.any(Object[].class))).thenReturn(countQuery);
when(jpaQuery.createQuery(Mockito.any(Object[].class))).thenReturn(query);
when(countQuery.getResultList()).thenReturn(Arrays.asList(20L));

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2008-2011 the original author or authors.
* Copyright 2008-2013 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.
@@ -15,15 +15,18 @@
*/
package org.springframework.data.jpa.repository.query;
import static javax.persistence.TemporalType.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
import java.util.Date;
import javax.persistence.Embeddable;
import javax.persistence.Query;
import javax.persistence.TemporalType;
import org.junit.Before;
import org.junit.Test;
@@ -32,21 +35,21 @@ import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.Temporal;
import org.springframework.data.repository.query.Param;
import org.springframework.data.repository.query.Parameters;
/**
* Unit test for {@link ParameterBinder}.
*
* @author Oliver Gierke
* @author Thomas Darimont
*/
@RunWith(MockitoJUnitRunner.class)
public class ParameterBinderUnitTests {
private Method valid;
@Mock
private Query query;
@Mock private Query query;
private Method useIndexedParameters;
private Method indexedParametersWithSort;
@@ -74,24 +77,31 @@ public class ParameterBinderUnitTests {
User validWithPageable(@Param("username") String username, Pageable pageable);
User validWithSort(@Param("username") String username, Sort sort);
User validWithDefaultTemporalTypeParameter(@Temporal Date registerDate);
User validWithCustomTemporalTypeParameter(@Temporal(TIMESTAMP) Date registerDate);
User invalidWithTemporalTypeParameter(@Temporal String registerDate);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsToManyParameters() throws Exception {
new ParameterBinder(new Parameters(valid), new Object[] { "foo", "bar" });
new ParameterBinder(new JpaParameters(valid), new Object[] { "foo", "bar" });
}
@Test(expected = IllegalArgumentException.class)
public void rejectsNullParameters() throws Exception {
new ParameterBinder(new Parameters(valid), (Object[]) null);
new ParameterBinder(new JpaParameters(valid), (Object[]) null);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsToLittleParameters() throws SecurityException, NoSuchMethodException {
Parameters parameters = new Parameters(valid);
JpaParameters parameters = new JpaParameters(valid);
new ParameterBinder(parameters);
}
@@ -100,7 +110,7 @@ public class ParameterBinderUnitTests {
Method method = SampleRepository.class.getMethod("validWithPageable", String.class, Pageable.class);
Parameters parameters = new Parameters(method);
JpaParameters parameters = new JpaParameters(method);
ParameterBinder binder = new ParameterBinder(parameters, new Object[] { "foo", null });
assertThat(binder.getPageable(), is(nullValue()));
@@ -111,7 +121,7 @@ public class ParameterBinderUnitTests {
Method validWithSort = SampleRepository.class.getMethod("validWithSort", String.class, Sort.class);
new ParameterBinder(new Parameters(validWithSort), new Object[] { "foo", null }).bind(query);
new ParameterBinder(new JpaParameters(validWithSort), new Object[] { "foo", null }).bind(query);
verify(query).setParameter(eq(1), eq("foo"));
}
@@ -120,14 +130,14 @@ public class ParameterBinderUnitTests {
Method validWithPageable = SampleRepository.class.getMethod("validWithPageable", String.class, Pageable.class);
new ParameterBinder(new Parameters(validWithPageable), new Object[] { "foo", null }).bind(query);
new ParameterBinder(new JpaParameters(validWithPageable), new Object[] { "foo", null }).bind(query);
verify(query).setParameter(eq(1), eq("foo"));
}
@Test
public void usesIndexedParametersIfNoParamAnnotationPresent() throws Exception {
new ParameterBinder(new Parameters(useIndexedParameters), new Object[] { "foo" }).bind(query);
new ParameterBinder(new JpaParameters(useIndexedParameters), new Object[] { "foo" }).bind(query);
verify(query).setParameter(eq(1), anyObject());
}
@@ -135,7 +145,7 @@ public class ParameterBinderUnitTests {
public void usesParameterNameIfAnnotated() throws Exception {
when(query.setParameter(eq("username"), anyObject())).thenReturn(query);
new ParameterBinder(new Parameters(valid), new Object[] { "foo" }) {
new ParameterBinder(new JpaParameters(valid), new Object[] { "foo" }) {
@Override
boolean hasNamedParameter(Query query) {
@@ -150,7 +160,7 @@ public class ParameterBinderUnitTests {
public void bindsEmbeddableCorrectly() throws Exception {
Method method = getClass().getMethod("findByEmbeddable", SampleEmbeddable.class);
Parameters parameters = new Parameters(method);
JpaParameters parameters = new JpaParameters(method);
SampleEmbeddable embeddable = new SampleEmbeddable();
new ParameterBinder(parameters, new Object[] { embeddable }).bind(query);
@@ -162,11 +172,53 @@ public class ParameterBinderUnitTests {
public void bindsSortForIndexedParameters() throws Exception {
Sort sort = new Sort("name");
ParameterBinder binder = new ParameterBinder(new Parameters(indexedParametersWithSort),
new Object[] { "name", sort });
ParameterBinder binder = new ParameterBinder(new JpaParameters(indexedParametersWithSort), new Object[] { "name",
sort });
assertThat(binder.getSort(), is(sort));
}
/**
* @see DATAJPA-107
*/
@Test
public void shouldSetTemporalQueryParameterToDate() throws Exception {
Method method = SampleRepository.class.getMethod("validWithDefaultTemporalTypeParameter", Date.class);
JpaParameters parameters = new JpaParameters(method);
Date date = new Date();
new ParameterBinder(parameters, new Object[] { date }).bind(query);
verify(query).setParameter(eq(1), eq(date), eq(TemporalType.DATE));
}
/**
* @see DATAJPA-107
*/
@Test
public void shouldSetTemporalQueryParameterToTimestamp() throws Exception {
Method method = SampleRepository.class.getMethod("validWithCustomTemporalTypeParameter", Date.class);
JpaParameters parameters = new JpaParameters(method);
Date date = new Date();
new ParameterBinder(parameters, new Object[] { date }).bind(query);
verify(query).setParameter(eq(1), eq(date), eq(TemporalType.TIMESTAMP));
}
/**
* @see DATAJPA-107
*/
@Test(expected = IllegalArgumentException.class)
public void shouldThrowIllegalArgumentExceptionIfIsAnnotatedWithTemporalParamAndParameterTypeIsNotDate()
throws Exception {
Method method = SampleRepository.class.getMethod("invalidWithTemporalTypeParameter", String.class);
JpaParameters parameters = new JpaParameters(method);
new ParameterBinder(parameters, new Object[] { "foo", "" });
}
public SampleEntity findByEmbeddable(SampleEmbeddable embeddable) {
return null;

View File

@@ -12,6 +12,7 @@ import javax.persistence.criteria.ParameterExpression;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.data.jpa.domain.sample.User;
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;
@@ -27,8 +28,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@ContextConfiguration("classpath:infrastructure.xml")
public class ParameterExpressionProviderTests {
@PersistenceContext
EntityManager em;
@PersistenceContext EntityManager em;
/**
* @see DATADOC-99
@@ -38,7 +38,7 @@ public class ParameterExpressionProviderTests {
public void createsParameterExpressionWithMostConcreteType() throws Exception {
Method method = SampleRepository.class.getMethod("findByIdGreaterThan", int.class);
Parameters parameters = new Parameters(method);
Parameters<?, ?> parameters = new DefaultParameters(method);
ParametersParameterAccessor accessor = new ParametersParameterAccessor(parameters, new Object[] { 1 });
Part part = new Part("IdGreaterThan", User.class);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2012 the original author or authors.
* Copyright 2011-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License
import org.springframework.aop.framework.Advised;
@@ -23,13 +23,21 @@ import static org.springframework.test.util.ReflectionTestUtils.*;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.Parameter;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.TemporalType;
import org.hibernate.ejb.HibernateQuery;
import org.hibernate.engine.TypedValue;
import org.hibernate.type.TimestampType;
import org.hibernate.type.Type;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -38,9 +46,11 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.sample.User;
import org.springframework.data.jpa.repository.Temporal;
import org.springframework.data.jpa.repository.support.PersistenceProvider;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.query.Param;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -53,11 +63,9 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@ContextConfiguration("classpath:infrastructure.xml")
public class PartTreeJpaQueryIntegrationTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Rule public ExpectedException thrown = ExpectedException.none();
@PersistenceContext
EntityManager entityManager;
@PersistenceContext EntityManager entityManager;
/**
* @see DATADOC-90
@@ -111,6 +119,32 @@ public class PartTreeJpaQueryIntegrationTests {
assertThat(hibernateQuery.getHibernateQuery().getQueryString(), endsWith("firstname is null"));
}
/**
* @throws Exception
* @see https://jira.springsource.org/browse/DATAJPA-107
*/
@Test
public void shouldSetTemporalQueryParameterToTimestamp() throws Exception {
Method method = UserRepository.class.getMethod("findByCreatedAtAfter", Date.class);
JpaQueryMethod queryMethod = new JpaQueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class),
PersistenceProvider.fromEntityManager(entityManager));
PartTreeJpaQuery jpaQuery = new PartTreeJpaQuery(queryMethod, entityManager);
Date date = new Date();
Query query = jpaQuery.createQuery(new Object[] { date });
HibernateQuery hibernateQuery = getValue(query, "h.target.val$jpaqlQuery");
Parameter<?> parameter = hibernateQuery.getParameter("param0");
Object parameterValue = hibernateQuery.getParameterValue(parameter);
Map<?, ?> namedParameterType = getValue(hibernateQuery.getHibernateQuery(), "namedParameters");
TypedValue refDateParam = (TypedValue) namedParameterType.get("param0");
assertThat(parameter, is(notNullValue()));
assertThat(hibernateQuery.getHibernateQuery().getQueryString(), endsWith("createdAt>:param0"));
assertThat(parameterValue, is((Object) date));
assertThat(refDateParam.getType(), is((Type) TimestampType.INSTANCE));
}
private void testIgnoreCase(String methodName, Object... values) throws Exception {
Class<?>[] parameterTypes = new Class[values.length];
@@ -144,5 +178,7 @@ public class PartTreeJpaQueryIntegrationTests {
User findByIdIgnoringCase(Integer id);
User findByIdAllIgnoringCase(Integer id);
List<User> findByCreatedAtAfter(@Temporal(TemporalType.TIMESTAMP) @Param("refDate") Date refDate);
}
}

View File

@@ -45,7 +45,6 @@ import org.springframework.data.jpa.repository.sample.UserRepository;
import org.springframework.data.jpa.repository.support.DefaultJpaEntityMetadata;
import org.springframework.data.jpa.repository.support.JpaEntityMetadata;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.Parameters;
/**
* Unit test for {@link SimpleJpaQuery}.
@@ -90,7 +89,7 @@ public class SimpleJpaQueryUnitTests {
method = mock(JpaQueryMethod.class);
when(method.getCountQuery()).thenReturn("foo");
when(method.getParameters()).thenReturn(
new Parameters(SimpleJpaQueryUnitTests.class.getMethod("prefersDeclaredCountQueryOverCreatingOne")));
new JpaParameters(SimpleJpaQueryUnitTests.class.getMethod("prefersDeclaredCountQueryOverCreatingOne")));
when(method.getEntityInformation()).thenReturn((JpaEntityMetadata) new DefaultJpaEntityMetadata<User>(User.class));
when(em.createQuery("foo", Long.class)).thenReturn(query);