Browse Source

DATAJPA-757 - Count queries for String-based native queries are now executed as native ones.

AbstractStringBasedJpaQuery now considers the native flag of the underlying JpaQueryMethod when creating count queries.

Had to widen the return type of   AbstractJpaQuery.createJpaQuery(…) to Query to accommodate the return value  EntityManager.createNativeQuery(…). Tweaked the implementation of PagedExecution to handle non-Long return values potentially converting them to longs to retain API.
pull/151/merge
Oliver Gierke 11 years ago
parent
commit
b4e7b096cd
  1. 6
      src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java
  2. 10
      src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java
  3. 19
      src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java
  4. 43
      src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java

6
src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java

@ -191,8 +191,8 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
return query; return query;
} }
protected TypedQuery<Long> createCountQuery(Object[] values) { protected Query createCountQuery(Object[] values) {
TypedQuery<Long> countQuery = doCreateCountQuery(values); Query countQuery = doCreateCountQuery(values);
return method.applyHintsToCountQuery() ? applyHints(countQuery, method) : countQuery; return method.applyHintsToCountQuery() ? applyHints(countQuery, method) : countQuery;
} }
@ -210,5 +210,5 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
* @param values must not be {@literal null}. * @param values must not be {@literal null}.
* @return * @return
*/ */
protected abstract TypedQuery<Long> doCreateCountQuery(Object[] values); protected abstract Query doCreateCountQuery(Object[] values);
} }

10
src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java

