之前介绍过一次 SpringBoot + Spring Data JPA + Thymeleaf 分页,但是不够详细,这次再重新发一次。
先看效果图,动态图,可以点击放大。
因为数据有限,效果图里是设置一页显示一条记录。
这里是以评论列表作为例子,主要贴出和分页有关的代码,至于 Dao 和 Service 这里省略。
1、post_detail.html
本项目使用了 Spring Security ,开启了 CSRF 防护,所以需要在 head 里引入
这是 Thymeleaf 的替换标签
页面底部,引入 JS,将文章的id postId 传到 js 里
page.html
因为很多页面都要使用分页,所以完全可以把分页的代码剥离出来,公共使用。只需要将分页的数据的 model 写成 page 就行。这里分页分成了两种,一种是分页少于等于7个的,我们直接显示 1234567;另一种是大于7 的,则中间以..分隔。
为了方便大家的使用,这里同时提供 CSS 代码。
注:本模板是扒自哔哩哔哩,侵删
page.css
1、Spring Data JPA 提供了分页器,可以直接使用。
其中 pageable 可以通过创建 PageRequest 对象获得,需要传入 page 页码,size 一页显示数量。sort 是排序规则,相当于 sql 语句里的 order by,可选。
2、将现成 List 转成 Page,也可以这样
3、page 对象的几个属性
SSM + JSP 自定义分页
先看效果图,动态图,可以点击放大。
因为数据有限,效果图里是设置一页显示一条记录。
这里是以评论列表作为例子,主要贴出和分页有关的代码,至于 Dao 和 Service 这里省略。
一、Controller
- /**
- * ajax 获取评论页面数据
- *
- * @param postId
- * @param model
- * @return
- */
- @GetMapping
- public ModelAndView listComments(
- @RequestParam(value = "postId") Long postId,
- @RequestParam(value = "async", required = false, defaultValue = "new") Boolean async,
- @RequestParam(value = "order", required = false, defaultValue = "new") String order,
- @RequestParam(value = "pageIndex", required = false, defaultValue = "1") Integer pageIndex,
- @RequestParam(value = "pageSize", required = false, defaultValue = "1") Integer pageSize,
- Model model) {
- Page<Comment> commentPage = null;
- Post post = postService.getPostById(postId);
- String commentOrder = "new";
- try {
- if (order.equals("hot")) { // 最热查询
- Sort sort = new Sort(Sort.Direction.DESC, "zanSize", "id"); //根据点赞数量排序
- Pageable pageable = new PageRequest(pageIndex - 1, pageSize, sort);
- commentPage = commentService.listCommentByPost(post, pageable);
- commentOrder = "hot";
- } else if (order.equals("new")) { // 最新查询
- Sort sort = new Sort(Sort.Direction.DESC, "id");
- Pageable pageable = new PageRequest(pageIndex - 1, pageSize, sort);
- commentPage = commentService.listCommentByPost(post, pageable);
- }
- } catch (Exception e) {
- Pageable pageable = new PageRequest(0, 10);
- commentPage = commentService.listCommentByPost(post, pageable);
- }
- Integer commentSize = post.getCommentSize();
- model.addAttribute("page", commentPage);
- model.addAttribute("commentSize", commentSize);
- model.addAttribute("commentOrder", commentOrder);
- model.addAttribute("post", post);
- return new ModelAndView(async == true ? "home/post_detail :: #comment" : "home/post_detail");
- }
二、HTML 代码
1、post_detail.html
本项目使用了 Spring Security ,开启了 CSRF 防护,所以需要在 head 里引入
- <meta name="_csrf" th:content="${_csrf.token}"/>
- <meta name="_csrf_header" th:content="${_csrf.headerName}"/>
这是 Thymeleaf 的替换标签
- <!--分页-->
- <div th:replace="~{home/fragments/page :: header-page}"></div>
- <!--/分页-->
页面底部,引入 JS,将文章的id postId 传到 js 里
- <script th:inline="javascript">
- var postId = [[${post.id}]];
- var postUrl = '/' + [[${post.user.username}]] + '/posts/' + [[${post.id}]];
- </script>
page.html
- <!doctype html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport"
- content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
- <meta http-equiv="X-UA-Compatible" content="ie=edge">
- <title>分页</title>
- </head>
- <body>
- <div data-th-fragment="header-page">
- <div class="header-page paging-box"
- th:if="${(page.totalPages gt 0) && (page.totalPages le 7)}"
- data-th-attr="data-order=${commentOrder}">
- <span class="result">共[[${page.totalPages}]]页</span>
- <a href="javascript:void(0)" class="page-link prev"
- th:classappend="${page.first?'disabled':''}"
- data-th-attr="pageIndex=${page.number}">上一页</a>
- <a href="javascript:void(0)" class="page-link"
- th:classappend="${(page.number+1) eq i} ?'current':''"
- th:each="i: ${#numbers.sequence(1, page.totalPages)}"
- data-th-attr="pageIndex=${i}">[[${i}]]</a>
- <a href="javascript:void(0)" class="page-link next"
- th:classappend="${page.last?'disabled':''}"
- data-th-attr="pageIndex=${page.number+2}">下一页</a>
- </div>
- <div class="header-page paging-box"
- th:if="${page.totalPages gt 7}"
- data-th-attr="data-order=${commentOrder}">
- <span class="result">共[[${page.totalPages}]]页</span>
- <!--上一页-->
- <a href="javascript:void(0)" class="page-link prev"
- th:classappend="${page.first?'disabled':''}"
- data-th-attr="pageIndex=${page.number}">上一页</a>
- <!--首页-->
- <a href="javascript:void(0)" class="page-link"
- th:classappend="${(page.number+1) eq 1} ?'current':''"
- data-th-attr="pageIndex=1">
- 1
- </a>
- <!-- 当前页面小于等于4 -->
- <a href="javascript:void(0)" class="page-link"
- th:if="${(page.number+1) le 4}"
- th:classappend="${(page.number+1) eq i} ?'current':''"
- data-th-each="i : ${#numbers.sequence(2,5)}"
- data-th-attr="pageIndex=${i}">[[${i}]]</a>
- <span class="dian" data-th-if="${(page.number + 1) le 4}">...</span>
- <!-- 最后一页与当前页面之差,小于等于3 -->
- <span class="dian"
- data-th-if="${(page.totalPages-(page.number + 1)) le 3}">...</span>
- <a href="javascript:void(0)" class="page-link"
- th:if="${(page.totalPages-(page.number + 1)) le 3}"
- th:classappend="${(page.number+1) eq i} ?'current':''"
- th:each="i : ${#numbers.sequence(page.totalPages-4, page.totalPages-1)}"
- data-th-attr="pageIndex=${i}">[[${i}]]</a>
- <!-- 最后一页与当前页面之差大于3,且当前页面大于4-->
- <span class="dian"
- data-th-if="${((page.number + 1) gt 4) && ((page.totalPages-(page.number + 1)) gt 3 )}">...</span>
- <a href="javascript:void(0)" class="page-link"
- th:if="${((page.number + 1) gt 4) && ((page.totalPages-(page.number + 1)) gt 3 )}"
- data-th-attr="pageIndex=${page.number}">[[${page.number}]]</a>
- <a href="javascript:void(0)" class="page-link current"
- th:if="${((page.number + 1) gt 4) && ((page.totalPages-(page.number + 1)) gt 3 )}"
- data-th-attr="pageIndex=${page.number+1}">[[${page.number
- +1 }]]</a>
- <a href="javascript:void(0)" class="page-link"
- th:if="${((page.number + 1) gt 4) && ((page.totalPages-(page.number + 1)) gt 3 )}"
- data-th-attr="pageIndex=${page.number+2}">[[${page.number
- +2 }]]</a>
- <span class="dian" data-th-if="${((page.number + 1) gt 4) && ((page.totalPages-(page.number + 1)) gt 3 )}">...</span>
- <!--尾页-->
- <a href="javascript:void(0)" class="page-link"
- th:classappend="${(page.number+1) eq page.totalPages} ?'current':''"
- data-th-attr="pageIndex=${page.totalPages}">
- [[${page.totalPages}]]
- </a>
- <!--下一页-->
- <a href="javascript:void(0)" class="page-link next"
- th:classappend="${page.last?'disabled':''}"
- data-th-attr="pageIndex=${page.number+2}">下一页</a>
- </div>
- </div>
- <div data-th-fragment="bottom-page">
- <div class="bottom-page paging-box-big" th:if="${(page.totalPages gt 0) && (page.totalPages le 7)}"
- data-th-attr="data-order=${commentOrder}">
- <a href="javascript:void(0)" class="page-link prev"
- th:classappend="${page.first?'disabled':''}"
- data-th-attr="pageIndex=${page.number}">上一页</a>
- <a href="javascript:void(0)" class="page-link"
- th:classappend="${(page.number+1) eq i} ?'current':''"
- th:each="i: ${#numbers.sequence(1, page.totalPages)}"
- data-th-attr="pageIndex=${i}">[[${i}]]</a>
- <a href="javascript:void(0)" class="page-link next"
- th:classappend="${page.last?'disabled':''}"
- data-th-attr="pageIndex=${page.number+2}">下一页</a>
- <div class="page-jump">共<span>[[${page.totalPages}]]</span>页,跳至
- <input type="number" class="jump-page-size" th:max="${page.totalPages}">页
- </div>
- </div>
- <div class="bottom-page paging-box-big"
- th:if="${page.totalPages gt 7}"
- data-th-attr="data-order=${commentOrder}">
- <!--上一页-->
- <a href="javascript:void(0)" class="page-link prev"
- th:classappend="${page.first?'disabled':''}"
- data-th-attr="pageIndex=${page.number}">上一页</a>
- <!--首页-->
- <a href="javascript:void(0)" class="page-link"
- th:classappend="${(page.number+1) eq 1} ?'current':''"
- data-th-attr="pageIndex=1">
- 1
- </a>
- <!-- 当前页面小于等于4 -->
- <a href="javascript:void(0)" class="page-link"
- th:if="${(page.number+1) le 4}"
- th:classappend="${(page.number+1) eq i} ?'current':''"
- data-th-each="i : ${#numbers.sequence(2,5)}"
- data-th-attr="pageIndex=${i}">[[${i}]]</a>
- <span class="dian" data-th-if="${(page.number + 1) le 4}">...</span>
- <!-- 最后一页与当前页面之差,小于等于3 -->
- <span class="dian"
- data-th-if="${(page.totalPages-(page.number + 1)) le 3}">...</span>
- <a href="javascript:void(0)" class="page-link"
- th:if="${(page.totalPages-(page.number + 1)) le 3}"
- th:classappend="${(page.number+1) eq i} ?'current':''"
- th:each="i : ${#numbers.sequence(page.totalPages-4, page.totalPages-1)}"
- data-th-attr="pageIndex=${i}">[[${i}]]</a>
- <!-- 最后一页与当前页面之差大于3,且当前页面大于4-->
- <span class="dian"
- data-th-if="${((page.number + 1) gt 4) && ((page.totalPages-(page.number + 1)) gt 3 )}">...</span>
- <a href="javascript:void(0)" class="page-link"
- th:if="${((page.number + 1) gt 4) && ((page.totalPages-(page.number + 1)) gt 3 )}"
- data-th-attr="pageIndex=${page.number}">[[${page.number}]]</a>
- <a href="javascript:void(0)" class="page-link current"
- th:if="${((page.number + 1) gt 4) && ((page.totalPages-(page.number + 1)) gt 3 )}"
- data-th-attr="pageIndex=${page.number+1}">[[${page.number
- +1 }]]</a>
- <a href="javascript:void(0)" class="page-link"
- th:if="${((page.number + 1) gt 4) && ((page.totalPages-(page.number + 1)) gt 3 )}"
- data-th-attr="pageIndex=${page.number+2}">[[${page.number
- +2 }]]</a>
- <span class="dian" data-th-if="${((page.number + 1) gt 4) && ((page.totalPages-(page.number + 1)) gt 3 )}">...</span>
- <!--尾页-->
- <a href="javascript:void(0)" class="page-link"
- th:classappend="${(page.number+1) eq page.totalPages} ?'current':''"
- data-th-attr="pageIndex=${page.totalPages}">
- [[${page.totalPages}]]
- </a>
- <!--下一页-->
- <a href="javascript:void(0)" class="page-link next"
- th:classappend="${page.last?'disabled':''}"
- data-th-attr="pageIndex=${page.number+2}">下一页</a>
- <div class="page-jump">共<span>[[${page.totalPages}]]</span>页,跳至
- <input type="number" name="pageIndex" class="jump-page-size" min="1" th:max="${page.totalPages}">页
- </div>
- </div>
- <script>
- var pageIndex = [[${page.number+1}]]
- </script>
- </div>
- </body>
- </html>
因为很多页面都要使用分页,所以完全可以把分页的代码剥离出来,公共使用。只需要将分页的数据的 model 写成 page 就行。这里分页分成了两种,一种是分页少于等于7个的,我们直接显示 1234567;另一种是大于7 的,则中间以..分隔。
三、JS 代码
- $(function () {
- // 获取评论列表
- function getComment(postId, pageIndex, order) {
- var _ctx = $("meta[name='ctx']").attr("content");
- // 获取 CSRF Token
- var token = $("meta[name='_csrf']").attr("content");
- var header = $("meta[name='_csrf_header']").attr("content");
- $.ajax({
- url: _ctx + '/comments',
- type: 'GET',
- data: {
- "async": true,
- "postId": postId,
- "pageIndex": pageIndex,
- "order": order
- },
- beforeSend: function (request) {
- request.setRequestHeader(header, token); // 添加 CSRF Token
- },
- success: function (data) {
- $("#comment-wrapper").html(data);
- },
- error: function () {
- layer.msg("出现错误,请尝试刷新页面!", {icon: 2, anim: 6});
- }
- });
- };
- //切换评论排序规则
- $(document).on('click', '.tabs-order .new-sort', function () {
- var pageIndex = $(this).attr("pageIndex");
- getComment(postId, pageIndex, "new");
- });
- $(document).on('click', '.tabs-order .hot-sort', function () {
- var pageIndex = $(this).attr("pageIndex");
- getComment(postId, pageIndex, "hot");
- });
- //分页获取评论列表
- $(document).on('click', '.tcd-number', function () {
- var order = $(this).parents(".paging-box").attr("data-order");
- if ($(this).hasClass('current')) {
- return false;
- }
- var pageIndex = $(this).attr("pageIndex");
- getComment(postId, pageIndex, order);
- });
- //跳转到指定的页号
- $(document).on('keydown', '.jump-page-size', function (event) {
- var max = parseInt($(this).attr("max"));
- var pageIndex = parseInt($(this).val());
- var order = $('.paging-box').attr('data-order');
- if (event.keyCode == "13") {//keyCode=13是回车键
- if (pageIndex == "" || pageIndex == null) {
- return false;
- }
- if (!/^\d+$/.test(pageIndex)) {
- pageIndex = 1;
- }
- if (pageIndex < 1) {
- pageIndex = 1;
- }
- if (pageIndex > max) {
- pageIndex = max;
- }
- getComment(postId, pageIndex, order);
- }
- })
- // 初始化 博客评论列表
- getComment(postId, 1, "new");
- })
四、CSS 代码
为了方便大家的使用,这里同时提供 CSS 代码。
注:本模板是扒自哔哩哔哩,侵删
page.css
- .paging-box {
- font-size: 12px
- }
- .paging-box .disabled {
- cursor: not-allowed
- }
- .paging-box .current, .paging-box .dian, .paging-box .next, .paging-box .prev, .paging-box .tcd-number {
- color: #222;
- cursor: pointer;
- text-align: center;
- margin: 0 4px;
- text-decoration: none;
- line-height: 26px
- }
- .paging-box .current:hover, .paging-box .dian:hover, .paging-box .next:hover, .paging-box .prev:hover, .paging-box .tcd-number:hover {
- color: #00a1d6
- }
- .paging-box .current {
- color: #00a1d6;
- font-weight: 700
- }
- .paging-box .dian {
- cursor: default
- }
- .paging-box .dian:hover {
- color: #222
- }
- .paging-box .result {
- padding-right: 10px
- }
- .paging-box-big {
- font-size: 12px
- }
- .paging-box-big .disabled {
- display: none
- }
- .paging-box-big .current, .paging-box-big .dian, .paging-box-big .next, .paging-box-big .prev, .paging-box-big .tcd-number {
- color: #222;
- cursor: pointer;
- text-align: center;
- border-radius: 4px;
- background-color: #fff;
- border: 1px solid #ddd;
- background-image: none;
- transition: all .2s;
- font-size: 14px;
- min-width: 15px;
- margin: 0 2px;
- padding: 0 13px;
- display: inline-block;
- height: 36px;
- line-height: 36px;
- text-decoration: none
- }
- .paging-box-big .current, .paging-box-big .current:hover, .paging-box-big .dian:hover, .paging-box-big .next:hover, .paging-box-big .prev:hover, .paging-box-big .tcd-number:hover {
- background: #00a1d6;
- color: #fff;
- border: 1px solid #00a1d6
- }
- .paging-box-big .dian {
- cursor: default;
- border-color: #fff
- }
- .paging-box-big .dian:hover {
- background: #fff;
- color: #222;
- border: 1px solid #fff
- }
- .paging-box-big .next, .paging-box-big .prev {
- padding: 0 15px
- }
- .paging-box-big .page-jump {
- float: rightright;
- color: #99a2aa;
- line-height: 36px
- }
- .paging-box-big .page-jump input {
- margin: 0 5px;
- padding: 0 2px;
- height: 24px;
- line-height: 24px;
- margin-top: 7px;
- font-size: 12px;
- box-shadow: none;
- width: 40px;
- border-radius: 4px;
- border: 1px solid #ddd;
- outline: 0;
- text-align: center
- }
- .paging-box-big .page-jump input:focus {
- border-color: #00a1d6
- }
五、补充
1、Spring Data JPA 提供了分页器,可以直接使用。
- /**
- * 根据文章获取评论,分页显示
- *
- * @param post
- * @param pid
- * @return
- */
- Page<Comment> findByPostAndPid(Post post, Long pid, Pageable pageable);
其中 pageable 可以通过创建 PageRequest 对象获得,需要传入 page 页码,size 一页显示数量。sort 是排序规则,相当于 sql 语句里的 order by,可选。
- Sort sort = new Sort(Sort.Direction.DESC, "zanSize", "id");
- Pageable pageable = new PageRequest(pageIndex - 1, pageSize, sort);
2、将现成 List 转成 Page,也可以这样
- List<Comment> commentList = post.getCommentList()
- PageRequest pageRequest = new PageRequest(pageIndex,pageSize);
- Page<Comment> commentPage = new PageImpl<Comment>(commentList,pageRequest,commentList.size());
3、page 对象的几个属性
- 总共页数:${userDTOPage.totalPages}
- 记录总数:${userDTOPage.totalElements}
- 当前页号:${userDTOPage.number}
- 是否为首页:${userDTOPage.first}
- 是否为尾页:${userDTOPage.last}
- 每页显示的数量:${userDTOPage.numberOfElements}
六、其他分页内容
SpringBoot Spring Data JPA Thymeleaf 实现关注者和粉丝,互相关注分页显示
SpringMVC+Spring Data JPA + JSP + Bootstrap 分页实现和模糊查询分页
SSM + JSP 自定义分页
2021年01月16日 20:53:51
请问点击不同的页码之后是怎么做到的不进行网页跳转的?
2021年01月16日 20:55:36
@麦肯锡家:ajax
2021年01月16日 20:11:54
对了,这个分页的话是提早加载了还是显示某一页就加载特定的页呀?
2021年01月16日 20:12:54
@mckola_molas: 请求后才加载,并不是预加载
2021年01月16日 20:14:31
@言曌:我突然又发现了一个问题,可不可以用freemarker而不是Thmeleaf 实现这个功能吗?
2021年01月16日 20:15:53
@mckola_molas:当然可以,跟什么模板没有太大关系,FreeMarker和Thymleaf我都用过
2018年09月11日 11:43:22
Spring Boot 2.0之后已经废弃new PageRequest()方法,换用静态方法PageRequest.of(pageNum,pageSize,Sort.Direction.....)
2018年06月04日 20:49:08
你好,有源码吗或者github