【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);
 Model创建   BookInfo UserInfo
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);
}
访问数据库, 使⽤MyBatis来实现, 所以把之前dao路径下的⽂件可以删掉, ⽤mapper⽬录来代替, 创
建UserInfoMapper ,当然, 继续使⽤dao⽬录也可以,此处为建议 ,dao和mapper通常都被认为是数据库层

🎓业务层

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);}
}

🎓控制层

我们需要进行前后端交互
我们需要创建一个类Result,里面包含errMsg(显示什么导致错误的信息),code(业务码), data(服务器返回给前端的数据)。
而code业务码,是需要通过键值对的形式创建,比如我们设定200标识succss,-1表示fail,-2表示未登录功能,我们需要创建一个一个枚举类。
创建一个枚举类StatusResult,枚举三种状态。
🍭Result类
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;}
}
🍭StatusResult枚举类
枚举类不能使用@Data注解
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中的数据是否是未登录页码


📝实现服务器代码

插入图书,还是之前的三层架构方式,再mapper层中,我们要插入book_name, author, `count`, price, publish, `status`,这几个字段,因为创建时间和更新时间是以当时时间为准的,id是自增的。
🎓数据层
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可以让我们再运行的时候,控制台会进行报出日志消息。

在后面我们是对图书进行增删改查的功能,这些功能的前提是我们需要用户必须是登录的,所以我们在效验参数之前,我们需要进行判断是否用户登录,如果没有登录返回error信息,设置状态码为NOLOGIN,并且错误信息就是“用户未登录”。
public static<T> Result nologin(String errMsg){Result result=new Result();result.setErrMsg(errMsg);result.setCode(StatusResult.NOLOGIN);return result;}
如果用户登录了,就按照正常的流程进行添加图书,由于添加图书的返回值是int类型,如果添加图书的数量是>0的我们就表示添加成功,然后我们返回success成功码,如果没有返回成功,就会报错。
   /*** 增加图书*/@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;}

📝测试前后端代码是否正确

我们先看用户登录状态,首先用postman进行发出登录请求
然后通过postman进行发出添加图书请求,返回SUCCESS状态码

我们再来看用户未登录状态:返回的错误信息“errMsg”,code为LOGIN


检查前端代码

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


🎈图书列表

可以看到, 添加图书之后, 跳转到图书列表⻚⾯, 并没有显⽰刚才添加的图书信息, 接下来我们来实现图书列表。
需求分析
我们之前做的表⽩墙查询功能,是将数据库中所有的数据查询出来并展⽰到⻚⾯上,试想如果数据库中的数据有很多(假设有⼗⼏万条)的时候,将数据全部展⽰出来肯定不现实,那如何解决这个问题呢?
使⽤分⻚解决这个问题。每次只展⽰⼀⻚的数据,⽐如:⼀⻚展⽰10条数据,如果还想看其他的数
据,可以通过点击⻚码进⾏查询

分⻚时, 数据是如何展⽰的呢
第1⻚: 显⽰1-10 条的数据
第2⻚: 显⽰11-20 条的数据
第3⻚: 显⽰21-30 条的数据
以此类推...
要想实现这个功能, 从数据库中进⾏分⻚查询,我们要使⽤ LIMIT 关键字,格式为:
limit 开始索引每⻚显⽰的条数(开始索引从0开始)

我们先增加一些数据,让每一页都更加的明显显示出来。

第一页的开始索引是0  (1-0)*10

第二页的开始索引是10,(2-1)*10

观察以上SQL语句,发现: 开始索引⼀直在改变, 每⻚显⽰条数是固定的
开始索引的计算公式: 开始索引 = (当前⻚码 - 1) * 每⻚显⽰条数

我们继续基于前端⻚⾯, 继续分析, 得出以下结论:
前端发出请求,需要向服务器传递参数
  • 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=10
Content-Type: application/x-www-form-urlencoded; charset=UTF-8

type:  get

参数:

响应:
{"errMsg":"","data":"","code":""}
我们约定, 浏览器给服务器发送⼀个 /book/getListByPage 这样的 HTTP 请求, 通过
currentPage 参数告诉服务器, 当前请求为第⼏⻚的数据, 后端根据请求参数, 返回对应⻚的数据
第⼀⻚可以不传参数, currentPage默认值为 1

📝实现服务器代码

🎓数据层

  /*** 获取当前页数据*/@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();//统计未借阅的书的本数

🎓业务层

1. 翻⻚信息需要返回数据的总数和列表信息, 需要查两次SQL
2. 图书状态: 图书状态和数据库存储的status有⼀定的对应关系 ,如果后续状态码有变动, 我们需要修改项⽬中所有涉及的代码, 这种情况, 通常采⽤枚举类来处理映射关系
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类型的。


📝测试前后端代码是否正确

先测后端
一共有32个书没有被借阅。
前端部分需要给博客列表做出来之后即可看到。


🎈修改图书

📝前后端交互

