Spring Data JPA 分页和排序示例

在本文中,介绍PagingAndSortingRepository一个实际示例, 用来实现Spring Data JPA 的接口实现分页和排序

什么是PagingAndSortingRepository?
检索数据的传统方法通常涉及大型列表。这可能会导致性能问题,尤其是在客户端渲染上。分页和排序可以解决这个问题,带来以下几个好处:

  • 改进的性能: 通过以较小的块(页面)获取数据,您可以减少带宽使用并缩短加载时间。
  • 增强的用户体验: 用户可以使用上一个/下一个按钮或页码有效地浏览数据。
  • 灵活排序: 允许用户根据自己的喜好对数据进行排序,从而带来更加个性化的体验。

依赖设置

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation=
"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.8</version>
        <relativePath/> 
    </parent>
    <groupId>com.jcg</groupId>
    <artifactId>paginationsortingexample</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>paginationsortingexample</name>
    <description>Demo project for Spring Boot Pagination and Sorting</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
 
</project>

数据库配置(application.properties):

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.jpa.hibernate.ddl-auto=update
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true

通过这些更新,该项目被配置为使用 H2 内存数据库。http://localhost:8080/h2-console当应用程序运行时,我们可以访问H2控制台。


JPA实体
首先,让我们定义一个实体类来表示我们要使用的数据。对于此示例,我们考虑一个包含、和字段的简单Book实体。

@Entity
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
 
    private String title;
    private String author;
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public String getTitle() {
        return title;
    }
 
    public void setTitle(String title) {
        this.title = title;
    }
 
    public String getAuthor() {
        return author;
    }
 
    public void setAuthor(String author) {
        this.author = author;
    }
     
}

创建存储库接口
我们将通过扩展Spring Data JPA 提供的接口来创建存储库接口。PagingAndSortingRepository接口提供了两个关键方法:

  • findAll(Sort sort): 返回根据给定 Sort 对象排序的所有实体。
  • findAll(Pageable pageable): 返回一个 Page 对象,其中包含该对象定义的特定数据页 Pageable 。

我们还可以创建将Pageable或Sort对象作为参数的自定义方法,以满足我们的特定需求。

@Repository
public interface BookRepository extends PagingAndSortingRepository<Book, Long> {
 
    List<Book> findByTitleContaining(String title, Pageable pageable);
}

我们添加了一个名为 findByTitleContaining(String title, Pageable pageable) 的自定义方法,用于按标题查找图书并支持分页。Pageable pageable 允许进行分页配置,如指定页码、大小、当前页、排序等。

请注意,从 Spring Data 3.0 开始,PagingAndSortingRepository 不再扩展 CrudRepository,因此如果我们想在存储库类中添加 CRUD 功能,就需要让我们的存储库 Bean 扩展 JpaRepository 接口。或者,我们可以显式地从 CrudRepository 或 ListCrudRepository 接口扩展,这样我们就能在执行 CRUD 的同时进行分页和排序。


@Repository
public interface BookRepository extends JpaRepository<Book, Long> {
 
}
@Repository
public interface BookRepository<Book, Long> extends PagingAndSortingRepository<Book, Long>, ListCrudRepository<Book, Long> {
 
}


服务层
接下来,让我们实现一个服务层,以便与书库交互并演示分页和排序。在这里,我们注入 BookRepository 并定义一个方法来检索分页和排序的图书。

@Service
public class BookService {
     
   @Autowired
   BookRepository bookRepository;
 
    public Page<Book> getAllBooks(Pageable pageable) {
        return bookRepository.findAll(pageable);
    }
 
    public List<Book> searchBooksByTitle(String title, Pageable pageable) {
        return bookRepository.findByTitleContaining(title, pageable);
    }
     
    public Page<Book> findAllPaginatedAndSorted(int pageNo, int pageSize, String sortBy, String sortDirection) {
        Sort sort = Sort.by(Sort.Direction.fromString(sortDirection), sortBy);
        Pageable pageable = PageRequest.of(pageNo, pageSize, sort);
         
        return bookRepository.findAll(pageable);
    }
   
}

上述代码:

  • public List<Book> searchBooksByTitle(String title, Pageable pageable) 方法根据包含特定子字符串的标题搜索图书,并根据提供的 Pageable 配置进行分页。它将调用委托给注入的 BookRepository 的 findByTitleContaining() 方法。
  • public Page<Book> findAllPaginatedAndSorted(int pageNo, int pageSize, String sortBy, String sortDirection) 方法从数据库中检索所有书籍,并根据提供的参数进行分页和排序。该方法根据排序方向和排序字段构建一个 Sort 对象,使用提供的分页参数创建一个 Pageable 对象,然后将调用委托给注入的 BookRepository 的 findAll() 方法。

在控制器中使用服务
最后,让我们创建一个 REST 控制器来公开我们的服务,并测试分页和排序功能。

