Skip to content

Commit 9576299

Browse files
committed
Pagination now supports sorting by property.
Closes spring-projects/spring-data-envers#379 Original Pull Request https://github.com/spring-projects/spring-data-envers/pull/
1 parent 0348c68 commit 9576299

File tree

3 files changed

+115
-49
lines changed

3 files changed

+115
-49
lines changed

spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryImpl.java

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,7 @@
1515
*/
1616
package org.springframework.data.envers.repository.support;
1717

18-
import static org.springframework.data.history.RevisionMetadata.RevisionType.*;
19-
20-
import java.util.ArrayList;
21-
import java.util.List;
22-
import java.util.Optional;
23-
2418
import jakarta.persistence.EntityManager;
25-
2619
import org.hibernate.Hibernate;
2720
import org.hibernate.envers.AuditReader;
2821
import org.hibernate.envers.AuditReaderFactory;
@@ -32,10 +25,12 @@
3225
import org.hibernate.envers.RevisionType;
3326
import org.hibernate.envers.query.AuditEntity;
3427
import org.hibernate.envers.query.AuditQuery;
28+
import org.hibernate.envers.query.criteria.AuditProperty;
3529
import org.hibernate.envers.query.order.AuditOrder;
3630
import org.springframework.data.domain.Page;
3731
import org.springframework.data.domain.PageImpl;
3832
import org.springframework.data.domain.Pageable;
33+
import org.springframework.data.domain.Sort;
3934
import org.springframework.data.history.AnnotationRevisionMetadata;
4035
import org.springframework.data.history.Revision;
4136
import org.springframework.data.history.RevisionMetadata;
@@ -48,6 +43,13 @@
4843
import org.springframework.transaction.annotation.Transactional;
4944
import org.springframework.util.Assert;
5045