进⼊修改⻚⾯, 需要显⽰当前图书的信息
[请求]
/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, 获取当前图书的信息
点击修改按钮, 修改图书信息
[ 请求 ]
/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表单的形式来
提交数据 ,服务器返回处理结果, 返回""表⽰添加图书成功, 否则, 返回失败信息.

📝实现服务器代码 

🎓数据层
数据层我们需要先寻找该书通过id,因为当我们再点击修改的时候,我们就可以获取到该书的id号,然后再对该书进行更新。

    /*** 修改图书*///通过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/deleteBook
Content-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);}
我们需要给定id,并且给状态设置为0,然后进行通过id进行修改即可。

🎓控制层

 /*** 删除图书*/@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;}

控制层返回的数据是返回给客户端代码中。


📝测试前后端代码是否正确

先测后端代码,首先是未登录状态

登录状态:
通过id删除图书:

检查前端代码:

此时删除成功,返回的列表页。


🎈批量删除图书

📝约定前后端交互接⼝

/book/batchDeleteBook
[ 请求 ]
/book/batchDeleteBook
Content-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;}

📝测试前后端代码是否正确

先测后端代码,首先是未登录状态

登录状态:
通过ids批量删除图书:


检查前端代码:

此时批量删除成功!


永远有优秀的代码,永远有提升的空间,比如,我们次实现都要调用一个接口,是不是很复杂,如果有更多的功能接口,那么就会更复杂。我们想想会有什么方法来优化呢?后续会有统一功能等等,让这个项目做进一步的优化。


披星戴月走过的路必将繁华似锦!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/28144.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一些硬件知识(十)

MOS管当开关控制时&#xff0c;一般用PMOS做上管&#xff0c;NMOS做下管 细说MOS管知识-MOS管高端驱动与低端驱动解析和原理及区别_高端功率 mos 管是什么意思-CSDN博客 PMOS管&#xff1a; PMOS管的源极&#xff08;Source&#xff09;通常连接到正电源&#xff08;Vcc&#…

西门子学习笔记15 - 位逻辑操作的学习

1、点动操作&#xff08;按下按钮就启动松开就停止&#xff09; 2、自锁电路&#xff08;可以自己保持的状态除非常闭停止按下&#xff09; 3、取反操作&#xff08;顾名思义就是反过来1就变成0&#xff0c;0就变成1&#xff09; 4、置为复位&#xff08;置位之后如果不复位的话…

SpringBoot自定义Starter及原理分析

目录 1.前言2.环境3.准备Starter项目4.准备AutoConfigure项目4.1 准备类HelloProperties4.2 准备类HelloService4.3 准备类HelloServiceAutoConfiguration4.4 创建spring.factories文件并引用配置类HelloServiceAutoConfiguration4.5 安装到maven仓库 5.在其他项目中引入自定义…

困惑度作为nlp指标的理解示例

为了更清晰地说明困惑度的计算过程以及如何通过困惑度判断模型的优劣&#xff0c;我们可以通过一个简单的例子来演示。假设我们有一个非常简单的文本语料库和两个基础的语言模型进行比较。 示例文本 假设我们的文本数据包括以下两个句子&#xff1a; “cat sits on the mat”…

计算机网络:网络层 - 路由选择协议

计算机网络&#xff1a;网络层 - 路由选择协议 路由器的结构路由选择协议概述自治系统 AS内部网关协议路由信息协议 RIP距离向量算法RIP报文格式收敛问题 开放最短路径优先 OSPF基本工作原理自治系统分区 外部网关协议BGP-4 路由器的结构 如图所示&#xff0c;路由器被分为路由…

【项目实战】如何写一个操作系统?

【项目实战】如何写一个操作系统? 目录 【项目实战】如何写一个操作系统?前言下载32位的lucid系统解决旧版系统下载源失效的问题利用共享文件夹将下载的.deb文件上传系统中如何解决下载了g但是不能使用的问题&#xff1f;编译系统源文件配置grub和qemu项目拓展 作者&#xff…

证照之星 XE版软件怎么下载安装? 【详细安装图文教程】

软件简介&#xff1a; 证照之星是国内顶级的证件照片制作软件&#xff0c;具有一键裁剪&#xff0c; 智能背景替换&#xff0c;批量制作、内置证照规格的四大优势。同时两大独创技术&#xff1a;智能去除皮肤油光、证照服装替换。同时支持联机拍摄&#xff1a;支持网络摄像头及…

【软件工程】【23.04】p2

关键字&#xff1a; 计算机软件定义、需求基本性质、创建系统类图所涉及的工作、RUP创建系统用况模型活动、软件生存周期模型、能力等级和成熟度等级区别联系&#xff1b; 模块结构图&#xff1a;深度宽度、扇入扇出、作用域、控制域&#xff1b; 程序流程图&#xff1a;语句…

吉他谱反复记号有哪些 Guitar Pro如何加吩咐标记 吉他初学者入门教程