@ -17,7 +17,6 @@ package org.springframework.data.jpa.repository.query;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.Query; import javax.persistence.Query;
import javax.persistence.TypedQuery;
import org.springframework.data.repository.query.EvaluationContextProvider; import org.springframework.data.repository.query.EvaluationContextProvider;
import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.ParameterAccessor;
@ -105,8 +104,13 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
* @see org.springframework.data.jpa.repository.query.AbstractJpaQuery#doCreateCountQuery(java.lang.Object[]) * @see org.springframework.data.jpa.repository.query.AbstractJpaQuery#doCreateCountQuery(java.lang.Object[])
*/ */
@Override @Override
protected TypedQuery<Long> doCreateCountQuery(Object[] values) { protected Query doCreateCountQuery(Object[] values) {
return createBinder(values).bind(getEntityManager().createQuery(countQuery.getQueryString(), Long.class));
String queryString = countQuery.getQueryString();
EntityManager em = getEntityManager();
return createBinder(values).bind(
getQueryMethod().isNativeQuery() ? em.createNativeQuery(queryString) : em.createQuery(queryString, Long.class));
} }
/** /**

19
src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java

@ -22,11 +22,10 @@ import javax.persistence.EntityManager;
import javax.persistence.NoResultException; import javax.persistence.NoResultException;
import javax.persistence.Query; import javax.persistence.Query;
import javax.persistence.StoredProcedureQuery; import javax.persistence.StoredProcedureQuery;
import javax.persistence.TypedQuery;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice; import org.springframework.data.domain.Slice;
@ -53,7 +52,7 @@ public abstract class JpaQueryExecution {
static { static {
ConfigurableConversionService conversionService = new GenericConversionService(); ConfigurableConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(JpaResultConverters.BlobToByteArrayConverter.INSTANCE); conversionService.addConverter(JpaResultConverters.BlobToByteArrayConverter.INSTANCE);
CONVERSION_SERVICE = conversionService; CONVERSION_SERVICE = conversionService;
@ -90,8 +89,8 @@ public abstract class JpaQueryExecution {
return result; return result;
} }
return CONVERSION_SERVICE.canConvert(result.getClass(), requiredType) ? CONVERSION_SERVICE.convert(result, return CONVERSION_SERVICE.canConvert(result.getClass(), requiredType)
requiredType) : result; ? CONVERSION_SERVICE.convert(result, requiredType) : result;
} }
/** /**
@ -173,10 +172,10 @@ public abstract class JpaQueryExecution {
protected Object doExecute(AbstractJpaQuery repositoryQuery, Object[] values) { protected Object doExecute(AbstractJpaQuery repositoryQuery, Object[] values) {
// Execute query to compute total // Execute query to compute total
TypedQuery<Long> projection = repositoryQuery.createCountQuery(values); Query projection = repositoryQuery.createCountQuery(values);
List<Long> totals = projection.getResultList(); List<?> totals = projection.getResultList();
Long total = totals.size() == 1 ? totals.get(0) : totals.size(); Long total = totals.size() == 1 ? CONVERSION_SERVICE.convert(totals.get(0), Long.class) : totals.size();
ParameterAccessor accessor = new ParametersParameterAccessor(parameters, values); ParameterAccessor accessor = new ParametersParameterAccessor(parameters, values);
Pageable pageable = accessor.getPageable(); Pageable pageable = accessor.getPageable();
@ -187,8 +186,8 @@ public abstract class JpaQueryExecution {
Query query = repositoryQuery.createQuery(values); Query query = repositoryQuery.createQuery(values);
List<Object> content = pageable == null || total > pageable.getOffset() ? query.getResultList() : Collections List<Object> content = pageable == null || total > pageable.getOffset() ? query.getResultList()
.emptyList(); : Collections.emptyList();
return new PageImpl<Object>(content, pageable, total); return new PageImpl<Object>(content, pageable, total);
} }

43
src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2008-2014 the original author or authors. * Copyright 2008-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -44,8 +44,8 @@ import org.springframework.data.jpa.provider.QueryExtractor;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.sample.UserRepository; import org.springframework.data.jpa.repository.sample.UserRepository;
import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.DefaultEvaluationContextProvider;
import org.springframework.data.repository.query.EvaluationContextProvider; import org.springframework.data.repository.query.EvaluationContextProvider;
import org.springframework.data.repository.query.ExtensionAwareEvaluationContextProvider;
import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser;
@ -60,14 +60,15 @@ public class SimpleJpaQueryUnitTests {
static final String USER_QUERY = "select u from User u"; static final String USER_QUERY = "select u from User u";
static final SpelExpressionParser PARSER = new SpelExpressionParser(); static final SpelExpressionParser PARSER = new SpelExpressionParser();
private static final EvaluationContextProvider EVALUATION_CONTEXT_PROVIDER = DefaultEvaluationContextProvider.INSTANCE; private static final EvaluationContextProvider EVALUATION_CONTEXT_PROVIDER = new ExtensionAwareEvaluationContextProvider();
JpaQueryMethod method; JpaQueryMethod method;
@Mock EntityManager em; @Mock EntityManager em;
@Mock EntityManagerFactory emf; @Mock EntityManagerFactory emf;
@Mock QueryExtractor extractor; @Mock QueryExtractor extractor;
@Mock TypedQuery<Long> query; @Mock javax.persistence.Query query;
@Mock TypedQuery<Long> typedQuery;
@Mock RepositoryMetadata metadata; @Mock RepositoryMetadata metadata;
@Mock ParameterBinder binder; @Mock ParameterBinder binder;
@ -78,7 +79,7 @@ public class SimpleJpaQueryUnitTests {
public void setUp() throws SecurityException, NoSuchMethodException { public void setUp() throws SecurityException, NoSuchMethodException {
when(em.createQuery(anyString())).thenReturn(query); when(em.createQuery(anyString())).thenReturn(query);
when(em.createQuery(anyString(), eq(Long.class))).thenReturn(query); when(em.createQuery(anyString(), eq(Long.class))).thenReturn(typedQuery);
when(em.getEntityManagerFactory()).thenReturn(emf); when(em.getEntityManagerFactory()).thenReturn(emf);
when(emf.createEntityManager()).thenReturn(em); when(emf.createEntityManager()).thenReturn(em);
when(metadata.getDomainType()).thenReturn((Class) User.class); when(metadata.getDomainType()).thenReturn((Class) User.class);
@ -92,13 +93,13 @@ public class SimpleJpaQueryUnitTests {
public void prefersDeclaredCountQueryOverCreatingOne() throws Exception { public void prefersDeclaredCountQueryOverCreatingOne() throws Exception {
method = new JpaQueryMethod(SimpleJpaQueryUnitTests.class.getMethod("prefersDeclaredCountQueryOverCreatingOne"), method = new JpaQueryMethod(SimpleJpaQueryUnitTests.class.getMethod("prefersDeclaredCountQueryOverCreatingOne"),
metadata, extractor); // mock(JpaQueryMethod.class); metadata, extractor);
when(em.createQuery("foo", Long.class)).thenReturn(query); when(em.createQuery("foo", Long.class)).thenReturn(typedQuery);
SimpleJpaQuery jpaQuery = new SimpleJpaQuery(method, em, "select u from User u", EVALUATION_CONTEXT_PROVIDER, SimpleJpaQuery jpaQuery = new SimpleJpaQuery(method, em, "select u from User u", EVALUATION_CONTEXT_PROVIDER,
PARSER); PARSER);
assertThat(jpaQuery.createCountQuery(new Object[] {}), is(query)); assertThat(jpaQuery.createCountQuery(new Object[] {}), is((javax.persistence.Query) typedQuery));
} }
/** /**
@ -112,8 +113,8 @@ public class SimpleJpaQueryUnitTests {
Method method = UserRepository.class.getMethod("findAllPaged", Pageable.class); Method method = UserRepository.class.getMethod("findAllPaged", Pageable.class);
JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, extractor); JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, extractor);
AbstractJpaQuery jpaQuery = new SimpleJpaQuery(queryMethod, em, "select u from User u", AbstractJpaQuery jpaQuery = new SimpleJpaQuery(queryMethod, em, "select u from User u", EVALUATION_CONTEXT_PROVIDER,
EVALUATION_CONTEXT_PROVIDER, PARSER); PARSER);
jpaQuery.createCountQuery(new Object[] { new PageRequest(1, 10) }); jpaQuery.createCountQuery(new Object[] { new PageRequest(1, 10) });
verify(query, times(0)).setFirstResult(anyInt()); verify(query, times(0)).setFirstResult(anyInt());
@ -169,7 +170,7 @@ public class SimpleJpaQueryUnitTests {
public void doesNotValidateCountQueryIfNotPagingMethod() throws Exception { public void doesNotValidateCountQueryIfNotPagingMethod() throws Exception {
Method method = SampleRepository.class.getMethod("findByAnnotatedQuery"); Method method = SampleRepository.class.getMethod("findByAnnotatedQuery");
when(em.createQuery(contains("count"))).thenThrow(IllegalArgumentException.class); when(em.createQuery(Mockito.contains("count"))).thenThrow(IllegalArgumentException.class);
createJpaQuery(method); createJpaQuery(method);
} }
@ -183,7 +184,7 @@ public class SimpleJpaQueryUnitTests {
Method method = SampleRepository.class.getMethod("pageByAnnotatedQuery", Pageable.class); Method method = SampleRepository.class.getMethod("pageByAnnotatedQuery", Pageable.class);
when(em.createQuery(contains("count"))).thenThrow(IllegalArgumentException.class); when(em.createQuery(Mockito.contains("count"))).thenThrow(IllegalArgumentException.class);
exception.expect(IllegalArgumentException.class); exception.expect(IllegalArgumentException.class);
exception.expectMessage("Count"); exception.expectMessage("Count");
exception.expectMessage(method.getName()); exception.expectMessage(method.getName());
@ -205,7 +206,23 @@ public class SimpleJpaQueryUnitTests {
assertThat(query instanceof NativeJpaQuery, is(true)); assertThat(query instanceof NativeJpaQuery, is(true));
} }
private RepositoryQuery createJpaQuery(Method method) { /**
* @see DATAJPA-757
*/
@Test
public void createsNativeCountQuery() throws Exception {
when(em.createNativeQuery(anyString())).thenReturn(query);
AbstractJpaQuery jpaQuery = createJpaQuery(
UserRepository.class.getMethod("findUsersInNativeQueryWithPagination", Pageable.class));
jpaQuery.doCreateCountQuery(new Object[] { new PageRequest(0, 10) });
verify(em, times(1)).createNativeQuery(anyString());
}
private AbstractJpaQuery createJpaQuery(Method method) {
JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, extractor); JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, extractor);
return JpaQueryFactory.INSTANCE.fromQueryAnnotation(queryMethod, em, EVALUATION_CONTEXT_PROVIDER); return JpaQueryFactory.INSTANCE.fromQueryAnnotation(queryMethod, em, EVALUATION_CONTEXT_PROVIDER);

Loading…
Cancel
Save