【JavaEE进阶】——利用框架完成功能全面的图书管理系统
目录
🚩项目所需要的技术栈
🚩项目准备工作
🎈环境准备
🎈数据库准备
🚩前后端交互分析
🎈登录
📝前后端交互
📝实现服务器代码
📝测试前后端代码是否正确
🎈添加图书
📝前后端交互
📝实现服务器代码
📝测试前后端代码是否正确
🎈图书列表
📝前后端交互
📝实现服务器代码
📝测试前后端代码是否正确
🎈修改图书
📝前后端交互
📝实现服务器代码
📝测试前后端代码是否正确
🎈删除图书
📝约定前后端交互接⼝
📝实现服务器代码
📝测试前后端代码是否正确
🎈批量删除图书
📝约定前后端交互接⼝
📝实现服务器代码
📝测试前后端代码是否正确
🚩项目所需要的技术栈
该项目是一个针对于SpringBoot+Mybatis+SpringMVC的基础运用项目适合初学者来检验水平测试能力,该项目所需技术栈如下:
>* SpringBoot:作为项目的框架,使用Maven托管代码
>* Mybatis:使用Mybatis框架操纵数据库,其中使用了xml和注解两种方式去操作数据库
>* 前端ajax:前后端的交互使用的是ajax作为前端为后端发送数据以及接收数据
>* 项目分层:项目分为前端页面+control(与前端建立连接的控制层)+Service(服务层供control层进行调用)+Mapper(操纵数据库实现数据与后端代码的 交互)+model(需要实现的主类)。
🚩项目准备工作
🎈环境准备
项目的创建需要选好项目名,项目路径,语言为java,type是基于maven构建,jdk可以选择17以上的(切记最好不要用jdk8),packing是打成jar包。

此时项目创建成功。MySQL Driver和MyBatis Framework引⼊MyBatis 和 MySQL驱动依赖

也可以手动引入依赖,上面只是更简单。
这是围绕整个项目的配置文件,没有该配置文件,是无法运行成功的,没有它们你就完成不了一个项目。我们依赖该pom.xml文件,让我们能完成该项目。
SpringBookt配置文件,统一使用yml格式 application.yml
很多项⽬或者框架的配置信息也放在配置⽂件中, ⽐如:
- • 项⽬的启动端⼝
- • 数据库的连接信息(包含⽤⼾名和密码的设置)
- • ⽤于发现和定位问题的普通⽇志和异常⽇志等
server:port: 8080
#配置数据库
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/book_system?characterEncoding=utf8&useSSL=falseusername: rootpassword: driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:configuration:#配置驼峰自动转换map-underscore-to-camel-case: true#sql日志(打印出来让我们可以清楚自己的sql语句是否正确)log-impl: org.apache.ibatis.logging.stdout.StdOutImpl#Spring Boot可以正确找到并加载位于 classpath:mapper/目录下的 xxxxMapper XML 文件,#从而在应用程序中使用这些 Mapper 进行数据库操作。(因为有时候sql语句需要动态)mapper-locations: classpath:mapper/BookMapper.xml
#将日志记录到一个文件可以通过在配置文件中指定日志文件的位置和名称来实现。
logging:file:name: spring-book.log

