Springboot 系列 (5) - 在 Spring Boot 项目里使用 JPA 和 Thymeleaf 实现分页显示

 

JPA 是Java Persistence API 的简称,它是 Sun 公司在充分吸收现有ORM框架(Hibernate)的基础上,开发而来的一个Java EE 5.0 平台标准的开源的对象关系映射(ORM)规范。

Hibernate 与 JPA 的关系:

    Hibernate 是一个开放源代码的对象关系映射(ORM)框架,它对 JDBC 进行了非常轻量级的对象封装,将 POJO 与 数据库表建立映射关系,是一个全自动的ORM框架,Hibernate 可以自动生成 SQL 语句,自动执行,使 Java 程序员可以方便地使用面向对象思维来操作数据库。

    而 JPA 是 Sun 官方提出的 Java 持久化规范,而 JPA 是在充分吸收 Hibernate、TopLink 等 ORM 框架的基础上发展而来的。

    JPA 是持久化的关系映射规范、接口 API,而 Hibernate 是其实现。


1. 开发环境


    Windows版本:Windows 10 Home (20H2)   
    IntelliJ IDEA (https://www.jetbrains.com/idea/download/):Community Edition for Windows 2020.1.4
    Apache Maven (https://maven.apache.org/):3.8.1

    注:Spring 开发环境的搭建,可以参考 “ Spring基础知识(1)- Spring简介、Spring体系结构和开发环境配置 ”。


2. 创建 Spring Boot 基础项目

    项目实例名称:SpringbootExample05
    Spring Boot 版本:2.6.6

    创建步骤:

        (1) 创建 Maven 项目实例 SpringbootExample05;
        (2) Spring Boot Web 配置;
        (3) 导入 Thymeleaf 依赖包;
        (4) 配置静态资源(jQuery、Bootstrap、Images);
        
    具体操作请参考 “Springboot 系列 (2) - 在 Spring Boot 项目里使用 Thymeleaf、JQuery+Bootstrap 和国际化” 里的项目实例 SpringbootExample02,文末包含如何使用 spring-boot-maven-plugin 插件运行打包的内容。

    SpringbootExample05 和 SpringbootExample02 相比,SpringbootExample05 不包含 Thymeleaf 模版文件(templates/*.htm l)和国际化。


3. 配置数据库(MariaDB)

    1) XAMPP for Windows

        https://www.apachefriends.org/download.html
        
        本文安装版本 7.4.25,用到 XAMPP 的如下功能:

            + Apache 2.4.51
            + MariaDB 10.4.21 (MySQL的分支)
            + PHP 7.4.25 (VC15 X86 64bit thread safe) + PEAR
            + phpMyAdmin 5.1.1

    2) 创建数据库 springboot_example 和 product 表,SQL 脚本如下

 1         CREATE TABLE `product` (
 2             `id` int(11) NOT NULL,
 3             `name` varchar(50) NOT NULL,
 4             `description` varchar(100) DEFAULT NULL,
 5             `price` double DEFAULT NULL,
 6             `createtime` timestamp NULL DEFAULT NULL
 7         ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 8 
 9         ALTER TABLE `product` ADD PRIMARY KEY (`id`);
10 
11         ALTER TABLE `product` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
12 
13         INSERT INTO `product` (`id`, `name`, `description`, `price`, `createtime`) VALUES
14         (1, 'apple', 'Red apple', 7.84, '2020-01-01 01:01:01'),
15         (2, 'orange', 'Yellow orange', 11.6, '2020-02-02 02:02:02'),
16         (3, 'phone', 'Android phone', 105.30, '2020-03-03 03:03:03'),
17         (4, 'tea', 'Green tea', 6.3, '2020-04-04 04:04:04'),
18         (5, 'paper', 'White paper A4', 3.5, '2020-05-05 05:05:05'),
19         (6, 'water', 'Pure water', 1.2, '2020-06-06 06:06:06'),
20         (7, 'test1', 'Test description 1', 1.0, '2020-07-07 07:07:07'),
21         (8, 'test2', 'Test description 2', 2.0, '2020-08-08 08:08:08'),
22         (9, 'test3', 'Test description 3', 3.0, '2020-09-09 09:09:09'),
23         (10, 'test4', 'Test description 4', 4.0, '2020-10-10 10:10:10'),
24         (11, 'test5', 'Test description 5', 5.0, '2020-11-11 11:11:11'),
25         (12, 'test6', 'Test description 6', 6.0, '2020-12-12 12:12:12'),
26         (13, 'test7', 'Test description 7', 7.0, '2020-12-13 13:13:13'),
27         (14, 'test8', 'Test description 8', 8.0, '2020-12-14 14:14:14'),
28         (15, 'test9', 'Test description 9', 9.0, '2020-12-15 15:15:15'),
29         (16, 'test10', 'Test description 10', 10.0, '2020-12-16 16:16:16'),
30         (17, 'test11', 'Test description 11', 11.0, '2020-12-17 17:17:17');


        注:定义表名时,不要和 SQL 的关键字同名

    3)创建 src/main/java/com/example/entity/Product.java 文件

 1         package com.example.entity;
 2 
 3         import java.util.Date;
 4         import javax.persistence.Column;
 5         import javax.persistence.Entity;
 6         import javax.persistence.Id;
 7         import javax.persistence.Table;
 8         import javax.persistence.GenerationType;
 9         import javax.persistence.GeneratedValue;
10 
11         @Entity
12         @Table(name = "product")
13         public class Product {
14             @Id
15             @GeneratedValue(strategy=GenerationType.AUTO)
16             private Integer id;
17             @Column(name = "name", length = 50)
18             private String name;
19             @Column(name = "description", length = 100)
20             private String description;
21             @Column(name = "price")
22             private Double price;
23             @Column(name = "createtime")
24             private Date createtime;
25 
26             public Product() {
27 
28             }
29 
30             public Integer getId() {
31                 return id;
32             }
33             public void setId(Integer id) {
34                 this.id = id;
35             }
36 
37             public String getName() {
38                 return name;
39             }
40             public void setName(String name) {
41                 this.name = name;
42             }
43 
44             public String getDescription() {
45                 return description;
46             }
47             public void setDescription(String description) {
48                 this.description = description;
49             }
50 
51             public Double getPrice() {
52                 return price;
53             }
54             public void setPrice(Double price) {
55                 this.price = price;
56             }
57 
58             public Date getCreatetime() {
59                 return createtime;
60             }
61             public void setCreatetime(Date createtime) {
62                 this.createtime = createtime;
63             }
64 
65             @Override
66             public String toString() {
67                 return "Product {" +
68                         "name = " + name +
69                         ", description = " + description +
70                         ", price = " + price +
71                         ", createtime = " + createtime +
72                         '}';
73             }
74         }


4. 配置 JPA

    1) 修改 pom.xml,导入 JPA 依赖包

 1         <project ... >
 2             ...
 3             <dependencies>
 4                 ...
 5 
 6                 <!-- MariaDB -->
 7                 <dependency>
 8                     <groupId>org.mariadb.jdbc</groupId>
 9                     <artifactId>mariadb-java-client</artifactId>
10                 </dependency>
11                 <!-- JDBC -->
12                 <dependency>
13                     <groupId>org.springframework.boot</groupId>
14                     <artifactId>spring-boot-starter-data-jdbc</artifactId>
15                 </dependency>
16                 <!-- JPA -->
17                 <dependency>
18                     <groupId>org.springframework.boot</groupId>
19                     <artifactId>spring-boot-starter-data-jpa</artifactId>
20                 </dependency>
21 
22                 ...
23             </dependencies>
24 
25             ...
26         </project>


        在IDE中项目列表 -> SpringbootExample05 -> 点击鼠标右键 -> Maven -> Reload Project

    2) 修改 src/main/resources/application.properties 文件

 1         spring.main.banner-mode=off
 2 
 3         # Web server
 4         server.display-name=SpringbootExample05-Test
 5         server.address=localhost
 6         server.port=9090
 7 
 8         # 数据源连接信息
 9         spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
10         spring.datasource.url=jdbc:mysql://localhost:3306/springboot_example
11         spring.datasource.username=root
12         spring.datasource.password=123456
13 
14         # JPA 相关配置
15         #spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
16         spring.jpa.show-sql=true
17         #spring.jpa.properties.hibernate.format_sql=true
18         #spring.jpa.hibernate.ddl-auto=update


    3) 创建 src/main/java/com/example/dao/ProductJpaRepository.java 文件

 1         package com.example.dao;
 2 
 3         import org.springframework.data.jpa.repository.JpaRepository;
 4 
 5         import com.example.entity.Product;
 6 
 7         public interface ProductJpaRepository extends JpaRepository<Product, Integer> {
 8 
 9             //
10         }

 

  注:在 ProductJpaRepository 里无需重写和重载方法。


5. 视图和控制器

    1) 创建 src/main/resources/templates/common.html 文件

 1         <div th:fragment="header(var)">
 2             <meta charset="UTF-8">
 3             <title th:text="${var}">Title</title>
 4             <link rel="stylesheet" th:href="@{/lib/bootstrap-4.2.1-dist/css/bootstrap.min.css}" href="/lib/bootstrap-4.2.1-dist/css/bootstrap.min.css">
 5             <script language="javascript" th:src="@{/lib/jquery/jquery-3.6.0.min.js}" src="/lib/jquery/jquery-3.6.0.min.js"></script>
 6             <script language="javascript" th:src="@{/lib/bootstrap-4.2.1-dist/js/bootstrap.min.js}" src="/lib/bootstrap-4.2.1-dist/js/bootstrap.min.js"></script>
 7         </div>
 8 
 9         <div th:fragment="content-header" class="container" id="content-header-id">
10             <nav class="navbar navbar-light bg-light">
11                 <a class="navbar-brand" href="#">
12                     <img th:src="@{/images/bootstrap-solid.svg}" src="/images/bootstrap-solid.svg" width="30" height="30" class="d-inline-block align-top" alt="">
13                     Thymeleaf Demo
14                 </a>
15 
16                 <a class="nav-link" th:href="@{/logout}" th:if="${session.loginUser} != null">Logout</a>
17             </nav>
18         </div>
19 
20         <div th:fragment="content-footer(var)" class="container" id="content-footer-id">
21             <p th:text="${var}">Content Footer</p>
22         </div>


    2) 创建 src/main/resources/templates/product/index.html 文件

 1         <!DOCTYPE html>
 2         <html lang="en" xmlns:th="http://www.thymeleaf.org">
 3         <head th:include="common::header(var='Product Index')">
 4         </head>
 5         <body>
 6         <div th:replace="common::content-header"></div>
 7 
 8         <div class="container" id="content" th:style="'min-height: 480px; padding: 10px;'">
 9             <h4>Product Index Page</h4>
10 
11             <p>&nbsp;</p>
12             <div class="alert alert-info" role="alert" th:text="${message}" th:if ="${message} != null"></div>
13 
14             <table class="table table-bordered">
15                 <thead>
16                     <tr>
17                         <th scope="col">#</th>
18                         <th scope="col">Name</th>
19                         <th scope="col">Description</th>
20                         <th scope="col">Price</th>
21                         <th scope="col">Create Time</th>
22                     </tr>
23                 </thead>
24                 <tbody th:if="${pageData} != null">
25                     <tr th:each="row:${pageData}" >
26                         <td th:object="${row}" th:text="*{id}"></td>
27                         <td th:object="${row}" th:text="*{name}"></td>
28                         <td th:object="${row}" th:text="*{description}"></td>
29                         <td th:object="${row}" th:text="*{price}"></td>
30                         <td th:object="${row}" th:text="*{createtime}"></td>
31                     </tr>
32                 </tbody>
33                 <tbody th:if ="${pageData} == null">
34                     <tr>
35                         <td colspan="5">No record</td>
36                     </tr>
37                 </tbody>
38             </table>
39 
40             <nav aria-label="Page navigation example" th:if="${pageData.getTotalPages() gt 1}">
41                 <ul class="pagination justify-content-end">
42 
43                     <li th:if="${pageData.hasPrevious()}" class="page-item">
44                         <a th:href="@{/product?index=1}" href="/product?index=1" class="page-link">&lt;&lt;</a>
45                     </li>
46                     <li th:if="${pageData.hasPrevious()}" class="page-item">
47                         <a th:href="@{/product?index={v}&step={s}(v=${pageData.getNumber()},s=${pageData.getSize()})}" href="/product?index=&step=" class="page-link">&lt;</a>
48                     </li>
49 
50                     <li th:unless="${pageData.hasPrevious()}" class="page-item disabled"><a href="#" class="page-link">&lt;&lt;</a></li>
51                     <li th:unless="${pageData.hasPrevious()}" class="page-item disabled"><a href="#" class="page-link">&lt;</a></li>
52 
53                     <li th:if="${pageData.getTotalPages() le 7}" th:each="i:${#numbers.sequence(1, pageData.getTotalPages())} " th:class="${i eq pageData.getNumber()+1}?'page-item active':'page-item'">
54                         <a th:href="@{/product?index={v}&step={s}(v=${i}, s=${pageData.getSize()})}" href="/product?index=&step=" th:text="${i}" class="page-link"></a>
55                     </li>
56 
57                     <li th:if="${pageData.getTotalPages() gt 7}" th:each="i:${#numbers.sequence(1, 3)}" th:class="${i eq pageData.getNumber()+1}?'page-item active':'page-item'">
58                         <a th:href="@{/product?index={v}&step={s}(v=${i}, s=${pageData.getSize()})}" href="/product?index=&step=" th:text="${i}" class="page-link"></a>
59                     </li>
60                     <li th:if="${pageData.getTotalPages() gt 7}" th:class="'page-item disabled'">
61                         <a href="#" class="page-link">...</a>
62                     </li>
63                     <li th:if="${pageData.getTotalPages() gt 7}" th:each="i:${#numbers.sequence(pageData.getTotalPages()-2, pageData.getTotalPages())}" th:class="${i eq pageData.getNumber()+1}?'page-item active':'page-item'">
64                         <a th:href="@{/product?index={v}&step={s}(v=${i}, s=${pageData.getSize()})}" href="/product?index=&step=" th:text="${i}" class="page-link"></a>
65                     </li>
66 
67                     <li th:if="${pageData.hasNext()}" class="page-item">
68                         <a th:href="@{/product?index={v}&step={s}(v=${pageData.getNumber()+2},s=${pageData.getSize()})}" href="/product?index=&step=" class="page-link">&gt;</a>
69                     </li>
70                     <li th:if="${pageData.hasNext()}" class="page-item">
71                         <a th:href="@{/product?index={v}(v=${pageData.getTotalPages()})}" href="/product?index=" class="page-link">&gt;&gt;</a>
72                     </li>
73 
74                     <li th:unless="${pageData.hasNext()}" class="page-item disabled"><a href="#" class="page-link">&gt;</a></li>
75                     <li th:unless="${pageData.hasNext()}" class="page-item disabled"><a href="#" class="page-link">&gt;&gt;</a></li>
76 
77                 </ul>
78             </nav>
79 
80         </div>
81 
82         <div th:replace="common::content-footer(var='Copyright &copy; 2020')"></div>
83 
84         <script type="text/javascript">
85                 $(document).ready(function(){
86                     console.log("jQuery is running");
87                 });
88             </script>
89         </body>
90         </html>


        注:本示例,页数小于等于 7 时,显示全部页码。大于 7 时,只显示前3个页码和后3个页码,其余用省略号替代。

    3) 创建 src/main/java/com/example/controller/ProductController.java 文件

 1         package com.example.controller;
 2 
 3         import javax.servlet.http.HttpServletRequest;
 4 
 5         import org.springframework.beans.factory.annotation.Autowired;
 6         import org.springframework.ui.Model;
 7         import org.springframework.stereotype.Controller;
 8         import org.springframework.web.bind.annotation.RequestMapping;
 9         import org.springframework.web.bind.annotation.RequestMethod;
10         import org.springframework.util.StringUtils;
11 
12         import org.springframework.data.domain.Page;
13         import org.springframework.data.domain.PageRequest;
14         import org.springframework.data.domain.Pageable;
15         import org.springframework.data.domain.Sort;
16 
17         import com.example.entity.Product;
18         import com.example.dao.ProductJpaRepository;
19 
20         @Controller
21         public class ProductController {
22             @Autowired
23             private ProductJpaRepository productJpaRepository;
24 
25             @RequestMapping(value = "/product", method = RequestMethod.GET)
26             public String product(HttpServletRequest request, Model model) {
27 
28                 Integer index = 1;
29                 Integer step = 5;
30                 String strIndex = request.getParameter("index");
31                 if (StringUtils.hasText(strIndex)) {
32                     index = Integer.valueOf(strIndex);
33                     if (index < 1)
34                         index = 1;
35                 }
36                 String strStep = request.getParameter("step");
37                 if (StringUtils.hasText(strStep)) {
38                     step = Integer.valueOf(strStep);
39                     if (step < 5)
40                         step = 5;
41                 }
42 
43                 Sort sort = Sort.by(Sort.Direction.ASC, "id");
44                 Pageable pageable= PageRequest.of(index-1, step, sort );
45                 Page<Product> pageData = productJpaRepository.findAll(pageable);
46 
47                 model.addAttribute("pageData", pageData);
48 
49                 return "product/index";
50             }
51         }


        注:PageRequest 的 page 参数是从 0 开始的,所以 index-1,对应 pageData.getNumber() 取到的当前页也是从 0 开始的。

        运行并访问 http://localhost:9090/product

 

--------------------------------------

示例代码:https://gitee.com/slksm/public-codes/tree/master/demos/springboot-series/SpringbootExample05

 

posted @ 2022-05-05 14:35  垄山小站  阅读(385)  评论(0)    收藏  举报