吉他谱中的反复记号是指用来指示音乐重复部分的符号&#xff0c;对于吉他演奏者来说&#xff0c;了解这些符号的含义和使用方法非常重要。下面我们来看看吉他谱反复记号有哪些&#xff0c;Guitar Pro 如何加吩咐标记的相关内容。 一、吉他谱反复记号有哪些 1.双线反复记号&am…

大数据与人工智能在保险行业数字化转型中的应用

随着科技的快速发展&#xff0c;大数据和人工智能&#xff08;AI&#xff09;技术在保险行业中扮演着越来越重要的角色&#xff0c;推动了保险行业的数字化转型。通过收集和分析海量的用户数据&#xff0c;利用先进的人工智能算法&#xff0c;保险公司能够更准确地评估风险&…

数据交换平台_10_activatemq 中间件容错性测试

目录概要 3. 容错测试: - 模拟ActiveMQ在异常情况下的表现,如网络中断、节点故障等。 - 观察ActiveMQ的容错机制是否能够正确处理异常情况,保证消息的可靠传输。 - 根据容错测试结果,优化ActiveMQ的容错机制,确保系统在面对异常情况时能够正确处理并恢复。 设计: 容错测…

操作系统—页表(实验)

文章目录 页表1.实验目标2.实验过程记录(1).增加打印页表函数(2).独立内核页表(3).简化软件模拟地址翻译 3.实验问题及相应解答问题1问题2问题3问题4 实验小结 页表 1.实验目标 了解xv6内核当中页表的实现原理&#xff0c;修改页表&#xff0c;使内核更方便地进行用户虚拟地址…

华媒舍:明星祝福视频,为你送上最真挚的祝福!

引言&#xff1a;嗨&#xff0c;亲爱哒书友&#xff01;在这样一个科谱详细介绍文中&#xff0c;我们将带你领略一份尤其的独家合辑——十部明星祝愿视频。这种视频汇聚了诸多明星为你送上的最真挚的祝福。让我们一起来探寻这种电影中蕴含的情绪和价值吧&#xff01; 1.共享温暖…

【JS重点16】对象原型

目录 一&#xff1a;对象原型是什么 二&#xff1a;对象原型作用 三&#xff1a;constructor属性 四&#xff1a;如何赚钱 一&#xff1a;对象原型是什么 每个对象都有一个属性__proto__(称为原型对象),该属性是一个对象 __proto__是JS非标准属性在实例对象中&#xff0c;…

MongoDB~高可用集群介绍:复制集群(副本集)、分片集群

背景 MongoDB 的集群主要包括副本集&#xff08;Replica Set&#xff09;和分片集群&#xff08;Sharded Cluster&#xff09;两种类型。 副本集 组成&#xff1a;通常由一个主节点&#xff08;Primary&#xff09;和多个从节点&#xff08;Secondary&#xff09;构成。 功…

Linux 按键输入实验

Linux 按键输入实验 1、添加 pinctrl 节点 首先修改在设备树里面添加关于按键的节点。I.MX6U-ALPHA 开发板上的 KEY 使用了 UART1_CTS_B 这个 PIN&#xff0c;打开 imx6ull-alientekemmc.dts&#xff0c;在 iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“pinctrl_key”的子…

深度学习 --- stanford cs231 编程作业(assignment1,Q3: softmax classifier)

stanford cs231 编程作业(assignment1&#xff0c;Q3: softmax classifier softmax classifier和svm classifier的assignment绝大多部分都是重复的&#xff0c;这里只捡几个重点。 1&#xff0c;softmax_loss_naive函数&#xff0c;尤其是dW部分 1&#xff0c;1 正向传递 第i张…

力扣爆刷第151天之TOP100五连刷(回文子串、DFS、旋转数组二分查找)

力扣爆刷第151天之TOP100五连刷&#xff08;回文子串、DFS、旋转数组二分查找&#xff09; 文章目录 力扣爆刷第151天之TOP100五连刷&#xff08;回文子串、DFS、旋转数组二分查找&#xff09;一、5. 最长回文子串二、102. 二叉树的层序遍历三、33. 搜索旋转排序数组四、200. 岛…

JS 实现Date日期格式的本地化

为了更好的更新多语言日期的显示&#xff0c;所以希望实现日期的本地化格式显示要求&#xff0c;常规的特殊字符型格式化无法满足显示要求&#xff0c;这里整理了几种我思考实现的本地化实现功能。 通过多方查找&#xff0c;总结了实现的思路主要有如下三个方向&#xff1a; 官…

【鸿蒙 HarmonyOS】Swiper组件

一、背景 项目中通常会遇到图片轮播&#xff0c;内容轮播的场景&#xff1b;如&#xff1a;在一些应用首页显示推荐的内容时&#xff0c;需要用到轮播显示的能力。 二、源码地址 ✍Gitee开源项目地址&#x1f449;&#xff1a;https://gitee.com/cheinlu/harmony-os-next-swi…