目录
[1. MyBatis与MySQL配置](#1. MyBatis与MySQL配置)
[1.1 创建数据库及数据表](#1.1 创建数据库及数据表)
[1.2 配置MyBatis与数据库](#1.2 配置MyBatis与数据库)
[1.2.1 增加MyBatis与MySQL相关依赖](#1.2.1 增加MyBatis与MySQL相关依赖)
[1.2.2 配置application.yml文件](#1.2.2 配置application.yml文件)
[1.3 增加数据表对应实体类](#1.3 增加数据表对应实体类)
[2. 功能1:用户登录](#2. 功能1:用户登录)
[2.1 约定前后端交互接口](#2.1 约定前后端交互接口)
[2.2 后端接口](#2.2 后端接口)
[2.3 前端页面](#2.3 前端页面)
[2.4 单元测试](#2.4 单元测试)
[3. 功能2:图书列表](#3. 功能2:图书列表)
[3.1 约定前后端交互接口](#3.1 约定前后端交互接口)
[3.2 后端接口](#3.2 后端接口)
[3.3 前端页面](#3.3 前端页面)
[3.4 单元测试](#3.4 单元测试)
1. MyBatis与MySQL配置
1.1 创建数据库及数据表
创建数据库book_test,建议选择编码为utf8mb4;
创建用户表user_info和图书表book_info;
sql
-- 用户表
DROP TABLE IF EXISTS user_info;
CREATE TABLE user_info (
`id` INT NOT NULL AUTO_INCREMENT,
`user_name` VARCHAR ( 128 ) NOT NULL,
`password` VARCHAR ( 128 ) NOT NULL,
`delete_flag` TINYINT ( 4 ) NULL DEFAULT 0,
`create_time` DATETIME DEFAULT now(),
`update_time` DATETIME DEFAULT now() ON UPDATE now(),
PRIMARY KEY ( `id` ),
UNIQUE INDEX `user_name_UNIQUE` ( `user_name` ASC )) ENGINE = INNODB DEFAULT
CHARACTER
SET = utf8mb4 COMMENT = '用户表';
-- 图书表
DROP TABLE IF EXISTS book_info;
CREATE TABLE `book_info` (
`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
`book_name` VARCHAR ( 127 ) NOT NULL,
`author` VARCHAR ( 127 ) NOT NULL,
`count` INT ( 11 ) NOT NULL,
`price` DECIMAL (7,2 ) NOT NULL,
`publish` VARCHAR ( 256 ) NOT NULL,
`status` TINYINT ( 4 ) DEFAULT 1 COMMENT '0-无效, 1-正常, 2-不允许借阅',
`create_time` DATETIME DEFAULT now(),
`update_time` DATETIME DEFAULT now() ON UPDATE now(),
PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;
-- 初始化数据
INSERT INTO user_info ( user_name, PASSWORD ) VALUES ( "admin", "admin" );
INSERT INTO user_info ( user_name, PASSWORD ) VALUES ( "zhangsan", "123456" );
-- 初始化图书数据
INSERT INTO `book_info` (book_name,author,count, price, publish) VALUES ('活着',
'余华', 29, 22.00, '北京文艺出版社');
INSERT INTO `book_info` (book_name,author,count, price, publish) VALUES ('平凡的世界',
'路遥', 5, 98.56, '北京十月文艺艺出版社');
INSERT INTO `book_info` (book_name,author,count, price, publish) VALUES ('三体',
'刘慈欣', 9, 102.67, '重庆出版社');
INSERT INTO `book_info` (book_name,author,count, price, publish) VALUES ('金字塔原理',
'麦肯锡', 16, 178.00, '民主与建设出版社');
INSERT INTO `book_info` (book_name,author,count, price, publish) VALUES ('图书5',
'作者5', 20, 45, '出版社5');
INSERT INTO `book_info` (book_name,author,count, price, publish) VALUES ('图书6',
'作者6', 20, 45, '出版社6');
INSERT INTO `book_info` (book_name,author,count, price, publish) VALUES ('图书7',
'作者7', 20, 45, '出版社7');
INSERT INTO `book_info` (book_name,author,count, price, publish) VALUES ('图书8',
'作者8', 20, 45, '出版社8');
INSERT INTO `book_info` (book_name,author,count, price, publish) VALUES ('图书9',
'作者9', 20, 45, '出版社9');
INSERT INTO `book_info` (book_name,author,count, price, publish) VALUES ('图书10',
'作者10', 20, 45, '出版社10');
INSERT INTO `book_info` (book_name,author,count, price, publish) VALUES ('图书11',
'作者11', 20, 45, '出版社11');
INSERT INTO `book_info` (book_name,author,count, price, publish) VALUES ('图书12',
'作者12', 20, 45, '出版社12');
INSERT INTO `book_info` (book_name,author,count, price, publish) VALUES ('图书13',
'作者13', 20, 45, '出版社13');
1.2 配置MyBatis与数据库
1.2.1 增加MyBatis与MySQL相关依赖

1.2.2 配置application.yml文件
html
# 端口配置
server:
port: 8080
# 数据库连接配置
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/book_test?characterEncoding=utf8&useSSL=false
username: root
password: xxxxxx
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #配置打印MyBatis日志
map-underscore-to-camel-case: true #配置转换驼峰
# 设置日志文件的文件名
logging:
file:
name: /logger/spring-book.log
1.3 增加数据表对应实体类
创建model包并在其下创建BookInfo和UserInfo类:
java
package com.example.bookmanagementsystem.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class BookInfo {
private Integer id;
private String bookName;
private String author;
private Integer count;
// 前端展示精度
@JsonFormat(shape = JsonFormat.Shape.STRING)
private BigDecimal price;
private String publish;
private Integer status; //1-可借阅,2-不可借阅
private String statusCN; // status的中文展示
}
java
package com.example.bookmanagementsystem.model;
import lombok.Data;
import java.util.Date;
@Data
public class UserInfo {
private Integer id;
private String userName;
private String password;
private Integer deleteFlag;
private Date createTime;
private Date updateTime;
}
2. 功能1:用户登录
2.1 约定前后端交互接口
请求:/User/login
参数:userName=admin&&password=admin
响应:成功返回true,失败返回false
2.2 后端接口
UserController类:
java
package com.example.bookmanagementsystem.controller;
import com.example.bookmanagementsystem.model.UserInfo;
import com.example.bookmanagementsystem.service.UserService;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/User")
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/login")
public Boolean login(String userName, String password, HttpSession session){
// 校验参数合法性
if(!StringUtils.hasLength(userName)||!StringUtils.hasLength(password)){
return false;
}
// 1、根据用户名查找用户信息
UserInfo userInfo =userService.getUserInfoByName(userName);
// 2、校验密码正确性
if(userInfo == null || userInfo.getId()<=0){
return false;
}
if(password.equals(userInfo.getPassword())){
// 存Session
userInfo.setPassword("");
session.setAttribute("userName",userInfo);
return true;
}
return false;
}
}
UserService类:
java
package com.example.bookmanagementsystem.service;
import com.example.bookmanagementsystem.mapper.UserInfoMapper;
import com.example.bookmanagementsystem.model.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
public UserInfo getUserInfoByName(String name){
return userInfoMapper.selectUserByName(name);
}
}
UserMapper接口:
java
package com.example.bookmanagementsystem.mapper;
import com.example.bookmanagementsystem.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface UserInfoMapper {
// 根据用户名查询用户信息
@Select("select* from user_info where user_name=#{name}")
UserInfo selectUserByName(String name);
}
2.3 前端页面
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/login.css">
<script type="text/javascript" src="js/jquery.min.js"></script>
</head>
<body>
<div class="container-login">
<div class="container-pic">
<img src="pic/computer.png" width="350px">
</div>
<div class="login-dialog">
<h3>登录</h3>
<div class="row">
<span>用户名</span>
<input type="text" name="userName" id="userName" class="form-control">
</div>
<div class="row">
<span>密码</span>
<input type="password" name="password" id="password" class="form-control">
</div>
<div class="row">
<button type="button" class="btn btn-info btn-lg" onclick="login()">登录</button>
</div>
</div>
</div>
<script src="js/jquery.min.js"></script>
<script>
function login() {
$.ajax({
url:"/User/login",
type:"post",
data:{
"userName":$("#userName").val(),
"password":$("#password").val()
},
success:function(result){
if(result){
location.href = "book_list.html";
}else{
alert("用户名或密码错误");
}
}
});
}
</script>
</body>
</html>
2.4 单元测试
1、后端接口测试:

可在服务器日志处查看相关信息:

2、前端页面测试:
输入账号admin和密码admin后,即可成功登录;
3. 功能2:图书列表
3.1 约定前后端交互接口
1、前端发送给后端:(封装为对象:PageRequest)
(1)当前页currentPage;
(2)每页显示的条数pageSize;
2、后端发送给前端:(封装为对象:PageResult)
(1)当前页的内容records(类型为List<BookInfo>);
(2)总条数total;
关于SQL语句的分页查询关键字:limit与offset,常用方式如下:
1、select* from table_name limit num2,num1;
2、select* from table_name limit num1 offset num2;
以上两句都表示从num2+1行开始返回num1行数据;
3.2 后端接口
BookController类:
java
package com.example.bookmanagementsystem.controller;
import com.example.bookmanagementsystem.model.BookInfo;
import com.example.bookmanagementsystem.model.PageRequest;
import com.example.bookmanagementsystem.model.PageResult;
import com.example.bookmanagementsystem.service.BookService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@Slf4j
@RequestMapping("/Book")
@RestController
public class BookController {
@Autowired
private BookService bookService;
@RequestMapping("/getBookListByPage")
public PageResult<BookInfo> getBookListByPage(PageRequest pageRequest){
log.info("接收到查询翻页信息:pageRequest:{}",pageRequest);
if(pageRequest.getPageSize()<0 || pageRequest.getCurrentPage()<1) {
return null;
}
PageResult<BookInfo> bookInfoPageResult=null;
try{
bookInfoPageResult = bookService.selectBookInfoByPage(pageRequest);
}catch (Exception e){
log.error("查询翻页信息错误:e{}",e);
}
return bookInfoPageResult;
}
}
BookService类:
java
package com.example.bookmanagementsystem.service;
import com.example.bookmanagementsystem.enums.BookStatusEnum;
import com.example.bookmanagementsystem.mapper.BookInfoMapper;
import com.example.bookmanagementsystem.model.BookInfo;
import com.example.bookmanagementsystem.model.PageRequest;
import com.example.bookmanagementsystem.model.PageResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class BookService {
@Autowired
private BookInfoMapper bookInfoMapper;
public PageResult<BookInfo> selectBookInfoByPage(PageRequest pageRequest){
if(pageRequest == null){
return null;
}
// 获取总记录数
Integer count = bookInfoMapper.count();
// 获取当前页内容
List<BookInfo> bookInfos = bookInfoMapper.selectBookInfoByPage(
pageRequest.getOffset(), pageRequest.getPageSize());
if(bookInfos !=null && bookInfos.size()>0){
for(BookInfo bookInfo: bookInfos){
// 根据状status获取图书状态定义
bookInfo.setStatusCN(BookStatusEnum.getNameByCode(bookInfo.getStatus()).getName());
}
}
return new PageResult<>(bookInfos,count,pageRequest);
}
}
并为BookService创建BookServiceEnum枚举:
java
package com.example.bookmanagementsystem.enums;
public enum BookStatusEnum {
DELETE(0,"删除"),
NORMAL(1,"可借阅"),
FORBIDDEN(2,"不可借阅");
private int code;
private String name;
public static BookStatusEnum getNameByCode(int code){
switch(code){
case 0: return BookStatusEnum.DELETE;
case 1: return BookStatusEnum.NORMAL;
case 2: return BookStatusEnum.FORBIDDEN;
default:
return BookStatusEnum.FORBIDDEN;
}
}
BookStatusEnum(int code, String name) {
this.code = code;
this.name = name;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
BookInfoMapper接口:
java
package com.example.bookmanagementsystem.mapper;
import com.example.bookmanagementsystem.model.BookInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface BookInfoMapper {
// 获取当前页信息
@Select("select* from book_info where status!=0 " +
"order by id desc limit #{offset}, #{pageSize}")
List<BookInfo> selectBookInfoByPage(@Param("offset") Integer offset, @Param("pageSize") Integer pageSize);
// 获取总记录数
@Select("select count(1) from book_info where status !=0")
Integer count();
}
3.3 前端页面
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图书列表展示</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/list.css">
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript" src="js/bootstrap.min.js"></script>
<script src="js/jq-paginator.js"></script>
</head>
<body>
<div class="bookContainer">
<h2>图书列表展示</h2>
<div class="navbar-justify-between">
<div>
<button class="btn btn-outline-info" type="button" onclick="location.href='book_add.html'">添加图书</button>
<button class="btn btn-outline-info" type="button" onclick="batchDelete()">批量删除</button>
</div>
</div>
<table>
<thead>
<tr>
<td>选择</td>
<td class="width100">图书ID</td>
<td>书名</td>
<td>作者</td>
<td>数量</td>
<td>定价</td>
<td>出版社</td>
<td>状态</td>
<td class="width200">操作</td>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div class="demo">
<ul id="pageContainer" class="pagination justify-content-center"></ul>
</div>
<script>
getBookList();
function getBookList() {
$.ajax({
url:"/Book/getBookListByPage"+location.search,
type:"get",
success:function(result){
var finalHtml="";
// 加载列表
// 根据每一条记录拼接HTML,即一个<tr>
for(var book of result.records){
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>';
finalHtml+='<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>';
finalHtml+=' <td>';
finalHtml+=' <tr>';
}
$("tbody").html(finalHtml)
//翻页信息
$("#pageContainer").jqPaginator({
totalCounts: result.total, //总记录数
pageSize: 10, //每页的个数
visiblePages: 5, //可视页数
currentPage: result.pageRequest.currentPage, //当前页码
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);
if(type=="change"){
location.href="book_list.html?currentPage="+page;
}
}
});
}
});
}
function deleteBook(id) {
var isDelete = confirm("确认删除?");
if (isDelete) {
//删除图书
alert("删除成功");
}
}
function batchDelete() {
var isDelete = confirm("确认批量删除?");
if (isDelete) {
//获取复选框的id
var ids = [];
$("input:checkbox[name='selectBook']:checked").each(function () {
ids.push($(this).val());
});
console.log(ids);
alert("批量删除成功");
}
}
</script>
</div>
</body>
</html>
3.4 单元测试
由于设有默认值,不传参数进行测试:

也可传参进行测试

通过前端页面进行测试:

