文章目錄
前言
繼上次寫了springboot+mybatis+ajax實現註冊功能後,這幾天花了時間寫了註冊功能,這個功能不是用ajax異步實現的,而是控制層直接跳轉頁面,然後利用thymeleaf在前端展示, 這裏我還是推薦用ajax,因爲ajax實現的話,前端的一些效果不會被侷限到。 因爲我之前實現註冊功能中很詳細的寫了用spring boot的整個流程,所以這一篇主要寫pagehelper分頁插件的使用還有前端thymeleaf要怎麼處理等等一些細節,這裏的一些配置都在註冊功能那一篇的基礎上加。
正文
業務場景
前端commons.html頁面中標題欄有“專題”和“分類”(因爲兩者實現是一樣的,所以我只寫“專題”的實現),點擊其下的標籤,。
跳轉到archive.html頁面,該頁面展示出分頁的內容。
後端
pom.xml
這裏說明一下,有的教程是用pagehelper的,兩種其實本質都是一樣的,寫法配置可能不同,具體看mybatis使用pagehelper-spring-boot-starter與pagehelper的區別
<!--分頁插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.5</version>
</dependency>
application.yml
或者自己配置到application.properties中也一樣。
#分頁設置
pagehelper:
reasonable: true # reasonable:啓用合理化時,如果pageNum<1會查詢第一頁,如果pageNum>pages會查詢最後一頁。
support-methods-arguments: true
params: count=countSql
helper-dialect: mysql #數據庫類型
額外話題,這裏修改一下application.yml原來數據庫的配置,因爲我的數據庫是放在centos虛擬機裏面的,一開始查出來的時間相差8個小時,後面查了一下是我的虛擬機時區不是中國的(改虛擬機時區教程:mybatis查詢mysql數據庫出來的時間少8小時解決辦法),後面改了時區後又相差14小時,後面查了說是mysql的時區不正確。所以下面的url加上了時區設定。
#因“CST”時區協商誤解導致時間差了14或13小時 https://www.cnblogs.com/todayforever/p/11997424.html 與數據庫連接時,定義時區,避免mybatis框架從mysql獲取時區。在連接上加上 serverTimezone=GMT%2B8
url: jdbc:mysql://192.168.0.113:3306/videoweb?serverTimezone=GMT%2B8 #路徑,這裏用虛擬機的mysql
實體類video.java和User.java----映射VideoMapper.xml----VideoMapper接口
因爲我這是個視頻網站,數據庫設計如下,所以需要查出視頻的相關信息以及視頻發佈者的名字,所以要多表查詢,mapper看起來可能複雜點。這裏涉及mybatis多表關係就不細說了,具體自己百度,速成的話我推薦how2j.cn中mybatis的教程,裏面就有多表關係快速使用教程。另外要說明一下,mapper.xml查詢到的屬性必須和實體類中的屬性相對應,到時候前端才能取到。 舉個例子我一開始video表是沒有countcomments、countlikes、countlooks這三項的,實體類Video中也沒有對應的屬性,而是用mysql中count()函數進行復雜查詢查出相關數據,後面發現前端沒辦法取到值,因爲儘管查詢語句查到了,但是mybatis不知道你的數據要存到實體類中的哪個值。
數據庫
import java.util.Date;
import java.util.List;
public class Video {
private Integer vid; //視頻id
private String vname; //視頻名
private String vtag; //視頻標籤
// @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") //https://www.cnblogs.com/huanggy/p/9471827.html
// @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date date; //上傳日期
private String introduce; //視頻介紹
private String url; //視頻路徑
private String imageurl; //視頻封面路徑
private Byte state; //視頻狀態,通過/審覈
private String special; //專題
private Integer countcomments; //評論數
private Integer countlikes; //喜歡數
private Integer countlooks; //觀看數
// 多對一,多個視頻由一個用戶上傳
private User user;
// 一對多,一個視頻有多個用戶喜歡的記錄
List<LikesKey> likesKeys;
// 一對多,一個視頻多條評論
List<Comments> comments;
省略get和set方法....
}
import java.util.Date;
public class User { //用戶
private Integer uid; //用戶id
private String username; //用戶名
private String password; //用戶密碼
private String email; //用戶郵箱
private Date birthday; //用戶出生日期
省略get和set方法....
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.liaojiexin.videoweb.mapper.VideoMapper" >
<resultMap id="BaseResultMap" type="com.liaojiexin.videoweb.entity.Video" >
<id column="vid" property="vid" jdbcType="INTEGER" />
<result column="vname" property="vname" jdbcType="VARCHAR" />
<result column="vtag" property="vtag" jdbcType="CHAR" />
<result column="date" property="date" jdbcType="TIMESTAMP" />
<!-- <result column="uid" property="uid" jdbcType="INTEGER" />-->
<result column="introduce" property="introduce" jdbcType="VARCHAR" />
<result column="url" property="url" jdbcType="VARCHAR" />
<result column="imageurl" property="imageurl" jdbcType="VARCHAR" />
<result column="state" property="state" jdbcType="TINYINT" />
<result column="special" property="special" jdbcType="VARCHAR" />
<result column="countcomments" property="countcomments" jdbcType="INTEGER" />
<result column="countlikes" property="countlikes" jdbcType="INTEGER" />
<result column="countlooks" property="countlooks" jdbcType="INTEGER" />
<!-- 多對一的關係 -->
<!-- column:指的是數據庫隊列名,property: 指的是屬性名稱, javaType:指的是屬性的類型 -->
<association property="user" javaType="com.liaojiexin.videoweb.entity.User">
<id column="uid" property="uid" jdbcType="INTEGER" />
<result column="username" property="username" jdbcType="VARCHAR" />
<result column="password" property="password" jdbcType="VARCHAR" />
<result column="email" property="email" jdbcType="VARCHAR" />
<result column="birthday" property="birthday" jdbcType="DATE" />
</association>
<!-- 一對多的關係 -->
<!-- property: 指的是集合屬性的值, ofType:指的是集合中元素的類型 -->
<collection property="likesKeys" ofType="com.liaojiexin.videoweb.entity.LikesKey">
<id column="uid" property="uid" jdbcType="INTEGER" />
<id column="vid" property="vid" jdbcType="INTEGER" />
</collection>
<!-- property: 指的是集合屬性的值, ofType:指的是集合中元素的類型 -->
<collection property="comments" ofType="com.liaojiexin.videoweb.entity.Comments">
<id column="commenttime" property="commenttime" jdbcType="TIMESTAMP" />
<id column="uid" property="uid" jdbcType="INTEGER" />
<id column="vid" property="vid" jdbcType="INTEGER" />
<result column="comment" property="comment" jdbcType="VARCHAR" />
</collection>
</resultMap>
<sql id="Base_Column_List" >
vid, vname, vtag, date, uid, introduce, url, imageurl, state, special, countcomments,
countlikes, countlooks
</sql>
<!-- 首頁查詢專題,視頻詳細信息和用戶名,評論數,喜歡數,觀看數 -->
<select id="specialSelect" resultMap="BaseResultMap">
select video.*,username
from video,user
where special=#{special,jdbcType=VARCHAR} and video.uid=user.uid and video.state=1
order by video.date DESC
</select>
</mapper>
import java.util.List;
@Repository
@Mapper
public interface VideoMapper {
//首頁查詢專題,視頻詳細信息和用戶名,評論數,喜歡數,觀看數
List specialSelect(@Param("special") String special);
}
服務層ArchiveShowServiceImpl實現類和ArchiveShowService接口
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.liaojiexin.videoweb.mapper.VideoMapper;
import com.liaojiexin.videoweb.service.ArchiveShowService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ArchiveShowServiceImpl implements ArchiveShowService { //處理視頻瀏覽頁的服務
@Autowired //注入DAO
private VideoMapper videoMapper;
@Override //該標籤會檢查檢查重寫方法的正確性
public PageInfo specialSelect(String special,int pageNum, int pageSize){ //首頁專題頁顯示,開啓分頁插件,放在查詢語句上面 幫助生成分頁語句
PageHelper.startPage(pageNum, pageSize);//底層實現原理採用改寫語句 將下面的方法中的sql語句獲取到然後做個拼接 limit
try {
List specialselect=videoMapper.specialSelect(special); //全部的視頻信息
// 封裝分頁之後的數據 返回給客戶端展示 PageInfo做了一些封裝 作爲一個類
PageInfo pageInfoSpecialSelect = new PageInfo(specialselect);
//所有分頁屬性都可以從pageInfoDemo拿到
return pageInfoSpecialSelect;
}finally {
PageHelper.clearPage(); //清理 ThreadLocal 存儲的分頁參數,保證線程安全
}
}
}
import com.github.pagehelper.PageInfo;
public interface ArchiveShowService { //視頻瀏覽頁的服務
PageInfo specialSelect(String special,int pageNum, int pageSize); //首頁專題頁顯示
}
控制層SpecialController
這裏把分頁欄的數字變動處理也交由控制層處理。本來想寫前端js來實現的,但是發現前端點擊按鈕後跳轉請求頁面刷新所以沒法實現(所以說還是有ajax來實現比較好,這裏如果用bootstrap自帶的效果也能展現出來)
import com.github.pagehelper.PageInfo;
import com.liaojiexin.videoweb.service.ArchiveShowService;
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.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.ArrayList;
import java.util.List;
@Controller
public class SpecialController { //專題控制層
@Autowired
private ArchiveShowService archiveShowService;
@GetMapping(value = "/specialSelect/{special}")
private String specialShow(@PathVariable("special") String special,
@RequestParam(value = "pageNum", defaultValue = "1") int pageNum, //在參數裏接受當前是第幾頁 pageNum,以及每頁顯示多少條數據 pageSize.默認值分別是1和10
@RequestParam(value = "pageSize", defaultValue = "10") int pageSize,
Model model)
{
PageInfo specialSelect=archiveShowService.specialSelect(special,pageNum,pageSize);
model.addAttribute("pages",specialSelect);
// 處理前端分頁欄數字變動問題
List pagenums = new ArrayList();
if(pageNum>3&&specialSelect.getPages()>5) //當前端頁數超過第三頁時,並且查詢到的總頁數大於5
{
if(pageNum>=specialSelect.getPages()-2){ //specialSelect.getPages()總頁數,如果前端頁數大於等於總頁數-2時
pagenums.add(specialSelect.getPages()-4);
pagenums.add(specialSelect.getPages()-3);
pagenums.add(specialSelect.getPages()-2);
pagenums.add(specialSelect.getPages()-1);
pagenums.add(specialSelect.getPages());
}
else{
pagenums.add(pageNum-2);
pagenums.add(pageNum-1);
pagenums.add(pageNum);
pagenums.add(pageNum+1);
pagenums.add(pageNum+2);
}
}else{ //前端頁數沒超過第三頁時
if(specialSelect.getPages()<5) //如果總頁數小於5
{
for(int i=0;i<specialSelect.getPages();i++)
pagenums.add(i+1);
}else{
pagenums.add("1");
pagenums.add("2");
pagenums.add("3");
pagenums.add("4");
pagenums.add("5");
}
}
model.addAttribute("pagenums",pagenums);
//跳到到personal.html頁面,thymeleaf默認會拼串,classpath:/templates/xxx.html
return "archive"; //這裏不可以寫redirect:/archive,前端頁面獲取不到model,https://www.wandouip.com/t5i350864/
}
}
前端
這裏前端模板用thymeleaf,還有bootstrap來實現樣式。
commons.html
這裏寫專題的那部分,其他的省略,點擊a標籤連接後發出“/specialSelect/第四屆NEW ERA青年電影季”的請求到控制層。
<li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="fa fa-play-circle-o"></i> 專題</a>
<div class="dropdown-menu">
<div class="dropdown-inner">
<ul class="list-unstyled">
<li><a th:href="@{/specialSelect/第四屆NEW ERA青年電影季}">第四屆NEW ERA青年電影季</a></li>
<li><a th:href="@{/specialSelect/第91屆奧斯卡入圍動畫短片大賞}">第91屆奧斯卡入圍動畫短片大賞</a></li>
<li><a th:href="@{/specialSelect/2019戛納廣告節必看廣告合集}">2019戛納廣告節必看廣告合集</a></li>
<li><a th:href="@{/specialSelect/歷屆奧斯卡優秀短片合集}">歷屆奧斯卡優秀短片合集</a></li>
<li><a th:href="@{/specialSelect/2019年V視頻年度精選短片回顧}">2019年V視頻年度精選短片回顧</a></li>
</ul>
</div>
</div>
</li>
archive.html
控制層處理好數據後跳轉到archive頁面,然後用thymeleaf處理數據。
這裏主要看兩個模塊,我在下面代碼中也用分割線分割出來。
這裏主要說thymeleaf中th:each的使用細節,我一開始看很多教程都沒有細說,所以我儘管後端寫好了,前端一直調用失敗。
很多教程都是寫th:each="page:${pages}"
,然後循環體內的標籤屬性寫th:text="${page.xxx}"
或者在標籤內容寫[[${page.xxx}]]
,這種寫法都不太正確,具體應該看你們代碼是怎麼寫的,比較正確的方法如下:
用IDEA編譯器debug一下你的控制層,如下圖所示,你會發現正確的寫法應該是th:each="page:${pages.list}"
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="">
<meta name="author" content="">
<title>瀏覽頁</title>
<!-- Bootstrap Core CSS -->
<link rel="stylesheet" href="/css/bootstrap.min.css" type="text/css">
<!-- Custom CSS -->
<link rel="stylesheet" href="/css/style.css">
<!-- Owl Carousel Assets -->
<link href="/owl-carousel/owl.carousel.css" rel="stylesheet">
<link href="/owl-carousel/owl.theme.css" rel="stylesheet">
<!-- Custom Fonts -->
<link rel="stylesheet" href="/font-awesome-4.4.0/css/font-awesome.min.css" type="text/css">
<!-- jQuery and Modernizr-->
<script src="/js/jquery-2.1.1.js"></script>
<!-- Core JavaScript Files -->
<script src="/js/bootstrap.min.js"></script>
<script src="/js/archive.js"></script>
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="/js/html5shiv.js"></script>
<script src="/js/respond.min.js"></script>
<![endif]-->
</head>
<body>
<header th:replace="commons :: commonHeader"></header>
<!-- /////////////////////////////////////////Content -->
<div id="page-content" class="archive-page">
<div class="container">
<div class="row">
<!---------------------------------分頁視頻詳細模塊---------------------------------------->
<div id="main-content" class="col-md-6" style="border-bottom:0px;border-top: 0px;padding-bottom: 0px" th:each="page:${pages.list}">
<article style="height: 257px">
<a href="#"><h4 class="vid-name" style="overflow: hidden;text-overflow:ellipsis;white-space: nowrap;">[[${page.vname}]]</h4></a>
<div class="info">
<h5>上傳人:<a>[[${page.user.username}]]</a></h5>
<!-- ↓↓↓ 格式化時間日期 -->
<span><i class="fa fa-calendar"></i>[[${#dates.format(page.date,"yyyy-MM-dd HH:mm:ss")}]]</span>
<span><i class="fa fa-comment"></i> [[${page.countcomments}]] 評論</span>
<span><i class="fa fa-heart"></i>[[${page.countlikes}]]</span>
<span><i class="fa fa-eye"></i>[[${page.countlooks}]]</span>
<ul class="list-inline"></ul>
</div>
<div class="wrap-vid">
<div class="zoom-container">
<div class="zoom-caption">
<span>[[${page.vtag}]]</span>
<a href="single.html">
<i class="fa fa-play-circle-o fa-5x" style="color: #fff"></i>
</a>
<p style="font-size: 17px;overflow: hidden;text-overflow:ellipsis;white-space: nowrap;" >[[${page.vname}]]</p>
</div>
<img src="/images/9.jpg" />
</div>
<!-- p標籤超出省略 http://www.bubuko.com/infodetail-2738912.html -->
<p style="font-size:18px;display: -webkit-box; word-break:break-all;-webkit-box-orient: vertical;-webkit-line-clamp: 6;text-overflow:ellipsis;overflow: hidden;text-indent:2em" th:text="${page.introduce}"></p>
</div>
</article>
<br><div class="line" style="margin: 0"></div>
</div>
<!------------------------------------------------------------------------------------->
</div>
<div class="row">
<div id="main-content2" class="col-md-12">
<!---------------------------------分頁欄模塊-------------------------------------->
<span>相關記錄視頻[[${pages.total}]]條,共[[${pages.pages}]]頁。</span>
<center>
<ul class="pagination">
<li><a id="firstpage" th:href="${#httpServletRequest.getRequestURL()} +'?pageNum=1'">首頁</a></li>
</ul>
<ul class="pagination">
<li>
<a th:if="${pages.pageNum} > '1'" id="previouspage" th:href="${#httpServletRequest.getRequestURL()}+'?pageNum='+${pages.prePage}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
</ul>
<!-- 當前頁數 ,方便js調用 display:none;隱藏 -->
<span style="display:none;" id="pagenums">[[${pages.pageNum}]]</span>
<ul class="pagination" th:each="pagenum:${pagenums}">
<!-- ${#httpServletRequest.getRequestURL()}表示當前url,href中也可以不寫,如下直接加參數 -->
<li><a th:class="'pagenum'+${pagenum}" th:href="'?pageNum='+${pagenum}">[[${pagenum}]]</a></li>
</ul>
<ul class="pagination">
<li>
<a th:if="${pages.pageNum} < ${pages.pages}" id="nextpage" th:href="${#httpServletRequest.getRequestURL()}+'?pageNum='+${pages.nextPage}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
<ul class="pagination">
<li><a id="endpage" th:href="${#httpServletRequest.getRequestURL()}+'?pageNum='+${pages.pages}">末頁</a></li>
</ul>
</center>
<!------------------------------------------------------------------------------------->
</div>
</div>
</div>
</div>
<footer th:replace="commons :: commonFooter"></footer>
</body>
</html>
archive.js
這裏主要是讓分頁欄顯示對應頁數按鈕的樣式
$(document).ready(function () {
var pagenum='.pagenum'+$("#pagenums").text();
$(pagenum).css({"background-color":"red","color":"white"});
});
展示js的效果差別
結語
以上就是實現分頁功能的教程,裏面還有很多細節性問題和知識可以值得你們去思考和學習(像虛擬機時區問題、查詢時間格式化問題、p標籤內容字數超出規定的長度變省略號…等一系列問題),具體可以看我代碼的註釋。如果有什麼問題歡迎留言互相談論。
參考:
- SpringBoot+PageHelper+Bootstrap+Thymeleaf 實現分頁功能
- SpringBoot2.0集成分頁插件pagehelper-spring-boot-starter
- 結合後臺pageInfo實現分頁功能(這個是用ajax來處理的,前端js展示代碼,如果用ajax的話可以參考)
- SpringBoot + thymeleaf 實現分頁
業務問題擴展:我的項目裏面有一個搜索功能,一開始我想要用同一個前端頁面(即archive.html)來展示搜索結果,但是突然卡住了,因爲我的搜索功能是一個form表單,發送請求到後端是不需要在路徑中賦值的。如下代碼
<form th:action="@{/checkSelect}" class="form-inline">
<input class="form-control required" type="" name="vname" id="search-form" placeholder="請輸入要搜索的視頻名稱" required/>
<button class="btn" type="submit" style="color: black"><i class="fa fa-search"></i></button>
</form>
@Controller
public class CheckController { //搜索控制層
@Autowired
private ArchiveShowService archiveShowService;
@GetMapping(value = "/checkSelect/{vname}")
private String checkShow(@PathVariable("vname") String vname,
@RequestParam(value = "pageNum", defaultValue = "1") int pageNum, //在參數裏接受當前是第幾頁 pageNum,以及每頁顯示多少條數據 pageSize.默認值分別是1和10
@RequestParam(value = "pageSize", defaultValue = "10") int pageSize,
Model model)
{
省略代碼........
}
}
一開始我老是想在怎麼把input的值賦值到url路徑中,即:/checkSelect/xxx一樣,但是form直接把input的值當成參數附在後面,即:/checkSelect?vame=xxx。這樣的話,archive.html頁面的分頁欄就會報錯,顯示缺少vame參數。
想了各種方法,像js來賦值或者thymeleaf等等,弄了一晚上結果都不行,後面自己老老實實寫了個新的前端頁面來展示搜索效果,在修改這個這個新的頁面分頁欄路徑時突然得到靈感。
我把控制層代碼改成如下,把請求和參數vname改了,同時把vame賦值到model中,然後前端把分頁欄的代碼的路徑改成如下來獲取vame參數。
@Controller
public class CheckController { //搜索控制層
@Autowired
private ArchiveShowService archiveShowService;
@GetMapping(value = "/checkSelect")
private String checkShow(@RequestParam("vname") String vname,
@RequestParam(value = "pageNum", defaultValue = "1") int pageNum, //在參數裏接受當前是第幾頁 pageNum,以及每頁顯示多少條數據 pageSize.默認值分別是1和10
@RequestParam(value = "pageSize", defaultValue = "10") int pageSize,
Model model)
{
model.addAttribute("vname",vname); //把vname傳給分頁欄
代碼省略.........
}
}
<div id="main-content2" class="col-md-12">
<center>
<ul class="pagination">
<li><a id="firstpage" th:href="${#httpServletRequest.getRequestURL()} +'?pageNum=1&vname='+${vname} ">首頁</a></li>
</ul>
<ul class="pagination">
<li>
<a th:if="${pages.pageNum} > '1'" id="previouspage" th:href="${#httpServletRequest.getRequestURL()}+'?pageNum='+${pages.prePage}+'&vname='+${vname} " aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
</ul>
<!-- 當前頁數 ,方便js調用 display:none;隱藏 -->
<span style="display:none;" id="pagenums">[[${pages.pageNum}]]</span>
<ul class="pagination" th:each="pagenum:${pagenums}">
<!-- ${#httpServletRequest.getRequestURL()}表示當前url,href中也可以不寫,如下直接加參數 -->
<li><a th:class="'pagenum'+${pagenum}" th:href="'?pageNum='+${pagenum}+'&vname='+${vname}">[[${pagenum}]]</a></li>
</ul>
<ul class="pagination">
<li>
<a th:if="${pages.pageNum} < ${pages.pages}" id="nextpage" th:href="${#httpServletRequest.getRequestURL()}+'?pageNum='+${pages.nextPage}+'&vname='+${vname}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
<ul class="pagination">
<li><a id="endpage" th:href="${#httpServletRequest.getRequestURL()}+'?pageNum='+${pages.pages}+'&vname='+${vname}">末頁</a></li>
</ul>
</center>
</div>
這樣的話,“專題”和“分類”沒有vname參數的話,就會顯示null,不會影響功能實現。
而搜索功能也能完美實現