DATAJPA-928 - Enabled native query with Pageable.
Just removed the check that was actively preventing the use of Pageable. Migrated to tests to AssertJ where applicable. Original pull request: #246.
This commit is contained in:
committed by
Oliver Gierke
parent
5dacc87694
commit
8bf8e3acaf
@@ -34,6 +34,7 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
final class NativeJpaQuery extends AbstractStringBasedJpaQuery {
|
||||
|
||||
@@ -53,13 +54,10 @@ final class NativeJpaQuery extends AbstractStringBasedJpaQuery {
|
||||
super(method, em, queryString, evaluationContextProvider, parser);
|
||||
|
||||
Parameters<?, ?> parameters = method.getParameters();
|
||||
boolean hasPagingOrSortingParameter = parameters.hasPageableParameter() || parameters.hasSortParameter();
|
||||
boolean containsPageableOrSortInQueryExpression = queryString.contains("#pageable")
|
||||
|| queryString.contains("#sort");
|
||||
|
||||
if (hasPagingOrSortingParameter && !containsPageableOrSortInQueryExpression) {
|
||||
if (parameters.hasSortParameter() && !queryString.contains("#sort")) {
|
||||
throw new InvalidJpaQueryMethodException(
|
||||
"Cannot use native queries with dynamic sorting and/or pagination in method " + method);
|
||||
"Cannot use native queries with dynamic sorting in method " + method);
|
||||
}
|
||||
|
||||
this.resultType = getTypeToQueryFor();
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.Set;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.ColumnResult;
|
||||
import javax.persistence.ElementCollection;
|
||||
import javax.persistence.Embedded;
|
||||
import javax.persistence.Entity;
|
||||
@@ -34,11 +35,15 @@ import javax.persistence.ManyToOne;
|
||||
import javax.persistence.NamedAttributeNode;
|
||||
import javax.persistence.NamedEntityGraph;
|
||||
import javax.persistence.NamedEntityGraphs;
|
||||
import javax.persistence.NamedNativeQueries;
|
||||
import javax.persistence.NamedNativeQuery;
|
||||
import javax.persistence.NamedQuery;
|
||||
import javax.persistence.NamedStoredProcedureQueries;
|
||||
import javax.persistence.NamedStoredProcedureQuery;
|
||||
import javax.persistence.NamedSubgraph;
|
||||
import javax.persistence.ParameterMode;
|
||||
import javax.persistence.SqlResultSetMapping;
|
||||
import javax.persistence.SqlResultSetMappings;
|
||||
import javax.persistence.StoredProcedureParameter;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Temporal;
|
||||
@@ -51,6 +56,7 @@ import javax.persistence.TemporalType;
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
@Entity
|
||||
@NamedEntityGraphs({ @NamedEntityGraph(name = "User.overview", attributeNodes = { @NamedAttributeNode("roles") }),
|
||||
@@ -61,11 +67,10 @@ import javax.persistence.TemporalType;
|
||||
attributeNodes = { @NamedAttributeNode("roles"), @NamedAttributeNode("manager"),
|
||||
@NamedAttributeNode("colleagues") }),
|
||||
@NamedEntityGraph(name = "User.withSubGraph",
|
||||
attributeNodes = { @NamedAttributeNode("roles"),
|
||||
@NamedAttributeNode(value = "colleagues", subgraph = "User.colleagues") },
|
||||
attributeNodes = { @NamedAttributeNode("roles"), @NamedAttributeNode(value = "colleagues",
|
||||
subgraph = "User.colleagues") },
|
||||
subgraphs = { @NamedSubgraph(name = "User.colleagues",
|
||||
attributeNodes = { @NamedAttributeNode("colleagues"),
|
||||
@NamedAttributeNode("roles") }) }),
|
||||
attributeNodes = { @NamedAttributeNode("colleagues"), @NamedAttributeNode("roles") }) }),
|
||||
@NamedEntityGraph(name = "User.deepGraph",
|
||||
attributeNodes = { @NamedAttributeNode("roles"),
|
||||
@NamedAttributeNode(value = "colleagues", subgraph = "User.colleagues") },
|
||||
@@ -84,6 +89,15 @@ import javax.persistence.TemporalType;
|
||||
@NamedStoredProcedureQuery(name = "User.plus1IO", procedureName = "plus1inout",
|
||||
parameters = { @StoredProcedureParameter(mode = ParameterMode.IN, name = "arg", type = Integer.class),
|
||||
@StoredProcedureParameter(mode = ParameterMode.OUT, name = "res", type = Integer.class) })
|
||||
|
||||
// Annotations for native Query with pageable
|
||||
@SqlResultSetMappings({
|
||||
@SqlResultSetMapping(name = "SqlResultSetMapping.count", columns = @ColumnResult(name = "cnt")) })
|
||||
@NamedNativeQueries({
|
||||
@NamedNativeQuery(name = "User.findByNativeNamedQueryWithPageable", resultClass = User.class,
|
||||
query = "SELECT * FROM SD_USER ORDER BY UCASE(firstname)"),
|
||||
@NamedNativeQuery(name = "User.findByNativeNamedQueryWithPageable.count",
|
||||
resultSetMapping = "SqlResultSetMapping.count", query = "SELECT count(*) AS cnt FROM SD_USER") })
|
||||
@Table(name = "SD_User")
|
||||
public class User {
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
||||
import org.assertj.core.api.SoftAssertions;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
@@ -2109,6 +2110,56 @@ public class UserRepositoryTests {
|
||||
repository.findAllOrderedBySpecialNameMultipleParamsIndexed("Oliver", "x", PageRequest.of(2, 3));
|
||||
}
|
||||
|
||||
// DATAJPA-928
|
||||
@Test
|
||||
public void executeNativeQueryWithPage() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
Page<User> firstPage = repository.findByNativeNamedQueryWithPageable(new PageRequest(0, 3));
|
||||
Page<User> secondPage = repository.findByNativeNamedQueryWithPageable(new PageRequest(1, 3));
|
||||
|
||||
SoftAssertions softly = new SoftAssertions();
|
||||
|
||||
assertThat(firstPage.getTotalElements()).isEqualTo(4L);
|
||||
assertThat(firstPage.getNumberOfElements()).isEqualTo(3);
|
||||
assertThat(firstPage.getContent()) //
|
||||
.extracting(User::getFirstname) //
|
||||
.containsExactly("Dave", "Joachim", "kevin");
|
||||
|
||||
assertThat(secondPage.getTotalElements()).isEqualTo(4L);
|
||||
assertThat(secondPage.getNumberOfElements()).isEqualTo(1);
|
||||
assertThat(secondPage.getContent()) //
|
||||
.extracting(User::getFirstname) //
|
||||
.containsExactly("Oliver");
|
||||
|
||||
softly.assertAll();
|
||||
}
|
||||
|
||||
// DATAJPA-928
|
||||
@Test
|
||||
public void executeNativeQueryWithPageWorkaround() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
Page<String> firstPage = repository.findByNativeQueryWithPageable(new PageRequest(0, 3));
|
||||
Page<String> secondPage = repository.findByNativeQueryWithPageable(new PageRequest(1, 3));
|
||||
|
||||
SoftAssertions softly = new SoftAssertions();
|
||||
|
||||
assertThat(firstPage.getTotalElements()).isEqualTo(4L);
|
||||
assertThat(firstPage.getNumberOfElements()).isEqualTo(3);
|
||||
assertThat(firstPage.getContent()) //
|
||||
.containsExactly("Dave", "Joachim", "kevin");
|
||||
|
||||
assertThat(secondPage.getTotalElements()).isEqualTo(4L);
|
||||
assertThat(secondPage.getNumberOfElements()).isEqualTo(1);
|
||||
assertThat(secondPage.getContent()) //
|
||||
.containsExactly("Oliver");
|
||||
|
||||
softly.assertAll();
|
||||
}
|
||||
|
||||
private Page<User> executeSpecWithSort(Sort sort) {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
@@ -15,26 +15,23 @@
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.metamodel.Metamodel;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.domain.sample.User;
|
||||
import org.springframework.data.jpa.provider.QueryExtractor;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
@@ -53,6 +50,7 @@ import org.springframework.data.repository.query.QueryLookupStrategy.Key;
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class JpaQueryLookupStrategyUnitTests {
|
||||
@@ -65,8 +63,6 @@ public class JpaQueryLookupStrategyUnitTests {
|
||||
@Mock Metamodel metamodel;
|
||||
@Mock ProjectionFactory projectionFactory;
|
||||
|
||||
public @Rule ExpectedException exception = ExpectedException.none();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
@@ -87,12 +83,9 @@ public class JpaQueryLookupStrategyUnitTests {
|
||||
Throwable reference = new RuntimeException();
|
||||
when(em.createQuery(anyString())).thenThrow(reference);
|
||||
|
||||
try {
|
||||
strategy.resolveQuery(method, metadata, projectionFactory, namedQueries);
|
||||
} catch (Exception e) {
|
||||
assertThat(e, is(instanceOf(IllegalArgumentException.class)));
|
||||
assertThat(e.getCause(), is(reference));
|
||||
}
|
||||
assertThatExceptionOfType(IllegalArgumentException.class)
|
||||
.isThrownBy(() -> strategy.resolveQuery(method, metadata, projectionFactory, namedQueries))
|
||||
.withCause(reference);
|
||||
}
|
||||
|
||||
@Test // DATAJPA-554
|
||||
@@ -100,14 +93,13 @@ public class JpaQueryLookupStrategyUnitTests {
|
||||
|
||||
QueryLookupStrategy strategy = JpaQueryLookupStrategy.create(em, Key.CREATE_IF_NOT_FOUND, extractor,
|
||||
EVALUATION_CONTEXT_PROVIDER);
|
||||
Method method = UserRepository.class.getMethod("findByInvalidNativeQuery", String.class, Pageable.class);
|
||||
Method method = UserRepository.class.getMethod("findByInvalidNativeQuery", String.class, Sort.class);
|
||||
RepositoryMetadata metadata = new DefaultRepositoryMetadata(UserRepository.class);
|
||||
|
||||
exception.expect(InvalidJpaQueryMethodException.class);
|
||||
exception.expectMessage("Cannot use native queries with dynamic sorting and/or pagination in method");
|
||||
exception.expectMessage(method.toString());
|
||||
|
||||
strategy.resolveQuery(method, metadata, projectionFactory, namedQueries);
|
||||
assertThatExceptionOfType(InvalidJpaQueryMethodException.class)
|
||||
.isThrownBy(() -> strategy.resolveQuery(method, metadata, projectionFactory, namedQueries))
|
||||
.withMessageContaining("Cannot use native queries with dynamic sorting in method")
|
||||
.withMessageContaining(method.toString());
|
||||
}
|
||||
|
||||
interface UserRepository extends Repository<User, Long> {
|
||||
@@ -116,6 +108,6 @@ public class JpaQueryLookupStrategyUnitTests {
|
||||
User findByFoo(String foo);
|
||||
|
||||
@Query(value = "select u.* from User u", nativeQuery = true)
|
||||
Page<User> findByInvalidNativeQuery(String param, Pageable page);
|
||||
List<User> findByInvalidNativeQuery(String param, Sort sort);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.Silent.class)
|
||||
public class SimpleJpaQueryUnitTests {
|
||||
@@ -153,13 +154,6 @@ public class SimpleJpaQueryUnitTests {
|
||||
createJpaQuery(method);
|
||||
}
|
||||
|
||||
@Test(expected = InvalidJpaQueryMethodException.class) // DATAJPA-554
|
||||
public void rejectsNativeQueryWithPageable() throws Exception {
|
||||
|
||||
Method method = SampleRepository.class.getMethod("findNativeByLastname", String.class, Pageable.class);
|
||||
createJpaQuery(method);
|
||||
}
|
||||
|
||||
@Test // DATAJPA-352
|
||||
@SuppressWarnings("unchecked")
|
||||
public void doesNotValidateCountQueryIfNotPagingMethod() throws Exception {
|
||||
|
||||
@@ -529,6 +529,13 @@ public interface UserRepository
|
||||
@Query(value = "SELECT u FROM User u WHERE ?2 = 'x' ORDER BY CASE WHEN (u.firstname >= ?1) THEN 0 ELSE 1 END, u.firstname")
|
||||
Page<User> findAllOrderedBySpecialNameMultipleParamsIndexed(String name, String other, Pageable page);
|
||||
|
||||
// DATAJPA-928
|
||||
Page<User> findByNativeNamedQueryWithPageable(Pageable pageable);
|
||||
|
||||
// DATAJPA-928
|
||||
@Query(value = "SELECT firstname FROM SD_User ORDER BY UCASE(firstname)", countQuery = "SELECT count(*) FROM SD_User", nativeQuery = true)
|
||||
Page<String> findByNativeQueryWithPageable(@Param("pageable") Pageable pageable);
|
||||
|
||||
interface RolesAndFirstname {
|
||||
|
||||
String getFirstname();
|
||||
|
||||
Reference in New Issue
Block a user