综合案例1:图书管理系统(1)项目准备,用户登录接口,添加图书接口
文章目录
- 综合案例1:图书管理系统(1)项目准备,用户登录接口,添加图书接口
-
- 观前提醒:
-
- 无Mybatis版本获取:
- [基于 Mybatis版本 的获取:](#基于 Mybatis版本 的获取:)
- 个人建议:
- [1. 准备工作:](#1. 准备工作:)
-
- [1.1 数据库设计:](#1.1 数据库设计:)
- [1.2 建立表,存储信息](#1.2 建立表,存储信息)
-
- [重新编写 UserInfo类:](#重新编写 UserInfo类:)
- [重新编写 BookInfo类:](#重新编写 BookInfo类:)
- 修改前端代码:
- [1.3 引入依赖](#1.3 引入依赖)
- [1.4 编写配置文件](#1.4 编写配置文件)
- [2. 后端代码编写](#2. 后端代码编写)
-
- [2.1 用户登陆接口:](#2.1 用户登陆接口:)
- [2.2 添加图书接口:](#2.2 添加图书接口:)
-
- mapper层(数据持久层):
- Service层(业务层):
- Controller层:
- 接口测试:
-
- Postman:
-
- 问题:程序报错,XXX为空
- [解决:传入 publish](#解决:传入 publish)
- 网页(须修改前端代码):
- [book_add.html 前端代码:](#book_add.html 前端代码:)
- [3. 打印日志 & 调试:](#3. 打印日志 & 调试:)
-
- [3.1 打印日志](#3.1 打印日志)
- [3.2 调试:](#3.2 调试:)
- [4. 遗留问题:](#4. 遗留问题:)
-
- 第一个:为什么是通过用户名获取数据
- [第二个问题:登录成功之后,为什么密码要设置为 空](#第二个问题:登录成功之后,为什么密码要设置为 空)
- [5. 总结:](#5. 总结:)
观前提醒:
这个图书管理系统,非常的简陋,仅作为练习使用。不建议大家使用我介绍的 图书管理系统 ,去作为 课程设计。
无Mybatis版本获取:
这个 图书管理系统 的实现,需要你从我的 gitee上,将 无Mybatis版本 的图书管理系统源代码下载下来。
gitee链接:https://gitee.com/mrbgvhbhjv/java-ee-course/tree/master/后端代码/springboot_bookManage_System

基于 Mybatis版本 的获取:
如果你想直接获取 基于 Mybatis 完全实现了增删查改功能的图书管理系统,就从我的 gitee 上面获取,下载源代码。
gitee链接:https://gitee.com/mrbgvhbhjv/java-ee-course/tree/master/后端代码/Book_System_20251107

个人建议:
这篇博客的代码,建议自己敲一下。
跟着我的步骤,使用 无Mybatis版本 的图书管理系统源代码 ,一步一步的将代码实现出来。
这里使用的数据库是 MySQL。
图形化工具:Navicat
虽然,截止到 2026年,cursor,TRAE等 AI工具,能够一键编写代码,甚至一个系统。
但是,如果你不会基础知识,只会让 ai 生成代码,不会看代码,不会修改代码,代码运行报错,你不会解决问题等等。其实,你都不算是一个 Java开发者。
如果我们将 AI工具,对我们的工作效率提升,有巨大的帮助,它一定是一个乘法结算的结果。
你的基础开发能力 * AI工具效率 == 工作效率。
如果你的基础开发能力不行,约等于 0,那么,无论 AI工具效率多高,99倍也好,0 * 99 == 0
所以,只有将我们自己的基础开发能力提升了,使用 AI工具提升效率,才是事半功倍的效果。
1. 准备工作:
一个项目的开发,可以大致分为这么几个流程:

开发阶段的设计:首先是架构设计,然后是功能设计,再是数据库设计,最后是接口设计。
接口设计,我们每一个功能模块,会单独说。
1.1 数据库设计:
数据库的表,分为两类表:
- 实体表
- 关系表
实体表:对应的是 Java对象,学生类对应学生表,还有班级表,课程表等等。
关系表:实体和实体之间的关系,例如:一个班级对应多个学生,一个课程对应多个学生。
实体间关系类型:
- 一对一
- 一对多(多对一)
- 多对多
什么时候需要创建关系表?
答:当两个实体表的的关系为 多对多 的时候,就必须设计关系表。
1.2 建立表,存储信息
创建数据库,创建图书表和用户表:
sql
-- 创建数据库
DROP DATABASE IF EXISTS book_test;
CREATE DATABASE book_test DEFAULT CHARACTER SET utf8mb4;
-- 用户表
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, '⺠主与建设出版社');
重新编写 UserInfo类:
java
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;
}
重新编写 BookInfo类:
java
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
@Data
public class BookInfo {
//图书ID
private Integer id;
//书名
private String bookName;
//作者
private String author;
//数量
private Integer count;
//定价
private BigDecimal price;
//出版社
private String publish;
//状态 0-⽆效 1-允许借阅 2-不允许借阅
private Integer status;
private String statusCN;
//创建时间
private Date createTime;
//更新时间
private Date updateTime;
}
由于 BookInfo类 的信息,发生变化,前端代码,也要进行修改:
修改前端代码:
html
function getBookList ()
{
$.ajax({
type: "get",
url: "/book/getList",
success: function (books)
{
var wholeHtml = "";
for (var book of books) {
// 拼接列表
wholeHtml += '<tr>';
wholeHtml += '<td><input type="checkbox" name="selectBook" value="' + book.bookID + '" id="selectBook" class="book-select"></td>';
wholeHtml += '<td>' + book.id + '</td>';
wholeHtml += '<td>' + book.bookName + '</td>';
wholeHtml += '<td>' + book.author + '</td>';
wholeHtml += '<td>' + book.count + '</td>';
wholeHtml += '<td>' + book.price + '</td>';
wholeHtml += '<td>' + book.publish + '</td>';
wholeHtml += '<td>' + book.statusCN + '</td>';
wholeHtml += '<td><div class="op">';
wholeHtml += '<a href="book_update.html bookId=' + book.bookID + '">修改</a>';
wholeHtml += '<a href="javascript:void(0)" onclick="deleteBook(' + book.bookID + ')">删除</a>';
wholeHtml += '</div></td></tr>';
}
$("tbody").html(wholeHtml);
}
});
}
1.3 引入依赖
引入 Mybatis框架的依赖,Mysql的驱动依赖:
java
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>3.0.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
1.4 编写配置文件
此处,我们使用的是 yml配置文件:
yaml
spring:
application:
name: book_system
# 数据库连接配置
datasource:
url: jdbc:mysql://127.0.0.1:3306/book_test?characterEncoding=utf8&useSSL=false
username: 数据库用户名
password: 数据库用户对应的密码
driver-class-name: com.mysql.cj.jdbc.Driver
# 配置打印 MyBatis⽇志
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#配置驼峰⾃动转换
map-underscore-to-camel-case: true
# XML文件映射
mapper-locations: classpath:mapper/**Mapper.xml
2. 后端代码编写
2.1 用户登陆接口:
接口文档:

mapper层(数据持久层):
java
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.example.book_system_20251107.book.modul.UserInfo;
@Mapper
public interface UserInfoMapper {
@Select("select * from user_info where user_name = #{userName} and delete_flag = 0")
UserInfo selectUserByUserName(String userName);
}
Service层(业务层):
java
import org.example.book_system_20251107.book.mapper.UserInfoMapper;
import org.example.book_system_20251107.book.modul.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserInfoService {
@Autowired
private UserInfoMapper userInfoMapper;
public UserInfo selectUserInfoByUserName(String userName) {
return userInfoMapper.selectUserByUserName(userName);
}
}
Controller层:
java
import jakarta.servlet.http.HttpSession;
import org.example.book_system_20251107.book.modul.UserInfo;
import org.example.book_system_20251107.book.service.UserInfoService;
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;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserInfoService userInfoService;
@RequestMapping("/login")
public Boolean login(String name, String password, HttpSession session) {
//1. 验证输入参数是否为空
if (!StringUtils.hasLength(name) || !StringUtils.hasLength(password)) {
return false;
}
// 根据用户名,获取用户信息
UserInfo userInfo = userInfoService.selectUserInfoByUserName(name);
if (userInfo == null) {
// userInfo == null 表示: 查询不到用户,就返回 false
return false;
}
//2.匹配用户名和密码
if (password.equals(userInfo.getPassword())){
//3. 存储 Session
// 防止密码泄露,此处的 userInfo对象,密码设置为 null,但是不影响数据库中的数据
userInfo.setPassword("");
// 密码为 "",其他人无法通过session,获取到用户密码
session.setAttribute("user", userInfo);
return true;
}
// 没有学数据库相关的操作,写死用户名和密码
// if ("admin".equals(name) && "admin".equals(password)) {
// session.setAttribute("username", name);
// return true;
// }
//4. 返回结果
return false;
}
}
接口测试:
Postman:

网页:
可以登录成功:

2.2 添加图书接口:
添加图书,并不需要输入图书id,id是自增的。
接口文档描述:
请求
/book/addBookContent-Type: application/x-www-form-urlencoded; charset=UTF-8
参数
bookName=图书1&author=作者1&count=23&price=36&publish=出版社1&status=1
响应
"" //失败信息, 成功时返回空字符串
mapper层(数据持久层):
java
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.example.book_system_20251107.book.modul.BookInfo;
@Mapper
public interface BookInfoMapper {
@Insert("insert into book_info(book_name,author,count,price,publish) " +
"values (#{bookName},#{author},#{count},#{price},#{publish})")
Integer insertBookInfo(BookInfo bookInfo);
}
Service层(业务层):
java
/**
* 添加图书
* @param bookInfo
* @return 返回值表示添加的行数
*/
public Integer addBook(BookInfo bookInfo){
return bookInfoMapper.insertBookInfo(bookInfo);
}
Controller层:
有这么几个需要编写的环节:
-
参数校验
-
存储数据
-
返回结果
参数校验问题:
参数校验流程:先执行参数校验,校验通过后再执行图书添加操作。
在实际开发中,无论前端是否已做参数校验,后端都必须强制进行参数校验。
原因:后端接口可能遭受黑客攻击,攻击者可绕过前端直接请求接口。若后端不做校验,极易产生脏数据,影响系统数据安全与稳定性。
例如:爬虫(获取数据),攻击(伤害服务器)
java
@RequestMapping("/addBook")
public String addBook(BookInfo bookInfo){
// 步骤:
// 1.参数验证
// 2.存储数据
// 3.返回结果
// 此处的参数校验,十分粗糙,只判断了参数是否为空,
// 并没有考虑 Integer参数,最大值是否溢出,String参数,是否超过最大字符串限制等等
// hasLength:数据为空,返回 false,数据不为空,返回 true
if (!StringUtils.hasLength(bookInfo.getBookName())){
return "添加图书,书名为空。";
}
if (!StringUtils.hasLength(bookInfo.getAuthor())){
return "添加图书,作者名为空。";
}
if (bookInfo.getCount() == null){
return "添加图书,图书数量为空。";
}
if (bookInfo.getPrice() == null){
return "添加图书,图书价格为空。";
}
if (!StringUtils.hasLength(bookInfo.getPublish())){
return "添加图书,图书出版社为空。";
}
// 数据存储:
try {
bookService.addBook(bookInfo);
return "";
}catch (Exception e){
return ("添加图书发生异常,e:" + e.getMessage());
}
}
接口测试:
Postman:

问题:程序报错,XXX为空
我们可以进行调试,打断点。
F8 是下一步,F9 是退出调试。
我们还可以通过鼠标右键,选择某一块代码,去查看这块代码的结果:



参数不合法的 原因:没有传入 publish
解决:传入 publish

网页(须修改前端代码):
后端开发,不关注前端是如何设计的,所以,前端代码,不进行讲解,需要掌握的是:学会如何使用 ajax发送请求。
book_add.html 前端代码:
html
<script type="text/javascript" src="js/jquery.min.js"></script>
<script>
function add() {
//前端应该进行参数校验,此处省略
//提交请求到后端
$.ajax({
type: "post",
url: "/book/addBook",
data: $("#addBook").serialize(),
success: function(result) {
if (result == "") {
//添加图书成功
location.href = "book_list.html";
} else {
//失败
alert(result);
}
}
});
}
</script>
测试:


3. 打印日志 & 调试:
3.1 打印日志
我们可以在上述程序中,添加日志,记录程序的运行信息:
使用 @Slf4j注解

打印日志的方式:
留意 log.info() 的两种方式。

一般来说,输出的参数,都是方法的参数,异常信息除外。
3.2 调试:
代码出现问题,要学会调试:
调试,首先要打断点,然后选择调试程序。
F8 是下一步,F9 是退出调试。
我们还可以通过鼠标右键,选择某一块代码,去查看这块代码的结果:



参数不合法的 原因:没有传入 publish
4. 遗留问题:
第一个:为什么是通过用户名获取数据
代码:
java
//Mapper层
@Select("select * from user_info where user_name = #{userName} and delete_flag = 0")
UserInfo selectUserByUserName(String userName);
//Controller层:
@RequestMapping("/login")
public Boolean login(String name, String password, HttpSession session) {
// 1. 验证是否为空
//2.匹配用户名和密码
//3. 存储 Session
//4. 返回结果
if (!StringUtils.hasLength(name) || !StringUtils.hasLength(password)) {
return false;
}
// 根据用户名,获取用户信息
UserInfo userInfo = userInfoService.selectUserInfoByUserName(name);
if (userInfo == null) {
// userInfo == null 表示: 查询不到用户,就返回 false
return false;
}
if (password.equals(userInfo.getPassword())){
// 防止密码泄露,此处的 userInfo对象,密码设置为 null,但是不影响数据库中的数据
userInfo.setPassword("");
// 密码为 "",其他人无法通过session,获取到用户密码
session.setAttribute("user", userInfo);
return true;
}
// 没有学数据库相关的操作,写死用户名和密码
// if ("admin".equals(name) && "admin".equals(password)) {
// session.setAttribute("username", name);
// return true;
// }
return false;
}
为什么是通过用户名获取数据,可以直接按照用户名密码来查找,这样返回值不为空不就是 true,就不用在 Controller代码中,验证密码了?
答:当前代码,可以这样写。
但是,企业开发中,数据库关于用户的密码,存储的方式,一般是密文存储 ,通过密码是查询不出来数据的。
如果是加密的数据库,我们建议使用 用户名 来查询数据,并对其他字段(如 密码),进行解密,然后单独验证 密码 等敏感信息。
数据库是可以加密的,这也是为了防止网络的攻击。
第二个问题:登录成功之后,为什么密码要设置为 空
代码:
java
if (password.equals(userInfo.getPassword())){
// 防止密码泄露,此处的 userInfo对象,密码设置为 null,但是不影响数据库中的数据
userInfo.setPassword("");
// 密码为 "",其他人无法通过session,获取到用户密码
session.setAttribute("user", userInfo);
return true;
}
可以不设置,因为 session 是存储在服务端的内存中的。
但是,密码要怎么设置,怎么保护这个敏感信息,要看后端如何实现。
session,可以通过技术手段,放到 redis 中。
redis 是一个 缓存中间件,通常也不放敏感数据,如果要放,也是要加密的。
5. 总结:
这篇博客,主要是对 无Mybatis版本 的图书管理系统,进行了部分代码的重新编写。
最后,如果这篇博客能帮到你的,请你点点赞,有写错了,写的不好的,欢迎评论指出,谢谢!
下一篇博客:综合案例1:图书管理系统(2)图书列表接口