Skip to content

Commit b7ca812

Browse files
committed
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.
1 parent 64c668d commit b7ca812

File tree

6 files changed

+103
-62
lines changed

6 files changed

+103
-62
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

+19-23
Original file line numberDiff line numberDiff line change
@@ -20,29 +20,7 @@
2020
import java.util.HashSet;
2121
import java.util.Set;
2222

23-
import javax.persistence.CascadeType;
24-
import javax.persistence.Column;
25-
import javax.persistence.ElementCollection;
26-
import javax.persistence.Embedded;
27-
import javax.persistence.Entity;
28-
import javax.persistence.GeneratedValue;
29-
import javax.persistence.GenerationType;
30-
import javax.persistence.Id;
31-
import javax.persistence.Lob;
32-
import javax.persistence.ManyToMany;
33-
import javax.persistence.ManyToOne;
34-
import javax.persistence.NamedAttributeNode;
35-
import javax.persistence.NamedEntityGraph;
36-
import javax.persistence.NamedEntityGraphs;
37-
import javax.persistence.NamedQuery;
38-
import javax.persistence.NamedStoredProcedureQueries;
39-
import javax.persistence.NamedStoredProcedureQuery;
40-
import javax.persistence.NamedSubgraph;
41-
import javax.persistence.ParameterMode;
42-
import javax.persistence.StoredProcedureParameter;
43-
import javax.persistence.Table;
44-
import javax.persistence.Temporal;
45-
import javax.persistence.TemporalType;
23+
import javax.persistence.*;
4624