46+
import java.util.ArrayList;
47+
import java.util.Collections;
48+
import java.util.List;
49+
import java.util.Optional;
50+
51+
import static org.springframework.data.history.RevisionMetadata.RevisionType.*;
52+
5153
/**
5254
* Repository implementation using Hibernate Envers to implement revision specific query methods.
5355
*
@@ -58,6 +60,7 @@
5860
* @author Julien Millau
5961
* @author Mark Paluch
6062
* @author Sander Bylemans
63+
* @author Niklas Loechte
6164
*/
6265
@Transactional(readOnly = true)
6366
public class EnversRevisionRepositoryImpl<T, ID, N extends Number & Comparable<N>>
@@ -70,14 +73,14 @@ public class EnversRevisionRepositoryImpl<T, ID, N extends Number & Comparable<N
7073
* Creates a new {@link EnversRevisionRepositoryImpl} using the given {@link JpaEntityInformation},
7174
* {@link RevisionEntityInformation} and {@link EntityManager}.
7275
*
73-
* @param entityInformation must not be {@literal null}.
76+
* @param entityInformation must not be {@literal null}.
7477
* @param revisionEntityInformation must not be {@literal null}.
75-
* @param entityManager must not be {@literal null}.
78+
* @param entityManager must not be {@literal null}.
7679
*/
7780
public EnversRevisionRepositoryImpl(JpaEntityInformation<T, ?> entityInformation,
78-
RevisionEntityInformation revisionEntityInformation, EntityManager entityManager) {
81+
RevisionEntityInformation revisionEntityInformation, EntityManager entityManager) {
7982

80-
Assert.notNull(revisionEntityInformation, "RevisionEntityInformation must not be null");
83+
Assert.notNull(revisionEntityInformation, "RevisionEntityInformation must not be null!");
8184

8285
this.entityInformation = entityInformation;
8386
this.entityManager = entityManager;
@@ -91,7 +94,7 @@ public Optional<Revision<N, T>> findLastChangeRevision(ID id) {
9194
.setMaxResults(1) //
9295
.getResultList();
9396

94-
Assert.state(singleResult.size() <= 1, "We expect at most one result");
97+
Assert.state(singleResult.size() <= 1, "We expect at most one result.");
9598

9699
if (singleResult.isEmpty()) {
97100
return Optional.empty();
@@ -104,14 +107,14 @@ public Optional<Revision<N, T>> findLastChangeRevision(ID id) {
104107
@SuppressWarnings("unchecked")
105108
public Optional<Revision<N, T>> findRevision(ID id, N revisionNumber) {
106109

107-
Assert.notNull(id, "Identifier must not be null");
108-
Assert.notNull(revisionNumber, "Revision number must not be null");
110+
Assert.notNull(id, "Identifier must not be null!");
111+
Assert.notNull(revisionNumber, "Revision number must not be null!");
109112

110113
List<Object[]> singleResult = (List<Object[]>) createBaseQuery(id) //
111114
.add(AuditEntity.revisionNumber().eq(revisionNumber)) //
112115
.getResultList();
113116

114-
Assert.state(singleResult.size() <= 1, "We expect at most one result");
117+
Assert.state(singleResult.size() <= 1, "We expect at most one result.");
115118

116119
if (singleResult.isEmpty()) {
117120
return Optional.empty();
@@ -133,15 +136,46 @@ public Revisions<N, T> findRevisions(ID id) {
133136
return Revisions.of(revisionList);
134137
}
135138

136-
@SuppressWarnings("unchecked")
137-
public Page<Revision<N, T>> findRevisions(ID id, Pageable pageable) {
138139

139-
AuditOrder sorting = RevisionSort.getRevisionDirection(pageable.getSort()).isDescending() //
140+
private AuditOrder mapRevisionSort(RevisionSort revisionSort) {
141+
142+
return RevisionSort.getRevisionDirection(revisionSort).isDescending() //
140143
? AuditEntity.revisionNumber().desc() //
141144
: AuditEntity.revisionNumber().asc();
145+
}
146+
147+
private List<AuditOrder> mapPropertySort(Sort sort) {
148+
149+
if (sort.isEmpty()) {
150+
return Collections.singletonList(AuditEntity.revisionNumber().asc());
151+
}
152+
153+
List<AuditOrder> result = new ArrayList<>();
154+
for (Sort.Order order : sort) {
155+
156+
AuditProperty<Object> property = AuditEntity.property(order.getProperty());
157+
AuditOrder auditOrder = order.getDirection().isAscending() ?
158+
property.asc() :
159+
property.desc();
160+
161+
result.add(auditOrder);
162+
}
163+
164+
return result;
165+
}
166+
167+
@SuppressWarnings("unchecked")
168+
public Page<Revision<N, T>> findRevisions(ID id, Pageable pageable) {
169+
170+
AuditQuery baseQuery = createBaseQuery(id);
171+
172+
List<AuditOrder> orderMapped = (pageable.getSort() instanceof RevisionSort) ?
173+
Collections.singletonList(mapRevisionSort((RevisionSort) pageable.getSort())) :
174+
mapPropertySort(pageable.getSort());
175+
176+
orderMapped.forEach(baseQuery::addOrder);
142177

143-
List<Object[]> resultList = createBaseQuery(id) //
144-
.addOrder(sorting) //
178+
List<Object[]> resultList = baseQuery //
145179
.setFirstResult((int) pageable.getOffset()) //
146180
.setMaxResults(pageable.getPageSize()) //
147181
.getResultList();
@@ -185,7 +219,7 @@ static class QueryResult<T> {
185219
Assert.notNull(data, "Data must not be null");
186220
Assert.isTrue( //
187221
data.length == 3, //
188-
() -> String.format("Data must have length three, but has length %d", data.length));
222+
() -> String.format("Data must have length three, but has length %d.", data.length));
189223
Assert.isTrue( //
190224
data[2] instanceof RevisionType, //
191225
() -> String.format("The third array element must be of type Revision type, but is of type %s",
@@ -201,7 +235,7 @@ RevisionMetadata<?> createRevisionMetadata() {
201235
return metadata instanceof DefaultRevisionEntity //
202236
? new DefaultRevisionMetadata((DefaultRevisionEntity) metadata, revisionType) //
203237
: new AnnotationRevisionMetadata<>(Hibernate.unproxy(metadata), RevisionNumber.class, RevisionTimestamp.class,
204-
revisionType);
238+
revisionType);
205239
}
206240

207241
private static RevisionMetadata.RevisionType convertRevisionType(RevisionType datum) {

spring-data-envers/src/test/java/org/springframework/data/envers/repository/support/RepositoryIntegrationTests.java

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,13 @@
1515
*/
1616
package org.springframework.data.envers.repository.support;
1717

18-
import static org.assertj.core.api.Assertions.*;
19-
import static org.springframework.data.history.RevisionMetadata.RevisionType.*;
20-
21-
import java.util.Arrays;
22-
import java.util.HashSet;
23-
import java.util.Iterator;
24-
import java.util.Optional;
25-
26-
import org.junit.jupiter.api.AfterEach;
2718
import org.junit.jupiter.api.BeforeEach;
2819
import org.junit.jupiter.api.Test;
2920
import org.junit.jupiter.api.extension.ExtendWith;
30-
3121
import org.springframework.beans.factory.annotation.Autowired;
3222
import org.springframework.data.domain.Page;
3323
import org.springframework.data.domain.PageRequest;
24+
import org.springframework.data.domain.Sort;
3425
import org.springframework.data.envers.Config;
3526
import org.springframework.data.envers.sample.Country;
3627
import org.springframework.data.envers.sample.CountryRepository;
@@ -42,11 +33,23 @@
4233
import org.springframework.test.context.ContextConfiguration;
4334
import org.springframework.test.context.junit.jupiter.SpringExtension;
4435

36+
import java.time.Instant;
37+
import java.util.Arrays;
38+
import java.util.HashSet;
39+
import java.util.Iterator;
40+
import java.util.Optional;
41+
42+
import static org.assertj.core.api.Assertions.*;
43+
import static org.springframework.data.history.RevisionMetadata.RevisionType.*;
44+
45+
4546
/**
4647
* Integration tests for repositories.
4748
*
4849
* @author Oliver Gierke
4950
* @author Jens Schauder
51+
* @author Krzysztof Krason
52+
* @author Niklas Loechte
5053
*/
5154
@ExtendWith(SpringExtension.class)
5255
@ContextConfiguration(classes = Config.class)
@@ -64,13 +67,6 @@ void setUp() {
6467
countryRepository.deleteAll();
6568
}
6669

67-
@AfterEach
68-
void tearDown() {
69-
70-
licenseRepository.deleteAll();
71-
countryRepository.deleteAll();
72-
}
73-
7470
@Test
7571
void testLifeCycle() {
7672

@@ -113,22 +109,26 @@ void testLifeCycle() {
113109
});
114110
}
115111

116-
@Test // #1
112+
@Test
113+
// #1
117114
void returnsEmptyLastRevisionForUnrevisionedEntity() {
118115
assertThat(countryRepository.findLastChangeRevision(100L)).isEmpty();
119116
}
120117

121-
@Test // #47
118+
@Test
119+
// #47
122120
void returnsEmptyRevisionsForUnrevisionedEntity() {
123121
assertThat(countryRepository.findRevisions(100L)).isEmpty();
124122
}
125123

126-
@Test // #47
124+
@Test
125+
// #47
127126
void returnsEmptyRevisionForUnrevisionedEntity() {
128127
assertThat(countryRepository.findRevision(100L, 23)).isEmpty();
129128
}
130129

131-
@Test // #31
130+
@Test
131+
// #31
132132
void returnsParticularRevisionForAnEntity() {
133133

134134
Country de = new Country();
@@ -156,7 +156,8 @@ void returnsParticularRevisionForAnEntity() {
156156
.hasValueSatisfying(it -> assertThat(it.getEntity().name).isEqualTo("Germany"));
157157
}
158158

159-
@Test // #55
159+
@Test
160+
// #55
160161
void considersRevisionNumberSortOrder() {
161162

162163
Country de = new Country();
@@ -177,7 +178,8 @@ void considersRevisionNumberSortOrder() {
177178
.isGreaterThan(page.getContent().get(1).getRequiredRevisionNumber());
178179
}
179180

180-
@Test // #21
181+
@Test
182+
// #21
181183
void findsDeletedRevisions() {
182184

183185
Country de = new Country();
@@ -197,7 +199,8 @@ void findsDeletedRevisions() {
197199
.containsExactly(null, null);
198200
}
199201

200-
@Test // #47
202+
@Test
203+
// #47
201204
void includesCorrectRevisionType() {
202205

203206
Country de = new Country();
@@ -223,7 +226,8 @@ void includesCorrectRevisionType() {
223226
);
224227
}
225228

226-
@Test // #146
229+
@Test
230+
// #146
227231
void shortCircuitingWhenOffsetIsToLarge() {
228232

229233
Country de = new Country();
@@ -239,10 +243,32 @@ void shortCircuitingWhenOffsetIsToLarge() {
239243
check(de.id, 2, 0, 2);
240244
}
241245

242-
@Test // #47
246+
@Test
247+
// #47
243248
void paginationWithEmptyResult() {
244249

245-
check(23L, 0, 0, 0);
250+
check(-23L, 0, 0, 0);
251+
}
252+
253+
254+
@Test
255+
// Envers #379
256+
void testSort_pageableByProperty() {
257+
258+
Country de = new Country();
259+
de.code = "de";
260+
de.name = "Deutschland";
261+
de.timestamp = Instant.parse("2000-01-01T00:00:00Z");
262+
countryRepository.save(de);
263+
264+
de.timestamp = Instant.parse("2000-01-04T00:01:00Z");
265+
countryRepository.save(de);
266+
267+
de.timestamp = Instant.parse("2000-01-04T00:00:00Z");
268+
countryRepository.save(de);
269+
270+
assertThat(countryRepository.findRevisions(de.id, PageRequest.of(0, 3, Sort.by("timestamp"))).map(Revision::getEntity).map(country -> country.timestamp).getContent())
271+
.isSortedAccordingTo(Instant::compareTo);
246272
}
247273

248274
void check(Long id, int page, int expectedSize, int expectedTotalSize) {

spring-data-envers/src/test/java/org/springframework/data/envers/sample/Country.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,23 @@
2020
import lombok.ToString;
2121
import org.hibernate.envers.Audited;
2222

23+
import java.time.Instant;
24+
2325
/**
2426
* Sample domain class.
2527
*
2628
* @author Oliver Gierke
2729
* @author Jens Schauder
30+
* @author Niklas Loechte
2831
*/
2932
@Audited
3033
@Entity
3134
@ToString
3235
public class Country extends AbstractEntity {
3336

3437
public String code;
38+
39+
public Instant timestamp;
40+
3541
public String name;
3642
}

0 commit comments

Comments
 (0)