Skip to content

Commit 1bcdbc2

Browse files
schaudergregturn
authored andcommitted
Pagination now supports sorting by property.
Closes spring-projects/spring-data-envers#379 Original Pull Request spring-projects/spring-data-envers#381
1 parent 393fa63 commit 1bcdbc2

File tree

3 files changed

+98
-44
lines changed

3 files changed

+98
-44
lines changed

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

+56-22
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

+36-22
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,13 @@
1515
*/
1616
package org.springframework.data.envers.repository.support;
1717

18-
import static org.assertj.core.api.Assertions.assertThat;
19-
import static org.springframework.data.history.RevisionMetadata.RevisionType.DELETE;
20-
import static org.springframework.data.history.RevisionMetadata.RevisionType.INSERT;
21-
import static org.springframework.data.history.RevisionMetadata.RevisionType.UPDATE;
22-
23-
import java.util.Arrays;
24-
import java.util.HashSet;
25-
import java.util.Iterator;
26-
import java.util.Optional;
27-
28-
import org.junit.jupiter.api.AfterEach;
2918
import org.junit.jupiter.api.BeforeEach;
3019
import org.junit.jupiter.api.Test;
3120
import org.junit.jupiter.api.extension.ExtendWith;
3221
import org.springframework.beans.factory.annotation.Autowired;
3322
import org.springframework.data.domain.Page;
3423
import org.springframework.data.domain.PageRequest;
24+
import org.springframework.data.domain.Sort;
3525
import org.springframework.data.envers.Config;
3626
import org.springframework.data.envers.sample.Country;
3727
import org.springframework.data.envers.sample.CountryRepository;
@@ -43,19 +33,30 @@
4333
import org.springframework.test.context.ContextConfiguration;
4434
import org.springframework.test.context.junit.jupiter.SpringExtension;
4535

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+
4645
/**
4746
* Integration tests for repositories.
4847
*
4948
* @author Oliver Gierke
5049
* @author Jens Schauder
51-
* @author Krzysztof Krason
50+
* @author Niklas Loechte
5251
*/
5352
@ExtendWith(SpringExtension.class)
5453
@ContextConfiguration(classes = Config.class)
5554
class RepositoryIntegrationTests {
5655

57-
@Autowired LicenseRepository licenseRepository;
58-
@Autowired CountryRepository countryRepository;
56+
@Autowired
57+
LicenseRepository licenseRepository;
58+
@Autowired
59+
CountryRepository countryRepository;
5960

6061
@BeforeEach
6162
void setUp() {
@@ -64,13 +65,6 @@ void setUp() {
6465
countryRepository.deleteAll();
6566
}
6667

67-
@AfterEach
68-
void tearDown() {
69-
70-
licenseRepository.deleteAll();
71-
countryRepository.deleteAll();
72-
}
73-
7468
@Test
7569
void testLifeCycle() {
7670

@@ -241,7 +235,27 @@ void shortCircuitingWhenOffsetIsToLarge() {
241235
@Test // #47
242236
void paginationWithEmptyResult() {
243237

244-
check(23L, 0, 0, 0);
238+
check(-23L, 0, 0, 0);
239+
}
240+
241+
242+
@Test // Envers #379
243+
void testSort_pageableByProperty() {
244+
245+
Country de = new Country();
246+
de.code = "de";
247+
de.name = "Deutschland";
248+
de.timestamp = Instant.parse("2000-01-01T00:00:00Z");
249+
countryRepository.save(de);
250+
251+
de.timestamp = Instant.parse("2000-01-04T00:01:00Z");
252+
countryRepository.save(de);
253+
254+
de.timestamp = Instant.parse("2000-01-04T00:00:00Z");
255+
countryRepository.save(de);
256+
257+
assertThat(countryRepository.findRevisions(de.id, PageRequest.of(0, 3, Sort.by("timestamp"))).map(Revision::getEntity).map(country -> country.timestamp).getContent())
258+
.isSortedAccordingTo(Instant::compareTo);
245259
}
246260

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

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

+6
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)