diff --git a/pom.xml b/pom.xml
index e05684db9e..9654a0f5ee 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-jpa
- 2.1.0.BUILD-SNAPSHOT
+ 2.1.0.DATAJPA-928-SNAPSHOT
Spring Data JPA
Spring Data module for JPA repositories.
diff --git a/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java
index 72be4f8eaa..277954da28 100644
--- a/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java
+++ b/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java
@@ -34,6 +34,7 @@
*
* @author Thomas Darimont
* @author Oliver Gierke
+ * @author Jens Schauder
*/
final class NativeJpaQuery extends AbstractStringBasedJpaQuery {
@@ -53,13 +54,10 @@ public NativeJpaQuery(JpaQueryMethod method, EntityManager em, String queryStrin
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();
diff --git a/src/test/java/org/springframework/data/jpa/domain/sample/User.java b/src/test/java/org/springframework/data/jpa/domain/sample/User.java
index f77e20e6f5..cf19f8126c 100644
--- a/src/test/java/org/springframework/data/jpa/domain/sample/User.java
+++ b/src/test/java/org/springframework/data/jpa/domain/sample/User.java
@@ -20,29 +20,7 @@
import java.util.HashSet;
import java.util.Set;
-import javax.persistence.CascadeType;
-import javax.persistence.Column;
-import javax.persistence.ElementCollection;
-import javax.persistence.Embedded;
-import javax.persistence.Entity;
-import javax.persistence.GeneratedValue;
-import javax.persistence.GenerationType;
-import javax.persistence.Id;
-import javax.persistence.Lob;
-import javax.persistence.ManyToMany;
-import javax.persistence.ManyToOne;
-import javax.persistence.NamedAttributeNode;
-import javax.persistence.NamedEntityGraph;
-import javax.persistence.NamedEntityGraphs;
-import javax.persistence.NamedQuery;
-import javax.persistence.NamedStoredProcedureQueries;
-import javax.persistence.NamedStoredProcedureQuery;
-import javax.persistence.NamedSubgraph;
-import javax.persistence.ParameterMode;
-import javax.persistence.StoredProcedureParameter;
-import javax.persistence.Table;
-import javax.persistence.Temporal;
-import javax.persistence.TemporalType;
+import javax.persistence.*;
/**
* Domain class representing a person emphasizing the use of {@code AbstractEntity}. No declaration of an id is
@@ -51,6 +29,7 @@
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
+ * @author Jens Schauder
*/
@Entity
@NamedEntityGraphs({ @NamedEntityGraph(name = "User.overview", attributeNodes = { @NamedAttributeNode("roles") }),
@@ -84,6 +63,23 @@
@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 {
diff --git a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java
index a2543a0236..7bfd7af365 100644
--- a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java
+++ b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java
@@ -40,6 +40,7 @@
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;
@@ -50,8 +51,6 @@
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
-import org.springframework.data.domain.ExampleMatcher.GenericPropertyMatcher;
-import org.springframework.data.domain.ExampleMatcher.StringMatcher;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
@@ -60,6 +59,7 @@
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
+import org.springframework.data.domain.ExampleMatcher.*;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.domain.sample.Address;
import org.springframework.data.jpa.domain.sample.Role;
@@ -2059,8 +2059,8 @@ public void supportsProjectionsWithNativeQueries() {
assertThat(result.getLastname()).isEqualTo(user.getLastname());
}
- @Test //DATAJPA-1235
- public void handlesColonsFollowedByIntegerInStringLiteral(){
+ @Test // DATAJPA-1235
+ public void handlesColonsFollowedByIntegerInStringLiteral() {
String firstName = "aFirstName";
@@ -2077,6 +2077,56 @@ public void handlesColonsFollowedByIntegerInStringLiteral(){
assertThat(users).extracting(User::getId).containsExactly(expected.getId());
}
+ // DATAJPA-928
+ @Test
+ public void executeNativeQueryWithPage() {
+
+ flushTestUsers();
+
+ Page firstPage = repository.findByNativeNamedQueryWithPageable(new PageRequest(0, 3));
+ Page 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 firstPage = repository.findByNativeQueryWithPageable(new PageRequest(0, 3));
+ Page 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 executeSpecWithSort(Sort sort) {
flushTestUsers();
diff --git a/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java
index ef00842040..4e01f01da5 100644
--- a/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java
+++ b/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java
@@ -15,26 +15,23 @@
*/
package org.springframework.data.jpa.repository.query;
-import static org.hamcrest.CoreMatchers.*;
-import static org.junit.Assert.*;
-import static org.mockito.ArgumentMatchers.*;
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.anyString;
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 @@
*
* @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 void invalidAnnotatedQueryCausesException() throws Exception {
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 void sholdThrowMorePreciseExceptionIfTryingToUsePaginationInNativeQueries
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 {
@@ -116,6 +108,6 @@ interface UserRepository extends Repository {
User findByFoo(String foo);
@Query(value = "select u.* from User u", nativeQuery = true)
- Page findByInvalidNativeQuery(String param, Pageable page);
+ List findByInvalidNativeQuery(String param, Sort sort);
}
}
diff --git a/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java
index a8cad5954d..3ad4fc260b 100644
--- a/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java
+++ b/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java
@@ -17,7 +17,9 @@
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
-import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
@@ -59,6 +61,7 @@
*
* @author Oliver Gierke
* @author Thomas Darimont
+ * @author Jens Schauder
*/
@RunWith(MockitoJUnitRunner.Silent.class)
public class SimpleJpaQueryUnitTests {
@@ -153,13 +156,6 @@ public void rejectsNativeQueryWithDynamicSort() throws Exception {
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 {
diff --git a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java
index 66944af650..12f29fdf5a 100644
--- a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java
+++ b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java
@@ -513,6 +513,15 @@ List findUsersByFirstnameForSpELExpressionWithParameterIndexOnlyWithEntity
@Query("SELECT u FROM User u where u.firstname >= ?1 and u.lastname = '000:1'")
List queryWithIndexedParameterAndColonFollowedByIntegerInString(String firstname);
+
+ // DATAJPA-928
+ Page 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 findByNativeQueryWithPageable(@Param("pageable") Pageable pageable);
+
static interface RolesAndFirstname {
String getFirstname();