如何使用原生查询实现动态分页
在这里,您可以找到存储库和服务层以及您的数据传输对象 (DTO),它们将用于映射我们的结果并将其发送到控制器层。
public interface CustomSomethingRepository {
List<Something> findPagedResultBySomethingElseId(long somethingElseId, int offset, int limit);
}
public class SomethingRepositoryImpl implements CustomSomethingRepository {
@Autowired
private EntityManager em;
@SuppressWarnings("unchecked")
@Override
public List<Something> findPagedResultBySomethingElseId(long somethingElseId, int offset, int limit) {
String query = "select s.* from Something s "
+ "join somethingelse selse on selse.id = s.fk_somethingelse "
+ "where selse.id = :somethingElseId "
+ "order by selse.date";
Query nativeQuery = em.createNativeQuery(query);
nativeQuery.setParameter("somethingElseId", somethingElseId);
//Paginering
nativeQuery.setFirstResult(offset);
nativeQuery.setMaxResults(limit);
final List<Object[]> resultList = nativeQuery.getResultList();
List<Something> somethingList = Lists.newArrayList();
resultList.forEach(object -> somethingList.add(//map obj to something));
return somethingList;
}
}
Hibernate 将您的查询翻译如下:
SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM ( select TOP(?) t as page0_ from Something s join s.somethingelse as selse order by selse.date ) inner_query ) SELECT page0_ FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?
@Service
public class SomethingService {
private SomethingRepository somethingRepository;
@Autowired
public SomethingService(SomethingRepository somethingRepository){
this.somethingRepository = somethingRepository;
}
@Transactional(readOnly=true)
public PageDto getSomething(long somethingElseId, int page, int size){
List<Something> somethings = somethingRepository.findBySomethingElseId(somethingElseId, offset, limit);
return new PagedResult<>(somethings
.stream()
.map(SomethingDto::createDto)
.sorted(comparing(SomethingDto::getDatum))
.collect(toList()), somethings.getTotalElements(), somethings.getTotalPages();
}
}
@Controller
//....
public class PagedResult<T> {
public static final long DEFAULT_OFFSET = 0;
public static final int DEFAULT_MAX_NO_OF_ROWS = 100;
private int offset;
private int limit;
private long totalElements;
private List<T> elements;
public PagedResult(List<T> elements, long totalElements, int offset, int limit) {
this.elements = elements;
this.totalElements = totalElements;
this.offset = offset;
this.limit = limit;
}
public boolean hasMore() {
return totalElements > offset + limit;
}
public boolean hasPrevious() {
return offset > 0 && totalElements > 0;
}
public long getTotalElements() {
return totalElements;
}
public int getOffset() {
return offset;
}
public int getLimit() {
return limit;
}
public List<T> getElements() {
return elements;
}
}
优点和缺点
优点:与使用 Spring Data 相比,将生成更少的 SQL 查询。这些复杂的查询不能用 Spring Data 编写,我们必须将我们的查询指定为原生查询,仍然可以使用这种方法进行分页。
缺点:“对象”数组必须映射到 Java 对象。这是痛苦且难以维持的。
如何使用 Spring Data 实现 OffsetLimit 分页
据我所知,默认 Spring Data 存储库中没有“开箱即用”的支持。但是您可以创建一个自定义实现 Pageable 对象,该对象将采用限制/偏移参数。
制作一个可分页对象并将其传递给 PaginationAndSortingRepository:
public class OffsetLimitRequest implements Pageable {
private int limit;
private int offset;
public OffsetLimitRequest(int offset, int limit){
this.limit = limit;
this.offset = offset;
}
@Override
public int getPageNumber() {
return 0;
}
@Override
public int getPageSize() {
return limit;
}
@Override
public int getOffset() {
return offset;
}
....
}
这意味着无需更改存储库层。您需要做的唯一更改是服务层,如下所示:
@Service
public class SomethingService {
private SomethingRepository somethingRepository;
@Autowired
public SomethingService(SomethingRepository somethingRepository){
this.somethingRepository = somethingRepository;
}
@Transactional(readOnly=true)
public PageDto getSomething(long somethingElseId, int page, int size){
Page<Something> somethings = somethingRepository.findBySomethingElseId(somethingElseId, new OffsetLimitRequest(offset, limit));
return new PageDto(somethings.getContent()
.stream()
.map(SomethingDto::createDto)
.sorted(comparing(SomethingDto::getDatum))
.collect(toList()), somethings.getTotalElements(), somethings.getTotalPages();
}
}
请注意,您不需要手动映射结果,这会占用大量开发时间。