🎈数据库准备
- 数据库表的设计是应用程序开发的一个重要的环节。设计数据库表是根据业务需求相关的。
- 数据库表通常分成两种:实体表和关系表,就如图书管理系统来说,图书馆里系统相对是简单的,只有两个实体:用户和图书,并且用户和图书之间没有关联关系。
- 表的具体字段设计,也与需求相关。
⽤⼾表:有⽤⼾名和密码即可(复杂的业务可能还涉及昵称, 年龄等资料)
图书表:有哪些字段, 也是参考需求⻚⾯(通常不是⼀个⻚⾯决定的, ⽽是要对整个 系统进⾏全⾯分析观察后定的)
创建数据库book_system 用户表user_info 图书表 book_info
DROP DATABASE IF EXISTS book_system;
CREATE DATABASE book_system DEFAULT CHARACTER SET utf8mb4;
use book_system;-- ⽤⼾表
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, status)
VALUES
('深入理解计算机系统', 'Randal E. Bryant', 18, 98.00, '机械工业出版社', 1),
('现代操作系统', 'Andrew S. Tanenbaum', 10, 89.00, '清华大学出版社', 1),
('计算机网络:自顶向下方法', 'James Kurose', 14, 85.00, '电子工业出版社', 1),
('数据库系统概念', 'Abraham Silberschatz', 7, 110.00, '机械工业出版社', 1),
('算法导论', 'Thomas H. Cormen', 5, 130.00, '机械工业出版社', 1),
('机器学习', '周志华', 20, 120.00, '清华大学出版社', 1),
('Python编程:从入门到实践', 'Eric Matthes', 25, 65.00, '电子工业出版社', 1),
('深度学习', 'Ian Goodfellow', 12, 180.00, '人民邮电出版社', 1),
('计算机图形学', 'John F. Hughes', 9, 95.00, '机械工业出版社', 1),
('操作系统真相还原', '史蒂文·穆查', 15, 80.00, '电子工业出版社', 1);
package com.example.cl.model;import lombok.Data;import java.math.BigDecimal;
import java.util.Date;@Data
public class BookInfo {//图书IDprivate 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;
}package com.example.cl.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;
}
🚩前后端交互分析
🎈登录
<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> 前端登录有两个表单,输入用户名和密码,表单中name属性是用于表单提交时用于标识表单数据的属性,服务器端通过‘name’属性来获取提交的数据。此时前端的id属性来获取客户端输入的用户名和密码给后端。
📝前后端交互
function login() {// var userName= $("#userName").val();// var password = $("#password").val()$.ajax({url: "/user/login",type: "post",data:{userName: $("#userName").val(),password: $("#password").val()}} 前端发出请求:
url:/user/login
type:post
参数:
data:userName=$("#userName").val()【admin】
password=$("#password").val()【admin】
响应:
true (密码或者用户名正确,返回true)
function login() {// var userName= $("#userName").val();// var password = $("#password").val()$.ajax({url: "/user/login",type: "post",data:{userName: $("#userName").val(),password: $("#password").val()},success:function(result){if(result.code=="SUCCESS" && result.data ==""){//密码正确location.href = "book_list.html?pageNum=1";}else{alert(result.errMsg);}}});}因为登录页面基本上错误出现在用户名和密码上,如果用户名和密码出现问题,其实并没有必要返回error信息,如果服务器返回的结果码是“SUCCESS”和返回的结果数据是“”,那么就表明密码正确,否则就弹窗 结果的errMsg错误信息。
📝实现服务器代码
我们要知道三层架构,mapper——service——controller
control(与前端建立连接的控制层)
Service(服务层供control层进行调用)
Mapper(操纵数据库实现数据与后端代码的 交互)
model(需要实现的主类)。
我们该如何实现服务器代码呢?首先,我们需要获取到delet_flag=0对应的userName因为,如果已经删除了,那么就也得返回null,如果没有找到服务器收到的userName那么返回null,如果找到了并且并没有删除该用户,那么就成功。
🎓数据层
package com.example.cl.mapper;import com.example.cl.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;@Mapper
public interface UserInfoMapper {@Select("select * from user_info where delete_flag=0 and user_name=#{name}")UserInfo queryUserByName(String name);
} 🎓业务层
package com.example.cl.service;import com.example.cl.mapper.UserInfoMapper;
import com.example.cl.model.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class UserInfoService {@Autowiredprivate UserInfoMapper userInfoMapper;public UserInfo queryUserByName(String userName){return userInfoMapper.queryUserByName(userName);}
}
🎓控制层
package com.example.cl.model;import com.example.cl.enums.ResultStatus;
import lombok.Data;@Data
public class Result<T> {private ResultStatus code; //业务码 不是Http状态码 200-成功 -2 失败 -1 未登录private String errMsg; //错误信息, 如果业务成功, errMsg为空private T data;public static <T> Result success(T data){Result result=new Result<>();result.setCode(ResultStatus.SUCCESS);result.setData(data);return result;}public static Result fail(String msg){Result result=new Result<>();result.setCode(ResultStatus.Fail);result.setErrMsg(msg);return result;}
} package com.example.cl;public enum StatusResult {SUCCESS(200),//成功返回200FAIL(-1),//错误返回-1NOLOGIN(-2)//未登录返回-2;private int code;StatusResult(int code) {this.code=code;}public int getCode() {return code;}public void setCode(int code) {this.code = code;}
}
package com.example.cl.controller;import com.example.cl.constant.constants;
import com.example.cl.model.Result;
import com.example.cl.model.UserInfo;
import com.example.cl.service.UserInfoService;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/user")
public class UserInfoController {@Autowiredprivate UserInfoService userInfoService;@RequestMapping(value = "/login",produces = "application/json")public Result login(String userName, String password, HttpSession session){//1.效验参数if(!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)){//如果长度其中一个为空,return Result.fail("用户名或者密码为空");}//账号和密码是否正确//获取查找当前userName的用户所有信息UserInfo userInfo=userInfoService.queryUserByName(userName);if(userInfo==null){return Result.fail("用户不存在");}if(!password.equals(userInfo.getPassword())){return Result.fail("密码错误");}//3.正确情况session.setAttribute(constants.USER_SESSION_KEY,userInfo);return Result.success("");//成功返回空串}
} 📝测试前后端代码是否正确
后端通过postman进行检查,最后返回的结果正确,说明后端代码是没有问题的, 如果后端代码有问题会返回505错误码。


我们可以看到如果成功,errMsg为空,data返回给前端也是空,code是success

🎈添加图书
📝前后端交互

前端发出请求:
url:/book/addBook
type:post
参数:
date:传给服务器的数据 ,用到的$("#addBook").serialize() 表示form表单中所有的数据
响应:
"" //失败信息,成功返回空串
function add() {$.ajax({url: "/book/addBook",type: "post",data: $("#addBook").serialize(),success: function (result) {if (result.code == "SUCCESS" && result.data == "") {//添加成功location.href = "book_list.html";} else {alert(result.data);}}, error: function (error) {//用户未登录if (error.code=="NOLOGIN"&&error.data==null) {location.href = "login.html";}}});}如果没有返回错误,那么就判断result的data是否为空,如果为空,我们就跳转到博客列表页,如果不等于“”,我们就弹框
如果返回错误,说明是用户未登录,那么就要判断error中的数据是否是未登录页码
📝实现服务器代码
package com.example.cl.mapper;import com.example.cl.model.BookInfo;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface BookInfoMapper {/*** 插入图书*/@Insert("insert into book_info (book_name, author, `count`, price, publish, `status`) "+ "values (#{bookName}, #{author}, #{count}, #{price}, #{publish}, #{status})")Integer insertBook(BookInfo bookInfo);
} 🎓业务层
package com.example.cl.service;import com.example.cl.mapper.BookInfoMapper;
import com.example.cl.mapper.UserInfoMapper;
import com.example.cl.model.BookInfo;
import com.example.cl.model.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class BookInfoService {@Autowiredprivate BookInfoMapper bookInfoMapper;public Integer insertBook(BookInfo bookInfo){return bookInfoMapper.insertBook(bookInfo);}
}
🎓控制层
添加@Slf4j可以让我们再运行的时候,控制台会进行报出日志消息。
public static<T> Result nologin(String errMsg){Result result=new Result();result.setErrMsg(errMsg);result.setCode(StatusResult.NOLOGIN);return result;}
/*** 增加图书*/@RequestMapping(value = "/addBook",produces = "application/json")public Result<String> addBook(BookInfo bookInfo, HttpSession session){Result<String> ans=new Result<>();//1.判断用户是否登录//如果用户信息为空, 说明用户未登录UserInfo loginUserInfo = (UserInfo) session.getAttribute(constants.USER_SESSION_KEY);if (loginUserInfo==null || loginUserInfo.getId()<=0){return Result.nologin("用户未登录");}//1.效验参数log.info("添加图书,接收到的参数bookInfo:{}",bookInfo);if (!StringUtils.hasLength(bookInfo.getBookName())|| !StringUtils.hasLength(bookInfo.getAuthor())|| bookInfo.getCount()==null|| bookInfo.getPrice()==null|| !StringUtils.hasLength(bookInfo.getPublish())|| bookInfo.getStatus()==null){ans.setData("输入错误");ans.setCode(StatusResult.FAIL);return ans;}//添加图书try {Integer result=bookInfoService.insertBook(bookInfo);if(result>0){ans.setData("");ans.setCode(StatusResult.SUCCESS);return ans;}}catch (Exception e){log.error("添加图书失败");}ans.setCode(StatusResult.FAIL);ans.setData("添加失败");return ans;} 📝测试前后端代码是否正确
我们再来看用户未登录状态:返回的错误信息“errMsg”,code为LOGIN

检查前端代码

此时查看数据库中的数据,此时增加了一条。


🎈图书列表
需求分析我们之前做的表⽩墙查询功能,是将数据库中所有的数据查询出来并展⽰到⻚⾯上,试想如果数据库中的数据有很多(假设有⼗⼏万条)的时候,将数据全部展⽰出来肯定不现实,那如何解决这个问题呢?
分⻚时, 数据是如何展⽰的呢第1⻚: 显⽰1-10 条的数据第2⻚: 显⽰11-20 条的数据第3⻚: 显⽰21-30 条的数据以此类推...要想实现这个功能, 从数据库中进⾏分⻚查询,我们要使⽤ LIMIT 关键字,格式为:limit 开始索引每⻚显⽰的条数(开始索引从0开始)
我们先增加一些数据,让每一页都更加的明显显示出来。
第一页的开始索引是0 (1-0)*10

第二页的开始索引是10,(2-1)*10
前端发出请求,需要向服务器传递参数
- currentPage 当前⻚码 //默认值为1
- ◦ pageSize 每⻚显⽰条数 //默认值为10
为了项⽬更好的扩展性, 通常不设置固定值, ⽽是以参数的形式来进⾏传递扩展性: 软件系统具备⾯对未来需求变化⽽进⾏扩展的能⼒ ,⽐如当前需求⼀⻚显⽰10条, 后期需求改为⼀⻚显⽰20条, 后端代码不需要任何修改
后端响应时, 需要响应给前端的数据
- records 所查询到的数据列表(存储到List 集合中)
- ◦ total 总记录数 (⽤于告诉前端显⽰多少⻚, 显⽰⻚数为: (total + pageSize -1)/pageSize
package com.example.cl.model;import lombok.Data;@Data
public class PageRequest {private Integer pageNum =1;//当前页private Integer pageSize = 10;//每页中的记录数private Integer offset;//起始索引public Integer getOffset() {return (pageNum-1) * pageSize;}
} 通过当前页和每页的记录数,记录当前的起始索引。
package com.example.cl.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;//当前页的数据private Integer count;//所有的记录数private PageRequest pageRequest;//从哪个下标开始,显示多少本书
}
📝前后端交互
前端发出请求
url:
/book/getListByPage?currentPage=1&pageSize=10Content-Type: application/x-www-form-urlencoded; charset=UTF-8type: get
参数:
响应:{"errMsg":"","data":"","code":""}
📝实现服务器代码
🎓数据层
/*** 获取当前页数据*/@Select("select * from book_info where status !=0 order by id asc limit #{offset}, #{pageSize}")//升序List<BookInfo> queryBookListByPage(PageRequest pageRequest);//查询当前页所有未借阅的书
//offset索引起始 pageSize一页展示多少数据@Select("select count(1) from book_info where status<>0")Integer count();//统计未借阅的书的本数 🎓业务层
package com.example.cl.enums;public enum BookStatus {DELETE(0,"删除"),NORMAL(1,"可借阅"),FORBIDDEN(2,"不可借阅");BookStatus(Integer code, String desc) {this.code = code;this.desc = desc;}private Integer code;//数字private String desc;//内容//根据code,返回描述信息public static BookStatus getDescByCode(Integer code){switch (code){case 0:return DELETE;case 1:return NORMAL;case 2:default:return FORBIDDEN;}}public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public String getDesc() {return desc;}public void setDesc(String desc) {this.desc = desc;}
业务层
public PageResult<BookInfo> queryBookListByPage(PageRequest pageRequest){//获取所有未借阅的书的总数Integer count=bookInfoMapper.count();//获取当前页的记录List<BookInfo>bookInfos=bookInfoMapper.queryBookListByPage(pageRequest.getOffset(), pageRequest.getPageSize());//3.处理状态(0表示删除,1表示可借阅,2表示未借阅,之前用数字代替,现在需要转成string形式for (BookInfo bookInfo:bookInfos) {bookInfo.setStatus(BookStatus.getDescByCode(bookInfo.getStatus()).getCode());}return new PageResult(bookInfos,count,pageRequest);} 🎓控制层
/*** 获取当前页数据*/@RequestMapping("/getBookListByPage")public Result<PageResult<BookInfo>> getBookListByPage(PageRequest pageRequest,HttpSession session){Result<PageResult<BookInfo>> ans=new Result<>();//1.判断用户是否登录//如果用户信息为空, 说明用户未登录UserInfo loginUserInfo = (UserInfo) session.getAttribute(constants.USER_SESSION_KEY);if (loginUserInfo==null || loginUserInfo.getId()<=0){return Result.nologin("用户未登录");}//PageResult<BookInfo> bookList=bookInfoService.queryBookListByPage(pageRequest);return Result.success(bookList);} public static <T> Result success(T data){Result result=new Result();result.setCode(StatusResult.SUCCESS);result.setData(data);return result;} 此时结果返回bookList类型的。
📝测试前后端代码是否正确

🎈修改图书
📝前后端交互
[请求]/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"}
[ 请求 ]/book/updateBookContent-Type: application/x-www-form-urlencoded; charset=UTF-8[ 参数 ]id=1&bookName= 图书 1&author= 作者 1&count=23&price=36&publish= 出版社 1&status=1[ 响应 ]"" // 失败信息 , 成功时返回空字符串
📝实现服务器代码

/*** 修改图书*///通过id查询图书信息@Select("select * from book_info where id=#{bookid} and status!=0")BookInfo queryBookById(Integer bookid);Integer updateBookById(BookInfo bookInfo); 通过点击修改按钮,之后跳转到更新页面的时候,就相当于通过id获取到图书信息显示再表单中,然后我们就可以再文本框中输入值,进行更改数据。

<?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.cl.mapper.BookMapper"><update id="updateBookById">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 != null">publish =#{publish},</if><if test="status != null">status =#{status}</if></set>where id=#{id}</update>
<mapper> 因为有些数据我们可能是不想改的,所以我们再更新图书的时候进行了动态sql,用了xml文件进行实现动态sql。
🎓业务层
/*更改图书*/public Integer updateBookById(BookInfo bookInfo){return bookInfoMapper.updateBookById(bookInfo);}public BookInfo queryBookById(Integer bookid){return bookInfoMapper.queryBookById(bookid);} 🎓控制层
/*** 查询图书信息*/@RequestMapping("/queryBookById")public Result<BookInfo> queryBookById(Integer bookId,HttpSession session) {Result<BookInfo>ans=new Result<>();//校验用户信息是否为空,说明是未登录状态UserInfo loginUserInfo = (UserInfo) session.getAttribute(constants.USER_SESSION_KEY);if (loginUserInfo==null || loginUserInfo.getId()<=0){ans.setData(null);ans.setCode(StatusResult.NOLOGIN);return ans;}log.info("根据ID查询图书信息, id:"+bookId);long start=System.currentTimeMillis();BookInfo bookInfo=bookInfoService.queryBookById(bookId);ans.setCode(StatusResult.SUCCESS);ans.setData(bookInfo);log.info("queryBookById 耗时: "+ (System.currentTimeMillis()-start) + "ms");return ans;}/*** 更新图书*/@RequestMapping(value = "/updateBook", produces = "application/json")public Result<String> updateBook(BookInfo bookInfo,HttpSession session) {Result<String>ans=new Result<>();//效验参数是否登录UserInfo loginUserInfo = (UserInfo) session.getAttribute(constants.USER_SESSION_KEY);if (loginUserInfo==null || loginUserInfo.getId()<=0){ans.setCode(StatusResult.NOLOGIN);ans.setData(null);return ans;}log.info("更新图书, bookInfo: {}", bookInfo);try {Integer result=bookInfoService.updateBookById(bookInfo);if(result>0){ans.setData("");ans.setCode(StatusResult.SUCCESS);return ans;}}catch (Exception e){log.error("更新图书失败");}ans.setData(null);ans.setCode(StatusResult.FAIL);return ans;} 首先通过id查询图书信息,返回的数据是BookInfo类型的,因为我们需要把书返回给客户端。
然后通过id进行修改该书。如果更新的结果行数大于0,说明更新成功,如果没有,那么就更新失败。返回null数据。
📝测试前后端代码是否正确
先测后端代码,首先是未登录状态

登录状态:
通过id获取图书信息

通过id更改图书信息

检查前端代码:

🎈删除图书
📝约定前后端交互接⼝
逻辑删除逻辑删除也称为软删除、假删除、Soft Delete,即不真正删除数据,⽽在某⾏数据上增加类型 is_deleted的删除标识,⼀般使⽤UPDATE语句物理删除物理删除也称为硬删除,从数据库表中删除某⼀⾏或某⼀集合数据,⼀般使⽤DELETE语句
删除图书的两种实现⽅式
- 逻辑删除 update book_info set status=0 where id = 1
- 物理删除 delete from book_info where id=25
/book/deleteBook[ 请求 ]/book/deleteBookContent-Type: application/x-www-form-urlencoded; charset=UTF-8[ 参数 ]id=1&status=0[ 响应 ]"" // 失败信息 , 成功时返回空字符串
当后端接收到bookid的时候,数据层通过id删除该图书,由于删除的sql语句,返回的是int类型,如果删除成功,执行的长度>0,并且返回""数据,并且设置字节码为successs,然后跳转到列表页,如果执行失败,长度为0,返回的是null数据,并且设置字节码fail。失败的情况是未登录状态,如果返回的数据是空并且字节码是nologin,就返回的到登录页面

📝实现服务器代码
🎓数据层
删除我们是按照id进行删除的,并且我们是按照逻辑删除的,所以就相当于通过id进行更新。
🎓服务层
/*** 删除图书*/public Integer deleteBook(Integer bookId){BookInfo bookInfo=new BookInfo();bookInfo.setId(bookId);bookInfo.setStatus(0);return bookInfoMapper.updateBookById(bookInfo);}
🎓控制层
/*** 删除图书*/@RequestMapping("/deleteBook")public Result<Boolean> deleteBook(Integer bookId,HttpSession session){Result<Boolean>ans=new Result<>();//效验参数是否登录UserInfo loginUserInfo = (UserInfo) session.getAttribute(constants.USER_SESSION_KEY);if (loginUserInfo==null || loginUserInfo.getId()<=0){ans.setCode(StatusResult.NOLOGIN);ans.setData(null);return ans;}log.info("删除图书,bookId:{}",bookId);Integer result=bookInfoService.deleteBook(bookId);if(result>0){ans.setData(true);ans.setCode(StatusResult.SUCCESS);return ans;}ans.setData(false);ans.setCode(StatusResult.FAIL);return ans;} 控制层返回的数据是返回给客户端代码中。
📝测试前后端代码是否正确
先测后端代码,首先是未登录状态
检查前端代码:


此时删除成功,返回的列表页。
🎈批量删除图书
📝约定前后端交互接⼝
/book/batchDeleteBook[ 请求 ]/book/batchDeleteBookContent-Type: application/x-www-form-urlencoded; charset=UTF-8[ 参数 ]id=1&status=0[ 响应 ]"" // 失败信息 , 成功时返回空字符串
function batchDelete() {var isDelete = confirm("确认批量删除?");if (isDelete) {//获取复选框的idvar ids = [];//已经选中的元素$("input:checkbox[name='selectBook']:checked").each(function () {ids.push($(this).val());});console.log(ids);$.ajax({url: "/book/batchDeleteBook?ids=" + ids,type: "post",success: function (result) {if (result.code == "SUCCESS" && result.data == "") {//删除成功location.href = "book_list.html";} else {alert(result.data);}},error: function (error) {//用户未登录if (error.data == null && error.data=="NOLOGIN") {location.href = "login.html";}}});// alert("批量删除成功");}} 前端将url和type发送给服务器。
📝实现服务器代码
Integer batchDeleteBookByIds(List<Integer> ids); 批量删除,我们需要将选中的id对应的图书都删除掉。用到动态sql语句
<delete id="batchDeleteBookByIds">update book_info set status=0where id in<foreach collection="ids" open="(" close=")" item="id" separator=",">#{id}</foreach></delete> 🎓业务层
/*** 批量删除*/public Integer batchDeleteBookByIds(List<Integer> ids){return bookInfoMapper.batchDeleteBookByIds(ids);} 🎓控制层
/*** 批量删除 ids*/@RequestMapping(value = "/batchDeleteBook", produces = "application/json")public Result<String> batchDelete(@RequestParam List<Integer> ids, HttpSession session) {Result<String>ans=new Result<>();//效验参数是否登录UserInfo loginUserInfo = (UserInfo) session.getAttribute(constants.USER_SESSION_KEY);if (loginUserInfo==null || loginUserInfo.getId()<=0){ans.setCode(StatusResult.NOLOGIN);ans.setData(null);return ans;}Integer result= bookInfoService.batchDeleteBookByIds(ids);if(result>0){ans.setData("");ans.setCode(StatusResult.SUCCESS);return ans;}ans.setData("批量删除失败");ans.setCode(StatusResult.FAIL);return ans;} 📝测试前后端代码是否正确
先测后端代码,首先是未登录状态


检查前端代码:



此时批量删除成功!

永远有优秀的代码,永远有提升的空间,比如,我们次实现都要调用一个接口,是不是很复杂,如果有更多的功能接口,那么就会更复杂。我们想想会有什么方法来优化呢?后续会有统一功能等等,让这个项目做进一步的优化。
披星戴月走过的路必将繁华似锦!
相关文章:
【JavaEE进阶】——利用框架完成功能全面的图书管理系统
目录 🚩项目所需要的技术栈 🚩项目准备工作 🎈环境准备 🎈数据库准备 🚩前后端交互分析 🎈登录 📝前后端交互 📝实现服务器代码 📝测试前后端代码是否正确 &am…...
WDF驱动开发-内存缓冲区
驱动程序通常使用内存缓冲区向/从框架和其他驱动程序传递数据,或在本地存储信息。 WDF常见的内存缓冲区包括框架内存对象(WDFMEMORY)、 lookaside、 MDL 和 本地缓冲区。 使用框架内存对象 框架使用 内存对象 来描述驱动程序从中接收并传递给框架的内存缓冲区。 每…...
c语言连接两个字符串
在C语言中,连接两个字符串可以使用 strcat 函数。这个函数将一个字符串复制到另一个字符串的末尾。使用 strcat 函数之前,需要确保目标字符串有足够的空间来容纳源字符串,否则可能会导致缓冲区溢出。 下面是一个使用 strcat 函数连接两个字符…...
基于springboot的大学计算机基础网络教学系统
文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于springboot的大学计算机基础网络教学…...
UOS常用命令
shutdown 关机 reboot 重启 reboot -f 强制重启 history 查看使用的历史命令 history -c 清空命令行常见目录结构 /bin 存储常用用户指令 /boot 存放用于系统引导时使用的各种文件 /dev 存放设备文件 /etc 存放系统,服务的配置…...
vue3 如何给表单添加表单效验+正则表达式
校验要求 我们的表单中有密码、电话号码 ,两项。 我们设置用密码为3到20位的非空字符 电话号码就用目前用的电话号码正则表达式,要求手机号码以 1 开头,第二位为 3 到 9 之间的数字,后面跟着任意 9 个数字,总共是 11…...
JavaScript算法实现dfs查找省市区路径
需求 存在如下数组,实现一个算法通过输入区名,返回省->市->区格式的路径,例如输入西湖区,返回浙江省->杭州市->西湖区。 // 定义省市区的嵌套数组 const data [{name: "浙江省",children: [{name: "…...
map文件分析
以下是一个具体的map文件示例,并附上详细的描述,帮助你更好地理解如何读取和分析map文件: 示例map文件 Memory ConfigurationName Origin Length Attributes FLASH 0x08000000 0x0…...
MySQL-创建表~数据类型
070-创建表 create table t_user(no int,name varchar(20),gender char(1) default 男);071-插入数据 语法格式: insert into 表名(字段名1, 字段名2, 字段名3,......) values (值1,值2,值3,......);insert into t_user(no, name, gender) values(1, Cupid, 男);字…...
【鸿蒙 HarmonyOS】Swiper组件
一、背景 项目中通常会遇到图片轮播,内容轮播的场景;如:在一些应用首页显示推荐的内容时,需要用到轮播显示的能力。 二、源码地址 ✍Gitee开源项目地址👉:https://gitee.com/cheinlu/harmony-os-next-swi…...
玩具机器人脚本适合场景
玩具机器人脚本作为一个模拟的玩具机器人脚本,适合以下场合: 1.教育和学习:对于初学者和编程爱好者来说,这个脚本是一个很好的学习工具,可以帮助他们理解如何编写和执行简单的控制逻辑。 2.在计算机科学、机器人技术或…...
人工智能模型组合学习的理论和实验实践
组合学习,即掌握将基本概念结合起来构建更复杂概念的能力,对人类认知至关重要,特别是在人类语言理解和视觉感知方面。这一概念与在未观察到的情况下推广的能力紧密相关。尽管它在智能中扮演着核心角色,但缺乏系统化的理论及实验研…...
MySQL备份与恢复:确保数据的安全与可靠性
引言: 数据的安全性和可靠性的重要性 在现代企业和组织中,数据已经成为了最重要的资产之一。数据的安全性和可靠性对于企业的运营至关重要。首先,数据的安全性保证了敏感信息不会落入错误的手中,防止了潜在的经济损失和法律风险。其次,数据的可靠性则确保了企业能够准确…...
Noisee AI – AI音乐影片MV在线生成工具,专门为Suno的好搭子来了~
导读 现在很多各大平台,抖音、快手、微视,还不能直接发布音频文件,如果有一个好听的音乐想做成MV,怎么办呢? 这时候就是Noisee AI的主场,上传一段音乐加上简单的描述就可以在3-5分钟内生成一个可以发布到…...
实战计算机网络02——物理层
实战计算机网络02——物理层 1、物理层实现的功能2、数据与信号2.1 数据通信模型2.2 通信领域常用术语2.3 模拟信号和数字信号 3、信道和调制3.1 信道3.2 单工通信、半双工通信、全双工通信3.3 调制3.4 奈式准则3.5 香农定律 4、传输媒体4.1 导向传输媒体4.2 非导向传输媒体 5、…...
Doris:冷热分层
目录 一、冷热分层介绍 二、存储策略(Storage policy) 2.1 创建存储资源 2.2 创建存储策略 2.3 使用存储策略 三、使用限制 一、冷热分层介绍 冷热分层支持所有 Doris 功能,只是把部分数据放到对象存储上,以节省成本&am…...
28.启动与暂停程序
上一个内容:27.设计注入功能界面 以它 27.设计注入功能界面 的代码为基础进行修改 点击添加游戏按钮之后就把游戏启动了 CWndINJ.cpp文件中修改: void CWndINJ::OnBnClickedButton1() {// TODO: 在此添加控件通知处理程序代码/*ExeLst.InsertItem(0, L…...
404 页面代码
<template> <div class"container"><h1>404</h1> <div ><p class"text-center">当前页面无法访问,可能没有权限或已删除</p><p class"text-center"> 去别处看看吧</p> </div> <…...
java设计模式和面向对象编程思想
Java设计模式和面向对象编程思想是软件开发中的核心概念,对于构建可维护、可扩展的软件系统至关重要。下面是对这两个主题的知识点总结: 面向对象编程(OOP)思想 封装:将数据(属性)和操作这些数据…...
超万卡训练集群网络互联技术解读
超万卡训练集群互联关键技术 大模型迈向万亿参数的多模态升级,万卡集群计算能力亟需飞跃。关键在于增强单芯片性能、提升超节点算力、融合DPU多计算能力,并追求算力能效比极致。这一系列提升将强有力支撑更大规模模型训练和推理,快速响应业务…...
UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...
基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...
PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...
(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...
今日科技热点速览
🔥 今日科技热点速览 🎮 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售,主打更强图形性能与沉浸式体验,支持多模态交互,受到全球玩家热捧 。 🤖 人工智能持续突破 DeepSeek-R1&…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...
七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...




