1. 引言
在本文中,我们将介绍基于引导框架和弹簧JPA的百里叶分页组件。将数据库结果划分为页是许多应用程序中常用的功能。本教程将介绍如何使用百里叶模板创建一个漂亮的分页组件。
有关百里叶和自举表的更多有用信息,请查看以下链接:
百里叶功能
数据表组件与百里叶
2. 依赖关系
2.1. 对马文的依赖
POC(概念证明)应用程序是使用以下依赖项作为 Maven 项目创建的:
- 启动:弹簧启动器-启动器-web:2.1.5.发布 - 弹簧启动 Web 启动器,
- 启动器:弹簧启动器-数据-jpa:2.1.5.发布 - 弹簧启动数据 JPA 启动器,
- 启动:弹簧启动器-启动器-百里香:2.1.5.发布 - 百里叶模板引擎,
- 组织:龙目岛:1.18.2 - POJO 类的生成设置器/获取器方法,
- com.h2 数据库:h2:1.4.200 - H2 数据库引擎,
- 组织液基:液基-核心:3.8.8 - 管理和执行数据库更改,
- org.webjars:引导:4.0.0-2 - 用于引导框架的 webjar 库。
2.2. 前端库
- 引导 - 用于创建响应式网站的前端框架。
项目 Maven 文件具有以下结构:pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>thymeleaf-bootstrap-pagination</artifactId>
<properties>
<bootstrap.version>4.0.0-2</bootstrap.version>
<webjars-locator.version>0.30</webjars-locator.version>
<lombok.version>1.18.2</lombok.version>
<liquibase-core.version>3.8.8</liquibase-core.version>
<h2.version>1.4.200</h2.version>
</properties>
<!-- Inherit defaults from Spring Boot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
</parent>
<!-- Add typical dependencies for a web application -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>${liquibase-core.version}</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>${bootstrap.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2.version}</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator</artifactId>
<version>${webjars-locator.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<!-- Package as an executable jar -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3. 模型、视图、控制器层
使用 H2 数据库和 Liquibase 管理数据库更改的示例应用程序。
因此,首先我们需要在文件中配置这些库:application.properties
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:testdb;MODE=PostgreSQL;DB_CLOSE_DELAY=-1
spring.datasource.username=h2
spring.datasource.password=pass
spring.liquibase.change-log=classpath:/db-scripts/db-changelog-master.xml
在数据库层中,我们创建一个具有字段(如 和 )的实体。Post
title
body
package com.frontbackend.thymeleaf.bootstrap.posts.entity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity(name = "POSTS")
@NoArgsConstructor
@Setter
@Getter
public class Post {
@Id
private Long id;
private String title;
private String body;
}
从扩展的数据访问对象 () 中,我们将使用采用该参数的方法。 类存储限制和排序结果记录所需的所有信息。PostDAO
JpaRepository
findAll
Pageable
Pageable
package com.frontbackend.thymeleaf.bootstrap.posts.control.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import com.frontbackend.thymeleaf.bootstrap.posts.entity.Post;
public interface PostDAO extends JpaRepository<Post, Long> {
}
PostService
类负责准备类,并用实例包装结果:PageRequest
Paged
package com.frontbackend.thymeleaf.bootstrap.posts.control.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import com.frontbackend.thymeleaf.bootstrap.posts.control.dao.PostDAO;
import com.frontbackend.thymeleaf.bootstrap.posts.entity.Post;
import com.frontbackend.thymeleaf.bootstrap.posts.entity.paging.Paged;
import com.frontbackend.thymeleaf.bootstrap.posts.entity.paging.Paging;
@Service
public class PostService {
private final PostDAO postDAO;
@Autowired
public PostService(PostDAO postDAO) {
this.postDAO = postDAO;
}
public Paged<Post> getPage(int pageNumber, int size) {
PageRequest request = PageRequest.of(pageNumber - 1, size, new Sort(Sort.Direction.ASC, "id"));
Page<Post> postPage = postDAO.findAll(request);
return new Paged<>(postPage, Paging.of(postPage.getTotalPages(), pageNumber, size));
}
}
Paged
、 和 是用于准备分页组件的实用程序类。PageItem
PageItemType
Paging
该类具有以下结构:Paged
package com.frontbackend.thymeleaf.bootstrap.posts.entity.paging;
import org.springframework.data.domain.Page;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Paged<T> {
private Page<T> page;
private Paging paging;
}
PageItem
具有以下结构:
package com.frontbackend.thymeleaf.bootstrap.posts.entity.paging;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Setter
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PageItem {
private PageItemType pageItemType;
private int index;
private boolean active;
}
PageItemType
是包含两个值的枚举:
package com.frontbackend.thymeleaf.bootstrap.posts.entity.paging;
public enum PageItemType {
DOTS,
PAGE
}
Paging
类负责计算页面和点在分页组件中的显示方式:
package com.frontbackend.thymeleaf.bootstrap.posts.entity.paging;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Setter
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Paging {
private static final int PAGINATION_STEP = 3;
private boolean nextEnabled;
private boolean prevEnabled;
private int pageSize;
private int pageNumber;
private List<PageItem> items = new ArrayList<>();
public void addPageItems(int from, int to, int pageNumber) {
for (int i = from; i < to; i++) {
items.add(PageItem.builder()
.active(pageNumber != i)
.index(i)
.pageItemType(PageItemType.PAGE)
.build());
}
}
public void last(int pageSize) {
items.add(PageItem.builder()
.active(false)
.pageItemType(PageItemType.DOTS)
.build());
items.add(PageItem.builder()
.active(true)
.index(pageSize)
.pageItemType(PageItemType.PAGE)
.build());
}
public void first(int pageNumber) {
items.add(PageItem.builder()
.active(pageNumber != 1)
.index(1)
.pageItemType(PageItemType.PAGE)
.build());
items.add(PageItem.builder()
.active(false)
.pageItemType(PageItemType.DOTS)
.build());
}
public static Paging of(int totalPages, int pageNumber, int pageSize) {
Paging paging = new Paging();
paging.setPageSize(pageSize);
paging.setNextEnabled(pageNumber != totalPages);
paging.setPrevEnabled(pageNumber != 1);
paging.setPageNumber(pageNumber);
if (totalPages < PAGINATION_STEP * 2 + 6) {
paging.addPageItems(1, totalPages + 1, pageNumber);
} else if (pageNumber < PAGINATION_STEP * 2 + 1) {
paging.addPageItems(1, PAGINATION_STEP * 2 + 4, pageNumber);
paging.last(totalPages);
} else if (pageNumber > totalPages - PAGINATION_STEP * 2) {
paging.first(pageNumber);
paging.addPageItems(totalPages - PAGINATION_STEP * 2 - 2, totalPages + 1, pageNumber);
} else {
paging.first(pageNumber);
paging.addPageItems(pageNumber - PAGINATION_STEP, pageNumber + PAGINATION_STEP + 1, pageNumber);
paging.last(totalPages);
}
return paging;
}
}
GET requests are handled by the class. To change the current page and page size we need to add two request parameters:PostController
- 页码 - 当前页码(默认第一页),
- 大小 - 页面大小(默认为 5 行):
package com.frontbackend.thymeleaf.bootstrap.posts.boundary;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.frontbackend.thymeleaf.bootstrap.posts.control.service.PostService;
@Controller
@RequestMapping("/")
public class PostController {
private final PostService postService;
@Autowired
public PostController(PostService postService) {
this.postService = postService;
}
@GetMapping
public String posts(@RequestParam(value = "pageNumber", required = false, defaultValue = "1") int pageNumber,
@RequestParam(value = "size", required = false, defaultValue = "5") int size, Model model) {
model.addAttribute("posts", postService.getPage(pageNumber, size));
return "posts";
}
}
是 Java 类,其主要方法是启动弹簧启动应用程序服务器:Application
package com.frontbackend.thymeleaf.bootstrap;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
4. 百里叶模板
表示层包含单个百里叶模板:posts.html
该视图具有以下结构:posts.html
<!DOCTYPE HTML>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>Spring Boot Thymeleaf Application - Bootstrap Pagination</title>
<link th:rel="stylesheet" th:href="@{webjars/bootstrap/4.0.0-2/css/bootstrap.min.css} "/>
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark static-top">
<div class="container">
<a class="navbar-brand" href="/">Thymeleaf - Bootstrap Pagination</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive"
aria-controls="navbarResponsive"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ml-auto">
<li class="nav-item active">
<a class="nav-link" href="#">Home
<span class="sr-only">(current)</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">About</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Services</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Contact</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container">
<div class="row">
<div class="col-lg-10 mt-5 mb-5">
<table id="posts" class="table table-bordered table-responsive-sm">
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Body</th>
</tr>
</thead>
<tbody>
<tr th:each="post : ${posts.page}">
<td th:text="${post.id}">id</td>
<td th:text="${post.title}">title</td>
<td th:text="${post.body}">body</td>
</tr>
</tbody>
</table>
<nav aria-label="Page navigation" class="paging">
<ul class="pagination" th:if="${posts.page.totalPages > 1}">
<li class="page-item" th:classappend="${!posts.paging.isPrevEnabled()? 'disabled' : ''}">
<a class="page-link" th:href="@{'/?pageNumber=' + ${posts.paging.pageNumber - 1}}"
tabindex="-1">Previous</a>
</li>
<th:block th:each="item : ${posts.paging.getItems()}">
<li class="page-item" th:classappend="${item.index == posts.paging.pageNumber? 'active' : ''}"
th:if="${item.pageItemType.name() == 'PAGE'}">
<a class="page-link" th:href="@{'/?pageNumber=' + ${item.index}}"
th:text="${item.index}"></a>
</li>
<li class="page-item disabled" th:if="${item.pageItemType.name() == 'DOTS'}">
<a class="page-link" href="#">...</a>
</li>
</th:block>
<li class="page-item" th:classappend="${!posts.paging.isNextEnabled()? 'disabled' : ''}">
<a class="page-link" th:href="@{'/?pageNumber=' + ${posts.paging.pageNumber + 1}}">Next</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<script th:src="@{/webjars/jquery/jquery.min.js}"></script>
<script th:src="@{/webjars/popper.js/umd/popper.min.js}"></script>
<script th:src="@{/webjars/bootstrap/js/bootstrap.min.js}"></script>
</body>
</html>
分页组件存储在导航标记中:。此代码可以很容易地移动到片段中,但为了示例应用程序的清晰度,我们将其与表一起保存在单个文件中。<nav aria-label="Page navigation" class="paging">...</nav>
5. 输出
正在运行的应用程序位于 URL 下,并提供以下功能:http://locahost:8080
6. 结论
在本文中,我们介绍了如何基于春季JPA和引导框架构建百里叶分页组件。我们将弹簧启动应用程序与 H2 内存数据库、JPA 和 Liquibase 结合使用来创建数据库结构,并用样本 100 条记录填充它。
像往常一样,本文中使用的代码位于我们的 GitHub 存储库下。