目录
[与 Page 的区别](#与 Page 的区别)
[1. Repository 接口定义](#1. Repository 接口定义)
[2. 服务层使用](#2. 服务层使用)
[3. 控制器层](#3. 控制器层)
[1. 自定义查询 + Slice](#1. 自定义查询 + Slice)
[2. 动态排序](#2. 动态排序)
[3. DTO 投影](#3. DTO 投影)
[五、与 Stream 结合使用](#五、与 Stream 结合使用)
[使用 Slice 的场景:](#使用 Slice 的场景:)
[使用 Page 的场景:](#使用 Page 的场景:)
Slice 是 Spring Data 中用于分页查询的核心接口之一,用于表示查询结果的一部分(分片),特别适用于大数据集的分页处理。
一、基本概念
与 Page 的区别
-
Slice: 只知道是否有下一页,不知道总页数和总数
-
Page : 扩展自
Slice,包含总页数和总记录数
主要特点
-
轻量级,不执行总数统计,性能更好
-
适用于无限滚动、流式加载等场景
-
内存占用更小
二、核心接口方法
public interface Slice<T> extends Streamable<T> {
int getNumber(); // 当前页码(从0开始)
int getSize(); // 每页大小
int getNumberOfElements(); // 当前页实际元素数
List<T> getContent(); // 获取当前页数据
boolean hasContent(); // 是否有数据
Sort getSort(); // 排序信息
boolean isFirst(); // 是否是第一页
boolean isLast(); // 是否是最后一页
boolean hasNext(); // 是否有下一页
boolean hasPrevious(); // 是否有上一页
Pageable getPageable(); // 分页信息
Pageable nextPageable(); // 下一页的分页信息
Pageable previousPageable(); // 上一页的分页信息
<U> Slice<U> map(Function<T, U> converter); // 转换数据
}
三、基本使用示例
1. Repository 接口定义
public interface UserRepository extends JpaRepository<User, Long> {
// 返回 Slice(不查询总数)
Slice<User> findByAgeGreaterThan(int age, Pageable pageable);
// 返回 Page(查询总数)
Page<User> findByNameContaining(String name, Pageable pageable);
}
2. 服务层使用
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// 使用 Slice 进行分页查询
public Slice<User> getUsersByAge(int age, int page, int size) {
Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
return userRepository.findByAgeGreaterThan(age, pageable);
}
// 处理分页数据
public void processUsersSlice() {
int page = 0;
int size = 20;
do {
Pageable pageable = PageRequest.of(page, size);
Slice<User> slice = userRepository.findByAgeGreaterThan(18, pageable);
// 处理当前页数据
List<User> users = slice.getContent();
users.forEach(user -> {
// 业务处理
System.out.println(user.getName());
});
// 如果有下一页,继续查询
if (slice.hasNext()) {
page++;
} else {
break;
}
} while (true);
}
}
3. 控制器层
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/slice")
public ResponseEntity<Map<String, Object>> getUsersBySlice(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam int minAge) {
Pageable pageable = PageRequest.of(page, size);
Slice<User> slice = userRepository.findByAgeGreaterThan(minAge, pageable);
Map<String, Object> response = new HashMap<>();
response.put("data", slice.getContent());
response.put("currentPage", slice.getNumber());
response.put("pageSize", slice.getSize());
response.put("hasNext", slice.hasNext());
response.put("hasPrevious", slice.hasPrevious());
response.put("isFirst", slice.isFirst());
response.put("isLast", slice.isLast());
return ResponseEntity.ok(response);
}
}
四、高级用法
1. 自定义查询 + Slice
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.active = true AND u.age > :age")
Slice<User> findActiveUsersByAge(@Param("age") int age, Pageable pageable);
// 使用原生查询
@Query(value = "SELECT * FROM users WHERE status = 'ACTIVE'",
countQuery = "SELECT count(*) FROM users WHERE status = 'ACTIVE'",
nativeQuery = true)
Slice<User> findActiveUsers(Pageable pageable);
}
2. 动态排序
public Slice<User> getUsersWithDynamicSort(int page, int size, String sortBy, String direction) {
Sort sort = direction.equalsIgnoreCase("desc")
? Sort.by(sortBy).descending()
: Sort.by(sortBy).ascending();
Pageable pageable = PageRequest.of(page, size, sort);
return userRepository.findAll(pageable);
}
3. DTO 投影
public interface UserProjection {
String getName();
String getEmail();
}
public interface UserRepository extends JpaRepository<User, Long> {
Slice<UserProjection> findSliceBy(Pageable pageable);
}
// 使用
Slice<UserProjection> slice = userRepository.findSliceBy(pageable);
slice.getContent().forEach(projection -> {
System.out.println(projection.getName());
});
五、与 Stream 结合使用
public void processAllUsersWithSlice() {
int pageSize = 100;
int page = 0;
do {
Pageable pageable = PageRequest.of(page, pageSize);
Slice<User> slice = userRepository.findAll(pageable);
// 使用 Stream 处理
slice.getContent().stream()
.filter(user -> user.isActive())
.forEach(user -> processUser(user));
if (!slice.hasNext()) {
break;
}
page++;
} while (true);
}
六、性能优化建议
使用 Slice 的场景:
-
大数据集分页:不需要知道总记录数时
-
无限滚动:移动端或Web端的懒加载
-
导出数据:分批处理大量数据
-
实时数据:数据频繁变化,总数意义不大
使用 Page 的场景:
-
需要显示总页数:传统分页组件
-
数据量相对较小:总数查询不会造成性能问题
-
需要统计信息:如总记录数、总页数等
七、注意事项
-
页码从0开始:Spring Data 的页码通常从0开始
-
排序字段索引:确保分页排序字段有数据库索引
-
内存管理:处理大量数据时注意内存使用
-
连接池配置:长时间分页查询注意连接超时设置
-
事务管理:确保分页查询在事务范围内
八、测试示例
@SpringBootTest
class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
void testSliceQuery() {
// 准备测试数据
for (int i = 0; i < 50; i++) {
User user = new User("User" + i, 20 + i);
userRepository.save(user);
}
// 测试 Slice 查询
Pageable pageable = PageRequest.of(0, 20);
Slice<User> slice = userRepository.findByAgeGreaterThan(25, pageable);
assertThat(slice.getContent()).hasSize(20);
assertThat(slice.hasNext()).isTrue();
assertThat(slice.isFirst()).isTrue();
// 测试下一页
Slice<User> nextSlice = userRepository.findByAgeGreaterThan(
25, slice.nextPageable());
assertThat(nextSlice.getNumber()).isEqualTo(1);
}
}
Slice 是 Spring Data 中非常实用的分页工具,特别适合大数据量和性能敏感的场景。根据实际需求选择使用 Slice 或 Page。