Skip to content

Commit 68efc51

Browse files
schauderodrotbohm
authored andcommitted
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.
1 parent 02bb54e commit 68efc51

File tree

6 files changed

+93
-37
lines changed

6 files changed

+93
-37
lines changed

src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java

+3-5
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
*
3535
* @author Thomas Darimont
3636
* @author Oliver Gierke
37+
* @author Jens Schauder
3738
*/
3839
final class NativeJpaQuery extends AbstractStringBasedJpaQuery {
3940

@@ -53,13 +54,10 @@ public NativeJpaQuery(JpaQueryMethod method, EntityManager em, String queryStrin
5354
super(method, em, queryString, evaluationContextProvider, parser);
5455

5556
Parameters<?, ?> parameters = method.getParameters();
56-
boolean hasPagingOrSortingParameter = parameters.hasPageableParameter() || parameters.hasSortParameter();
57-
boolean containsPageableOrSortInQueryExpression = queryString.contains("#pageable")
58-
|| queryString.contains("#sort");
5957

60-
if (hasPagingOrSortingParameter && !containsPageableOrSortInQueryExpression) {
58+
if (parameters.hasSortParameter() && !queryString.contains("#sort")) {
6159
throw new InvalidJpaQueryMethodException(
62-
"Cannot use native queries with dynamic sorting and/or pagination in method " + method);
60+
"Cannot use native queries with dynamic sorting in method " + method);
6361
}
6462

6563
this.resultType = getTypeToQueryFor();

src/test/java/org/springframework/data/jpa/domain/sample/User.java

+18-4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import javax.persistence.CascadeType;
2424
import javax.persistence.Column;
25+
import javax.persistence.ColumnResult;
2526
import javax.persistence.ElementCollection;
2627
import javax.persistence.Embedded;
2728
import javax.persistence.Entity;
@@ -34,11 +35,15 @@
3435
import javax.persistence.NamedAttributeNode;
3536
import javax.persistence.NamedEntityGraph;
3637
import javax.persistence.NamedEntityGraphs;
38+
import javax.persistence.NamedNativeQueries;
39+
import javax.persistence.NamedNativeQuery;
3740
import javax.persistence.NamedQuery;
3841
import javax.persistence.NamedStoredProcedureQueries;
3942
import javax.persistence.NamedStoredProcedureQuery;
4043
import javax.persistence.NamedSubgraph;
4144
import javax.persistence.ParameterMode;
45+
import javax.persistence.SqlResultSetMapping;
46+
import javax.persistence.SqlResultSetMappings;
4247
import javax.persistence.StoredProcedureParameter;
4348
import javax.persistence.Table;
4449
import javax.persistence.Temporal;
@@ -51,6 +56,7 @@
5156
* @author Oliver Gierke
5257
* @author Thomas Darimont
5358
* @author Christoph Strobl
59+
* @author Jens Schauder
5460
*/
5561
@Entity
5662
@NamedEntityGraphs({ @NamedEntityGraph(name = "User.overview", attributeNodes = { @NamedAttributeNode("roles") }),
@@ -61,11 +67,10 @@
6167
attributeNodes = { @NamedAttributeNode("roles"), @NamedAttributeNode("manager"),
6268
@NamedAttributeNode("colleagues") }),
6369
@NamedEntityGraph(name = "User.withSubGraph",
64-
attributeNodes = { @NamedAttributeNode("roles"),
65-
@NamedAttributeNode(value = "colleagues", subgraph = "User.colleagues") },
70+
attributeNodes = { @NamedAttributeNode("roles"), @NamedAttributeNode(value = "colleagues",
71+
subgraph = "User.colleagues") },
6672
subgraphs = { @NamedSubgraph(name = "User.colleagues",
67-
attributeNodes = { @NamedAttributeNode("colleagues"),
68-
@NamedAttributeNode("roles") }) }),
73+
attributeNodes = { @NamedAttributeNode("colleagues"), @NamedAttributeNode("roles") }) }),
6974
@NamedEntityGraph(name = "User.deepGraph",
7075
attributeNodes = { @NamedAttributeNode("roles"),
7176
@NamedAttributeNode(value = "colleagues", subgraph = "User.colleagues") },
@@ -84,6 +89,15 @@
8489
@NamedStoredProcedureQuery(name = "User.plus1IO", procedureName = "plus1inout",
8590
parameters = { @StoredProcedureParameter(mode = ParameterMode.IN, name = "arg", type = Integer.class),
8691
@StoredProcedureParameter(mode = ParameterMode.OUT, name = "res", type = Integer.class) })
92+
93+
// Annotations for native Query with pageable
94+
@SqlResultSetMappings({
95+
@SqlResultSetMapping(name = "SqlResultSetMapping.count", columns = @ColumnResult(name = "cnt")) })
96+
@NamedNativeQueries({
97+
@NamedNativeQuery(name = "User.findByNativeNamedQueryWithPageable", resultClass = User.class,
98+
query = "SELECT * FROM SD_USER ORDER BY UCASE(firstname)"),
99+
@NamedNativeQuery(name = "User.findByNativeNamedQueryWithPageable.count",
100+
resultSetMapping = "SqlResultSetMapping.count", query = "SELECT count(*) AS cnt FROM SD_USER") })
87101
@Table(name = "SD_User")
88102
public class User {
89103

src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java

+51
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import javax.persistence.criteria.Predicate;
4141
import javax.persistence.criteria.Root;
4242

43+
import org.assertj.core.api.SoftAssertions;
4344
import org.junit.Before;
4445
import org.junit.Ignore;
4546
import org.junit.Test;
@@ -2109,6 +2110,56 @@ public void handlesCountQueriesWithLessParametersMoreThanOneIndexed() {
21092110
repository.findAllOrderedBySpecialNameMultipleParamsIndexed("Oliver", "x", PageRequest.of(2, 3));
21102111
}
21112112

2113+
// DATAJPA-928
2114+
@Test
2115+
public void executeNativeQueryWithPage() {
2116+
2117+
flushTestUsers();
2118+
2119+
Page<User> firstPage = repository.findByNativeNamedQueryWithPageable(new PageRequest(0, 3));
2120+
Page<User> secondPage = repository.findByNativeNamedQueryWithPageable(new PageRequest(1, 3));
2121+
2122+
SoftAssertions softly = new SoftAssertions();
2123+
2124+
assertThat(firstPage.getTotalElements()).isEqualTo(4L);
2125+
assertThat(firstPage.getNumberOfElements()).isEqualTo(3);
2126+
assertThat(firstPage.getContent()) //
2127+
.extracting(User::getFirstname) //
2128+
.containsExactly("Dave", "Joachim", "kevin");
2129+
2130+
assertThat(secondPage.getTotalElements()).isEqualTo(4L);
2131+
assertThat(secondPage.getNumberOfElements()).isEqualTo(1);
2132+
assertThat(secondPage.getContent()) //
2133+
.extracting(User::getFirstname) //
2134+
.containsExactly("Oliver");
2135+
2136+
softly.assertAll();
2137+
}
2138+
2139+
// DATAJPA-928
2140+
@Test
2141+
public void executeNativeQueryWithPageWorkaround() {
2142+
2143+
flushTestUsers();
2144+
2145+
Page<String> firstPage = repository.findByNativeQueryWithPageable(new PageRequest(0, 3));
2146+
Page<String> secondPage = repository.findByNativeQueryWithPageable(new PageRequest(1, 3));
2147+
2148+
SoftAssertions softly = new SoftAssertions();
2149+
2150+
assertThat(firstPage.getTotalElements()).isEqualTo(4L);
2151+
assertThat(firstPage.getNumberOfElements()).isEqualTo(3);
2152+
assertThat(firstPage.getContent()) //
2153+
.containsExactly("Dave", "Joachim", "kevin");
2154+
2155+
assertThat(secondPage.getTotalElements()).isEqualTo(4L);
2156+
assertThat(secondPage.getNumberOfElements()).isEqualTo(1);
2157+
assertThat(secondPage.getContent()) //
2158+
.containsExactly("Oliver");
2159+
2160+
softly.assertAll();
2161+
}
2162+
21122163
private Page<User> executeSpecWithSort(Sort sort) {
21132164

21142165
flushTestUsers();

src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java

+13-21
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,23 @@
1515
*/
1616
package org.springframework.data.jpa.repository.query;
1717

18-
import static org.hamcrest.CoreMatchers.*;
19-
import static org.junit.Assert.*;
18+
import static org.assertj.core.api.Assertions.*;
2019
import static org.mockito.ArgumentMatchers.*;
2120
import static org.mockito.Mockito.*;
2221

2322
import java.lang.reflect.Method;
23+
import java.util.List;
2424

2525
import javax.persistence.EntityManager;
2626
import javax.persistence.EntityManagerFactory;
2727
import javax.persistence.metamodel.Metamodel;
2828

2929
import org.junit.Before;
30-
import org.junit.Rule;
3130
import org.junit.Test;
32-
import org.junit.rules.ExpectedException;
3331
import org.junit.runner.RunWith;
3432
import org.mockito.Mock;
3533
import org.mockito.junit.MockitoJUnitRunner;
36-
import org.springframework.data.domain.Page;
37-
import org.springframework.data.domain.Pageable;
34+
import org.springframework.data.domain.Sort;
3835
import org.springframework.data.jpa.domain.sample.User;
3936
import org.springframework.data.jpa.provider.QueryExtractor;
4037
import org.springframework.data.jpa.repository.Query;
@@ -53,6 +50,7 @@
5350
*
5451
* @author Oliver Gierke
5552
* @author Thomas Darimont
53+
* @author Jens Schauder
5654
*/
5755
@RunWith(MockitoJUnitRunner.class)
5856
public class JpaQueryLookupStrategyUnitTests {
@@ -65,8 +63,6 @@ public class JpaQueryLookupStrategyUnitTests {
6563
@Mock Metamodel metamodel;
6664
@Mock ProjectionFactory projectionFactory;
6765

68-
public @Rule ExpectedException exception = ExpectedException.none();
69-
7066
@Before
7167
public void setUp() {
7268

@@ -87,27 +83,23 @@ public void invalidAnnotatedQueryCausesException() throws Exception {
8783
Throwable reference = new RuntimeException();
8884
when(em.createQuery(anyString())).thenThrow(reference);
8985

90-
try {
91-
strategy.resolveQuery(method, metadata, projectionFactory, namedQueries);
92-
} catch (Exception e) {
93-
assertThat(e, is(instanceOf(IllegalArgumentException.class)));
94-
assertThat(e.getCause(), is(reference));
95-
}
86+
assertThatExceptionOfType(IllegalArgumentException.class)
87+
.isThrownBy(() -> strategy.resolveQuery(method, metadata, projectionFactory, namedQueries))
88+
.withCause(reference);
9689
}
9790

9891
@Test // DATAJPA-554
9992
public void sholdThrowMorePreciseExceptionIfTryingToUsePaginationInNativeQueries() throws Exception {
10093

10194
QueryLookupStrategy strategy = JpaQueryLookupStrategy.create(em, Key.CREATE_IF_NOT_FOUND, extractor,
10295
EVALUATION_CONTEXT_PROVIDER);
103-
Method method = UserRepository.class.getMethod("findByInvalidNativeQuery", String.class, Pageable.class);
96+
Method method = UserRepository.class.getMethod("findByInvalidNativeQuery", String.class, Sort.class);
10497
RepositoryMetadata metadata = new DefaultRepositoryMetadata(UserRepository.class);
10598

106-
exception.expect(InvalidJpaQueryMethodException.class);
107-
exception.expectMessage("Cannot use native queries with dynamic sorting and/or pagination in method");
108-
exception.expectMessage(method.toString());
109-
110-
strategy.resolveQuery(method, metadata, projectionFactory, namedQueries);
99+
assertThatExceptionOfType(InvalidJpaQueryMethodException.class)
100+
.isThrownBy(() -> strategy.resolveQuery(method, metadata, projectionFactory, namedQueries))
101+
.withMessageContaining("Cannot use native queries with dynamic sorting in method")
102+
.withMessageContaining(method.toString());
111103
}
112104

113105
interface UserRepository extends Repository<User, Long> {
@@ -116,6 +108,6 @@ interface UserRepository extends Repository<User, Long> {
116108
User findByFoo(String foo);
117109

118110
@Query(value = "select u.* from User u", nativeQuery = true)
119-
Page<User> findByInvalidNativeQuery(String param, Pageable page);
111+
List<User> findByInvalidNativeQuery(String param, Sort sort);
120112
}
121113
}

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

+1-7
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
*
6060
* @author Oliver Gierke
6161
* @author Thomas Darimont
62+
* @author Jens Schauder
6263
*/
6364
@RunWith(MockitoJUnitRunner.Silent.class)
6465
public class SimpleJpaQueryUnitTests {
@@ -153,13 +154,6 @@ public void rejectsNativeQueryWithDynamicSort() throws Exception {
153154
createJpaQuery(method);
154155
}
155156

156-
@Test(expected = InvalidJpaQueryMethodException.class) // DATAJPA-554
157-
public void rejectsNativeQueryWithPageable() throws Exception {
158-
159-
Method method = SampleRepository.class.getMethod("findNativeByLastname", String.class, Pageable.class);
160-
createJpaQuery(method);
161-
}
162-
163157
@Test // DATAJPA-352
164158
@SuppressWarnings("unchecked")
165159
public void doesNotValidateCountQueryIfNotPagingMethod() throws Exception {

src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java

+7
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,13 @@ List<User> findUsersByFirstnameForSpELExpressionWithParameterIndexOnlyWithEntity
529529
@Query(value = "SELECT u FROM User u WHERE ?2 = 'x' ORDER BY CASE WHEN (u.firstname >= ?1) THEN 0 ELSE 1 END, u.firstname")
530530
Page<User> findAllOrderedBySpecialNameMultipleParamsIndexed(String name, String other, Pageable page);
531531

532+
// DATAJPA-928
533+
Page<User> findByNativeNamedQueryWithPageable(Pageable pageable);
534+
535+
// DATAJPA-928
536+
@Query(value = "SELECT firstname FROM SD_User ORDER BY UCASE(firstname)", countQuery = "SELECT count(*) FROM SD_User", nativeQuery = true)
537+
Page<String> findByNativeQueryWithPageable(@Param("pageable") Pageable pageable);
538+
532539
interface RolesAndFirstname {
533540

534541
String getFirstname();

0 commit comments

Comments
 (0)