@RestController
@RequestMapping("/api/books")
public class BookController {
 
    @Autowired
    BookService bookService;
 
    @GetMapping
    public List<Book> findAllPaginatedAndSorted(
            @RequestParam(defaultValue =
"0") int pageNo,
            @RequestParam(defaultValue =
"5") int pageSize,
            @RequestParam(defaultValue =
"id") String sortBy,
            @RequestParam(defaultValue =
"DESC") String sortDirection) {
        Page result = bookService.findAllPaginatedAndSorted(pageNo, pageSize, sortBy, sortDirection);
 
        return result.toList();
    }
 
    @GetMapping(
"/search")
    public List<Book> searchBooksByTitle(
            @RequestParam String title,
            @RequestParam(defaultValue =
"0") int pageNo,
            @RequestParam(defaultValue =
"5") int pageSize,
            @RequestParam(defaultValue =
"title") String sortBy) {
        Pageable pageable = PageRequest.of(pageNo, pageSize, Sort.by(sortBy));
        return bookService.searchBooksByTitle(title, pageable);
    }
 
}

图书控制器(BookController)提供了两个与图书交互的端点:一个用于检索所有图书的分页和排序列表,另一个用于按标题搜索图书,并进行分页和可选排序。

  • public List<Book> findAllPaginatedAndSorted(...)方法:可检索已分页和排序的图书列表。该方法从 URL 的查询字符串中获取分页(pageNo、pageSize)和排序(sortBy、sortDirection)参数。它将调用委托给注入的 BookService 的 findAllPaginatedAndSorted 方法。
  • public List<Book> searchBooksByTitle(...)方法:根据包含特定子字符串的书名搜索图书。它从 URL 的查询字符串中获取要搜索的标题(title)、分页(pageNo、pageSize)和排序(sortBy)参数。它根据分页和排序参数构建一个 Pageable 对象,然后将调用委托给注入的 BookService 的 searchBooksByTitle 方法。

测试应用程序
要在 H2 数据库中创建一个 SQL 表并加载一些示例值,我们可以在应用程序初始化过程中以编程方式执行 SQL 脚本,我们可以利用 schema.sql 和 data.sql 文件:

在 src/main/resources 文件夹中创建 schema.sql 文件。该文件包含创建表格的 SQL 命令。

CREATE TABLE IF NOT EXISTS Book (
    id INT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255),
    author VARCHAR(255)
);

在 src/main/resources 文件夹中创建 data.sql 文件。该文件包含向表中插入示例数据的 SQL 命令。

INSERT INTO Book (title, author) VALUES ('Core HTML5 Canvas', 'Geary');
INSERT INTO Book (title, author) VALUES ('Java EE 6 Platform with Glassfish 3', 'Goncalves');
INSERT INTO Book (title, author) VALUES ('Core Java Fundamentals', 'Horstmann Cornell');
INSERT INTO Book (title, author) VALUES ('JavaScript and JQuery', 'McFarland');
INSERT INTO Book (title, author) VALUES ('Real World Java EE Patterns', 'A.Bien');
INSERT INTO Book (title, author) VALUES ('Age of Reason', 'T.Paine');
INSERT INTO Book (title, author) VALUES ('Smashing CSS', 'Meyer');
INSERT INTO Book (title, author) VALUES ('The Essential Blender', 'Hess');
INSERT INTO Book (title, author) VALUES ('Pro JavaScript for Web Apps', 'Freeman');
INSERT INTO Book (title, author) VALUES ('Java EE 7 Essentials', 'A.Gupta');
INSERT INTO Book (title, author) VALUES ('JavaScript Enlightenment', 'Lindley');

更新 application.properties 文件。在 application.properties 文件中,我们需要指定以下属性,以告诉 Spring Boot 在应用程序启动时执行这些脚本:

application.properties

spring.datasource.initialization-mode=always
spring.datasource.schema=classpath:schema.sql
spring.datasource.data=classpath:data.sql


现在,我们可以运行 Spring Boot 应用程序,并使用查询参数向 /api/books 端点发出 HTTP 请求,以进行分页和排序。

例如,我们可以使用以下请求参数发送 HTTP 请求:PageNo = 0、PageSize = 5、SortBy = author、SortDirection = DESC 并观察输出结果:
http://localhost:8080/api/books?pageNo=0&pageSize=5&sortBy=author&sortDirection=desc

使用以下请求参数在网络浏览器上再次进行测试:PageNo = 1, PageSize = 4, SortBy = title, SortDirection = ASC

http://localhost:8080/api/books?pageNo=1&pageSize=4&sortBy=title&sortDirection=asc

结论
在本文中,我们探讨了如何使用 Spring Data JPA 的 PagingAndSortingRepository 接口在 Spring Boot 应用程序中实现分页和排序。利用这些功能,我们可以高效地管理大型数据集,提高查询性能,并增强应用程序的整体用户体验。