4725
/**
4826
* Domain class representing a person emphasizing the use of {@code AbstractEntity}. No declaration of an id is
@@ -51,6 +29,7 @@
5129
* @author Oliver Gierke
5230
* @author Thomas Darimont
5331
* @author Christoph Strobl
32+
* @author Jens Schauder
5433
*/
5534
@Entity
5635
@NamedEntityGraphs({ @NamedEntityGraph(name = "User.overview", attributeNodes = { @NamedAttributeNode("roles") }),
@@ -84,6 +63,23 @@
8463
@NamedStoredProcedureQuery(name = "User.plus1IO", procedureName = "plus1inout",
8564
parameters = { @StoredProcedureParameter(mode = ParameterMode.IN, name = "arg", type = Integer.class),
8665
@StoredProcedureParameter(mode = ParameterMode.OUT, name = "res", type = Integer.class) })
66+
67+
// Annotations for native Query with pageable
68+
@SqlResultSetMappings({
69+
@SqlResultSetMapping(name = "SqlResultSetMapping.count", columns = @ColumnResult(name = "cnt"))
70+
})
71+
@NamedNativeQueries({
72+
@NamedNativeQuery(
73+
name = "User.findByNativeNamedQueryWithPageable",
74+
resultClass = User.class,
75+
query = "SELECT * FROM SD_USER ORDER BY UCASE(firstname)"
76+
),
77+
@NamedNativeQuery(
78+
name = "User.findByNativeNamedQueryWithPageable.count",
79+
resultSetMapping = "SqlResultSetMapping.count",
80+
query = "SELECT count(*) AS cnt FROM SD_USER"
81+
)
82+
})
8783
@Table(name = "SD_User")
8884
public class User {
8985

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

+54-4
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;
@@ -50,8 +51,6 @@
5051
import org.springframework.dao.InvalidDataAccessApiUsageException;
5152
import org.springframework.data.domain.Example;
5253
import org.springframework.data.domain.ExampleMatcher;
53-
import org.springframework.data.domain.ExampleMatcher.GenericPropertyMatcher;
54-
import org.springframework.data.domain.ExampleMatcher.StringMatcher;
5554
import org.springframework.data.domain.Page;
5655
import org.springframework.data.domain.PageImpl;
5756
import org.springframework.data.domain.PageRequest;
@@ -60,6 +59,7 @@
6059
import org.springframework.data.domain.Sort;
6160
import org.springframework.data.domain.Sort.Direction;
6261
import org.springframework.data.domain.Sort.Order;
62+
import org.springframework.data.domain.ExampleMatcher.*;
6363
import org.springframework.data.jpa.domain.Specification;
6464
import org.springframework.data.jpa.domain.sample.Address;
6565
import org.springframework.data.jpa.domain.sample.Role;
@@ -2059,8 +2059,8 @@ public void supportsProjectionsWithNativeQueries() {
20592059
assertThat(result.getLastname()).isEqualTo(user.getLastname());
20602060
}
20612061

2062-
@Test //DATAJPA-1235
2063-
public void handlesColonsFollowedByIntegerInStringLiteral(){
2062+
@Test // DATAJPA-1235
2063+
public void handlesColonsFollowedByIntegerInStringLiteral() {
20642064

20652065
String firstName = "aFirstName";
20662066

@@ -2077,6 +2077,56 @@ public void handlesColonsFollowedByIntegerInStringLiteral(){
20772077
assertThat(users).extracting(User::getId).containsExactly(expected.getId());
20782078
}
20792079

2080+
// DATAJPA-928
2081+
@Test
2082+
public void executeNativeQueryWithPage() {
2083+
2084+
flushTestUsers();
2085+
2086+
Page<User> firstPage = repository.findByNativeNamedQueryWithPageable(new PageRequest(0, 3));
2087+
Page<User> secondPage = repository.findByNativeNamedQueryWithPageable(new PageRequest(1, 3));
2088+
2089+
SoftAssertions softly = new SoftAssertions();
2090+
2091+
assertThat(firstPage.getTotalElements()).isEqualTo(4L);
2092+
assertThat(firstPage.getNumberOfElements()).isEqualTo(3);
2093+
assertThat(firstPage.getContent()) //
2094+
.extracting(User::getFirstname) //
2095+
.containsExactly("Dave", "Joachim", "kevin");
2096+
2097+
assertThat(secondPage.getTotalElements()).isEqualTo(4L);
2098+
assertThat(secondPage.getNumberOfElements()).isEqualTo(1);
2099+
assertThat(secondPage.getContent()) //
2100+
.extracting(User::getFirstname) //
2101+
.containsExactly("Oliver");
2102+
2103+
softly.assertAll();
2104+
}
2105+
2106+
// DATAJPA-928
2107+
@Test
2108+
public void executeNativeQueryWithPageWorkaround() {
2109+
2110+
flushTestUsers();
2111+
2112+
Page<String> firstPage = repository.findByNativeQueryWithPageable(new PageRequest(0, 3));
2113+
Page<String> secondPage = repository.findByNativeQueryWithPageable(new PageRequest(1, 3));
2114+
2115+
SoftAssertions softly = new SoftAssertions();
2116+
2117+
assertThat(firstPage.getTotalElements()).isEqualTo(4L);
2118+
assertThat(firstPage.getNumberOfElements()).isEqualTo(3);
2119+
assertThat(firstPage.getContent()) //
2120+
.containsExactly("Dave", "Joachim", "kevin");
2121+
2122+
assertThat(secondPage.getTotalElements()).isEqualTo(4L);
2123+
assertThat(secondPage.getNumberOfElements()).isEqualTo(1);
2124+
assertThat(secondPage.getContent()) //
2125+
.containsExactly("Oliver");
2126+
2127+
softly.assertAll();
2128+
}
2129+
20802130
private Page<User> executeSpecWithSort(Sort sort) {
20812131

20822132
flushTestUsers();

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

+14-22
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.*;
20-
import static org.mockito.ArgumentMatchers.*;
18+
import static org.assertj.core.api.Assertions.*;
19+
import static org.mockito.ArgumentMatchers.anyString;
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

+4-8
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717

1818
import static org.hamcrest.Matchers.*;
1919
import static org.junit.Assert.*;
20-
import static org.mockito.ArgumentMatchers.*;
20+
import static org.mockito.ArgumentMatchers.anyInt;
21+
import static org.mockito.ArgumentMatchers.anyString;
22+
import static org.mockito.ArgumentMatchers.eq;
2123
import static org.mockito.Mockito.*;
2224

2325
import java.lang.reflect.Method;
@@ -59,6 +61,7 @@
5961
*
6062
* @author Oliver Gierke
6163
* @author Thomas Darimont
64+
* @author Jens Schauder
6265
*/
6366
@RunWith(MockitoJUnitRunner.Silent.class)
6467
public class SimpleJpaQueryUnitTests {
@@ -153,13 +156,6 @@ public void rejectsNativeQueryWithDynamicSort() throws Exception {
153156
createJpaQuery(method);
154157
}
155158

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-
163159
@Test // DATAJPA-352
164160
@SuppressWarnings("unchecked")
165161
public void doesNotValidateCountQueryIfNotPagingMethod() throws Exception {

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

+9
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,15 @@ List<User> findUsersByFirstnameForSpELExpressionWithParameterIndexOnlyWithEntity
513513
@Query("SELECT u FROM User u where u.firstname >= ?1 and u.lastname = '000:1'")
514514
List<User> queryWithIndexedParameterAndColonFollowedByIntegerInString(String firstname);
515515

516+
517+
// DATAJPA-928
518+
Page<User> findByNativeNamedQueryWithPageable(Pageable pageable);
519+
520+
521+
// DATAJPA-928
522+
@Query(value = "SELECT firstname FROM SD_User ORDER BY UCASE(firstname)", countQuery = "SELECT count(*) FROM SD_User", nativeQuery = true)
523+
Page<String> findByNativeQueryWithPageable(@Param("pageable") Pageable pageable);
524+
516525
static interface RolesAndFirstname {
517526

518527
String getFirstname();

0 commit comments

Comments
 (0)