基于my Batis优化图书管理系统(二)
4. 图书列表
添加图书之后, 跳转到图书列表⻚⾯, 并没有显⽰刚才添加的图书信息, 接下来我们来实现图 书列表
4.1 需求分析
当查询到我们的图书数据很多的时候,一个页可能存放不了,所以我们进行分页处理数据,并且分页进行查询;如果想看非当前页的数据,我们可以进行点击其他页码进行查询;
分⻚时, 数据是如何展⽰的呢:
第1⻚: 显⽰1-10 条的数据
第2⻚: 显⽰11-20 条的数据
以此类推... 要想实现这个功能, 从数据库中进⾏分⻚查询,我们要使⽤ LIMIT 关键字,
格式为:limit 开始索引 每⻚显⽰的条数(开始索引从0开始
查询第1⻚的SQL语句
SELECT * FROM book_info LIMIT 0,10;
查询第2⻚的SQL语句
SELECT * FROM book_info LIMIT 10,10;
观察以上SQL语句,发现: 开始索引⼀直在改变, 每⻚显⽰条数是固定的 开始索引的计算公式: 开始索引 = (当前⻚码 - 1) * 每⻚显⽰条数
我们继续基于前端⻚⾯, 继续分析, 得出以下结论:
1. 前端在发起查询请求时,需要向服务端传递的参数 :
currentPage 当前⻚码 //默认值为1 ,
pageSize 每⻚显⽰条数 //默认值为10
2、后端响应时, 需要响应给前端的数据 ◦:
records 所查询到的数据列表(存储到List 集合中)
total 总记录数 (⽤于告诉前端显⽰多少⻚, 显⽰⻚数为: (total + pageSize -1)/pageSize 显⽰⻚数;totalPage 计算公式为 : total % pagesize == 0 ? total / pagesize : (total / pagesize)+1 ;
翻⻚请求和响应部分, 我们通常封装在两个对象中 ;
1、翻⻚请求对象
@Data
public class PageRequest {
//当前页
private Integer currentPage =1;
//每页显示个数
private Integer pageSize =10;
/**
* 从多少条记录开始查询
*/
private Integer offset;
public Integer getOffset() {
return (currentPage-1) * pageSize;
}
}
2、翻⻚列表结果类:
@AllArgsConstructor//全参构造
@NoArgsConstructor//无参构造
@Data
public class PageResult<T> {
private List<T> records;
//当前的t是bookinfo类型
private Integer count;//所有的记录数
private PageRequest pageRequest;//当前页的记录数据
}
4.2 约定前后端交互接⼝
[请求]
/book/getListByPage?currentPage=1&pageSize=10
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
[参数]
[响应]
Content-Type: application/json
{
"total": 25,
"records": [{
"id": 25,
"bookName": "图书21",
"author": "作者2",
"count": 29,
"price": 22.00,
"publish": "出版社1",
"status": 1,
"statusCN": "可借阅"
}, {
......
} ]
}
4.3 实现服务器代码
控制层: 完善 BookController
@RequestMapping("/getListByPage")
public PageResult<BookInfo> getListByPage(PageRequest pageRequest) {
log.info("获取图书列表, pageRequest:{}", pageRequest);
PageResult<BookInfo> pageResult =
bookService.getListByPage(pageRequest);
return pageResult;
}
业务层: BookService
public PageResult<BookInfo> getBookListByPage(PageRequest pageRequest) {
Integer count = bookInfoMapper.count();
List<BookInfo> books = bookInfoMapper.queryBookListByPage(pageRequest);
for (BookInfo book:books){
if (book.getStatus()==0){
book.setStatusCN("⽆效");
}else if (book.getStatus()==1){
book.setStatusCN("可借阅");
}else {
book.setStatusCN("不可借阅");
}
}
return new PageResult<>(count,books);
}
存在的问题:
1. 翻⻚信息需要返回数据的总数和列表信息, 需要查两次SQL
2. 图书状态: 图书状态和数据库存储的status有⼀定的对应关系 如果后续状态码有变动, 我们需要修改项⽬中所有涉及的代码, 这种情况, 通常采⽤枚举类来处理映射关系
创建enmus⽬录, 创建BookStatus类:
package com.example.book_manage_240827.enums;
public enum BookStatusEnums {
DELETE(0,"无效"),
NORMAL(1,"可借阅"),
FORBIDDEN(2,"不可借阅"),
;
private int code;
private String desc;
BookStatusEnums(int code, String desc) {
this.code = code;
this.desc = desc;
}
//根据code, 获取描述
public static BookStatusEnums getDescByCode(int code){
switch (code){
case 0: return BookStatusEnums.DELETE;
case 1: return BookStatusEnums.NORMAL;
case 2: return BookStatusEnums.FORBIDDEN;
}
return BookStatusEnums.DELETE;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
getNameByCode: 通过code来获取对应的枚举, 以获取该枚举对应的中⽂名称 后续如果有状态变更, 只需要修改该枚举类即可
BookService的代码, 可以修改如下:
public PageResult<BookInfo> getListByPage(PageRequest pageRequest) {
//1. 查询记录的总数
Integer count = bookInfoMapper.count();
//2. 查询当前页的数据
List<BookInfo> bookInfos = bookInfoMapper.queryListBypage(pageRequest);
for (BookInfo bookInfo:bookInfos){
//根据状态, 设置描述
bookInfo.setStateCN(BookStatusEnums.getDescByCode(bookInfo.getStatus()).getDesc());
}
return new PageResult<>(bookInfos,count,pageRequest);
}
BookInfoMapper :
图书列表按id降序排列:
/**
* 查询总数
*/
@Select("select count(1) from book_info where `status` <>0")
Integer count();
/**
* 查询当前页的数据
*/
@Select("select * from book_info where `status` <>0 order by id desc limit #{offset}, #{pageSize}")
List<BookInfo> queryListBypage(PageRequest pageRequest);
访问http://127.0.0.1:8082/book/getListByPage ,查询第一页的数据;
访问第二页的数据,http://127.0.0.1:8082/book/getListByPage?currentPage=2 ,
4.4 实现客户端代码
浏览器访问 book_list.html ⻚⾯时, 就去请求后端, 把后端返回数据显⽰在⻚⾯上 调⽤后端请求: /book/getListByPage?currentPage=1
getBookList();
getBookList();
function getBookList() {
$.ajax({
type: "get",
url: "/book/getListByPage",
success: function (result) {
if (result != null) {
var finalHtml = "";
for (var book of result) {
finalHtml += '<tr>';
finalHtml += '<td><input type="checkbox" name="selectBook" ' +
'value="'+book.id+'" id="selectBook" class="book-select"></td>';
finalHtml += '<td>'+book.id+'</td>';
finalHtml += '<td>'+book.bookName+'</td>';
finalHtml += '<td>'+book.author+'</td>';
finalHtml += '<td>'+book.count+'</td>';
finalHtml += '<td>'+book.price+'</td>';
finalHtml += '<td>'+book.publish+'</td>';
finalHtml += '<td>'+book.statusCN+'</td>';
finalHtml += '<td><div class="op">';
finalHtml += '<a href="book_update.html?bookId='+book.id+'">修改</a>';
finalHtml += '<a href="javascript:void(0)"onclick="deleteBook('+book.id+')">删除</a>';
finalHtml += '</div></td>';
finalHtml += "</tr>";
}
$("tbody").html(finalHtml);
}
}
});
}
此时, url还未设置 currentPage 参数 ,我们直接使⽤ location.search 从url中获取参数信息即可 ;
location.search : 获取url的查询字符串 (包含问号) 如: url: http://127.0.0.1:8082/book_list.html?currentPage=1
location.search : ?currentPage=1
$.ajax({
type: "get",
url: "/book/getListByPage"+ location.search,
接下来处理分页信息
分页插件 --->本案例中, 分⻚代码采⽤了⼀个分⻚组件 ;
分⻚组件⽂档介绍: jqPaginator分⻚组件 ,使⽤时, 只需要按照 [使⽤说明]部分的⽂档, 把代码复制粘贴进来就可以了(提供的前端代码中, 已经包含该部分内容)
onPageChange :回调函数,当换⻚时触发(包括初始化第⼀⻚的时候),会传⼊两个参数: 1、"⽬标⻚"的⻚码,Number类型
2、触发类型,可能的值:"init"(初始化),"change"(点击分⻚)
我们在图书列表信息加载之后, 需要分⻚信息, 同步加载:
分⻚组件需要提供⼀些信息:
totalCounts: 总记录数,
pageSize: 每⻚的个数,
visiblePages: 可视⻚数
currentPage: 当前⻚码
这些信息中, pageSize 和 visiblePages 前端直接设置即可. totalCounts 后端已经提供, currentPage 也可以从参数中取到, 但太复杂了, 咱们直接由后端返回即可.
修改后端代码
1. 为避免后续还需要其他请求处的信息, 我们直接在PageResult 添加 PageRequest 属性
2. 处理返回结果, 填充 PageRequest
PageResult:
package com.example.book_manage_240827.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@AllArgsConstructor//全参构造
@NoArgsConstructor//无参构造
@Data
public class PageResult<T> {
private List<T> records;
//当前的t是bookinfo类型
private Integer count;//所有的记录数
private PageRequest pageRequest;//当前页的记录数据
public PageResult(Integer count, PageRequest pageRequest, List<T> records)
{
this.count = count;
this.pageRequest = pageRequest;
this.records = records;
}
}
BookService:
public PageResult<BookInfo> getListByPage(PageRequest pageRequest) {
//1. 查询记录的总数
Integer count = bookInfoMapper.count();
//2. 查询当前页的数据
List<BookInfo> bookInfos = bookInfoMapper.queryListBypage(pageRequest);
for (BookInfo bookInfo:bookInfos){
//根据状态, 设置描述
bookInfo.setStateCN(BookStatusEnums.getDescByCode(bookInfo.getStatus()).getDesc());
}
return new PageResult<>(count,pageRequest,bookInfos);
}
后端数据返回后, 我们加载⻚⾯信息, 把分⻚代码挪到getBookList⽅法中;
getBookList();
getBookList();
function getBookList() {
$.ajax({
type: "get",
url: "/book/getListByPage"+ location.search,
success: function (result) {
if (result != null) {
var finalHtml = "";
for (var book of result) {
finalHtml += '<tr>';
finalHtml += '<td><input type="checkbox" name="selectBook" ' +
'value="'+book.id+'" id="selectBook" class="book-select"></td>';
finalHtml += '<td>'+book.id+'</td>';
finalHtml += '<td>'+book.bookName+'</td>';
finalHtml += '<td>'+book.author+'</td>';
finalHtml += '<td>'+book.count+'</td>';
finalHtml += '<td>'+book.price+'</td>';
finalHtml += '<td>'+book.publish+'</td>';
finalHtml += '<td>'+book.statusCN+'</td>';
finalHtml += '<td><div class="op">';
finalHtml += '<a href="book_update.html?bookId='+book.id+'">修改</a>';
finalHtml += '<a href="javascript:void(0)"onclick="deleteBook('+book.id+')">删除</a>';
finalHtml += '</div></td>';
finalHtml += "</tr>";
}
$("tbody").html(finalHtml);
//翻页信息
$("#pageContainer").jqPaginator({
totalCounts: 100, //总记录数
pageSize: 10, //每页的个数
visiblePages: 5, //可视页数
currentPage: 1, //当前页码
first: '<li class="page-item"><a class="page-link">首页</a></li>',
prev: '<li class="page-item"><a class="page-link" href="javascript:void(0);">上一页<\/a><\/li>',
next: '<li class="page-item"><a class="page-link" href="javascript:void(0);">下一页<\/a><\/li>',
last: '<li class="page-item"><a class="page-link" href="javascript:void(0);">最后一页<\/a><\/li>',
page: '<li class="page-item"><a class="page-link" href="javascript:void(0);">{{page}}<\/a><\/li>',
//页面初始化和页码点击时都会执行
onPageChange: function (page, type) {
console.log("第"+page+"页, 类型:"+type);
}
});
}
}
});
}
完善⻚⾯点击代码: 当点击⻚码时: 跳转到⻚⾯: book_list.html?currentPage=? 修改上述代码代码:
//页面初始化和页码点击时都会执行
onPageChange: function (page, type) {
if (type != 'init') {
location.href = "book_list.html?currentPage=" + page;
}
}
4.5 测试
浏览器访问地址http://127.0.0.1:8082/book_list.html ,即是图书列表第一页,结果如下:
点击第二页,结果如下:
5.修改图书
5.1 约定前后端交互接⼝
1、进⼊修改⻚⾯, 需要显⽰当前图书的信息:
[请求]
/book/queryBookById?bookId=25
[参数]
⽆
[响应]
{
"id": 25,
"bookName": "图书21",
"author": "作者2",
"count": 999,
"price": 222.00,
"publish": "出版社1",
"status": 2,
"statusCN": null,
"createTime": "2023-09-04T04:01:27.000+00:00",
"updateTime": "2023-09-05T03:37:03.000+00:00"
}
即根据图书ID, 获取当前图书的信息;
2、点击修改按钮, 修改图书信息
[请求]
/book/updateBook
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
[参数]
id=1&bookName=图书1&author=作者1&count=23&price=36&publish=出版社1&status=1
[响应]
"" //失败信息, 成功时返回空字符串
我们约定, 浏览器给服务器发送⼀个 /book/updateBook 这样的 HTTP 请求, form表单的形式来 提交数据 ,服务器返回处理结果, 返回""表⽰修改图书成功,;否则, 返回失败信息;
5.2 实现服务器代码
BookController:
@RequestMapping("/queryBookById")
public BookInfo queryBookById(Integer bookId){
if (bookId==null || bookId<=0){
return new BookInfo();
}
BookInfo bookInfo = bookService.queryBookById(bookId);
return bookInfo;
}
@RequestMapping("/updateBook")
public String updateBook(BookInfo bookInfo) {
log.info("修改图书:{}", bookInfo);
try {
bookService.updateBook(bookInfo);
return "";
} catch (Exception e) {
log.error("修改图书失败", e);
return e.getMessage();
}
}
业务层: BookService:
public BookInfo queryBookById(Integer bookId) {
return bookInfoMapper.queryBookById(bookId);
}
public Integer updateBook(BookInfo bookInfo) {
return bookInfoMapper.updateBook(bookInfo);
}
数据层: 根据图书ID,查询图书信息
/**
* 根据ID查询图书信息
*/
@Select("select * from book_info where `status` <>0 and id= #{id}")
BookInfo queryBookById(Integer id);
/**
* 根据ID, 修改图书信息
*/
Integer updateBook(BookInfo bookInfo);
对于update接口,我们采⽤xml的⽅式来实现;
定义Mapper接⼝: BookInfoMapper
Integer updateBook(BookInfo bookInfo);
xml实现:
创建bookInfoMapper.xml文件:文件路径和代码如下如下:
<?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.example.book_manage_240827.mapper.BookInfoMapper">
<update id="updateBook">
update book_info
<set>
<if test="bookName!=null">
book_name = #{bookName},
</if>
<if test="author!=null">
author =#{author},
</if>
<if test="count!=null">
count = #{count},
</if>
<if test="price!=null">
price =#{price},
</if>
<if test="publish">
publish = #{publish},
</if>
<if test="status!=null">
status =#{status},
</if>
</set>
where id = #{id}
</update>
</mapper>
5.3 实现客户端代码
1、在列表⻚时, 我们已经补充了[修改] 的链接:
<div>
<button class="btn btn-outline-info" type="button" onclick="location.href='book_add.html'">添加图书</button>
</div>
2、点击[修改] 链接时, 就会⾃动跳转到 http://127.0.0.1:8082/book_update.html?bookId=2 (2为对应的图书ID)
进⼊[修改图书]⻚⾯时, 需要先从后端拿到当前图书的信息, 显⽰在⻚⾯上:
$.ajax({
type: "get",
url: "/book/queryBookById"+location.search,
success: function (result) {
//前端根据后端返回结果, 针对不同的情况进行处理
if (result != null) {
console.log(result)
$("#bookId").val(result.id);
$("#bookName").val(result.bookName);
$("#bookAuthor").val(result.author);
$("#bookStock").val(result.count);
$("#bookPrice").val(result.price);
$("#bookPublisher").val(result.publish);
$("#bookStatus").val(result.status);
}
}
});
补全修改图书的⽅法:
function update() {
$.ajax({
type:"post",
url: "/book/updateBook",
data: $("#updateBook").serialize(),
success:function(result){
if(result == "") {
location.href = "book_list.html"
}else{
console.log(result);
alert("更新失败"+result);
}
},
error: function (error) {
console.log(error);
}
});
}
我们修改图书信息, 是根据图书ID来修改的, 所以需要前端传递的参数中, 包含图书ID. 有两种⽅式:
1. 获取url中参数的值(⽐较复杂, 需要拆分url)
2. 在form表单中, 再增加⼀个隐藏输⼊框, 存储图书ID, 随 $("#updateBook").serialize() ⼀起提交到后端
下面我们采⽤第⼆种⽅式 在form表单中,添加隐藏输⼊框
<form id="updateBook"> <input type="hidden" class="form-control" id="bookId" name="id"> <div class="form-group"> <label for="bookName">图书名称:</label> <input type="text" class="form-control" id="bookName" name="bookName"> </div>
hidden 类型的 <input>元素 :隐藏表单, 用户不可⻅、不可改的数据,在用户提交表单时,这些数据会⼀并发送出
使⽤场景: 正被请求或编辑的内容的 ID. 这些隐藏的 input 元素在渲染完成的⻚⾯中完全不可⻅,⽽ 且没有⽅法可以使它重新变为可⻅.
⻚⾯加载时, 给该hidden框赋值
$("#bookId").val(result.id); $("#bookName").val(result.bookName); $("#bookAuthor").val(result.author); $("#bookStock").val(result.count); $("#bookPrice").val(result.price); $("#bookPublisher").val(result.publish); $("#bookStatus").val(result.status);
5.4 测试
点击修改,
出现修改页面:
在修改正确的数据之后点击确定,返回到图书列表页;
查看修改前与修改后的数据变化:
ps:本项目未完待续,如果对你有所帮助的话,就请一键三连哦!!!