图书管理系统(Mybatis)
目录
系统开发的步骤
1.需求确认阶段:需求分析,需求评审
2.开发
1)方案设计
2)接口定义
3)开发业务代码
4)测试(自测+联调)
3.提测阶段
4.上线(发布阶段)
数据库设计:实体表(用户表、博客表、图书表)、关系表(一对多、多对多)
数据库相关
创建数据库与表
-- 创建数据库
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,'民主与建设出版社');
引入Mybatis和MySQL依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
数据库连接
# 数据库连接配置
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/book_test?characterEncoding=utf8&useSSL=false
username: root #数据库用户名
password: password #密码
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration:
map-underscore-to-camel-case: true #配置驼峰⾃动转换
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印sql语句
# 设置⽇志⽂件的⽂件名
logging:
file:
name: spring-book.log
后端
用户相关
UserController
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
public UserService userService;
@RequestMapping("/login")
public Boolean login(String userName, String password, HttpSession session){
//校验参数
if(!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)) {
return false;
}
//if(userName.equals("admin")){}这种写法会报空指针异常0
//校验账号密码是否正确
//1.根据用户名查找用户信息
UserInfo userInfo=userService.getUserInfoByName(userName);
//比对密码是否正确
if(userInfo==null||userInfo.getId()<=0){
return false;
}
if(password.equals(userInfo.getPassword())){
//账号密码正确
//存session
userInfo.setPassword("");
session.setAttribute("userName",userInfo);
return true;
}
return false;
}
}
图书相关
添加翻页功能
第一次请求的时候,请求第一页的内容,后端只需要返回第一页的信息即可。翻页可以由后端完成,也可以有前端完成。
前端完成需要一次性拿到所有的数据。
缺点:1)第一次请求响应时间长
2)数据如果要进行修改,前端无法感知
优点:后续响应快
后端完成,可以避免前端的缺点,但是需要多次请求。(本系统推荐后端,因为数据存在多次修改)
请求第一页的内容,后端只需要返回第一页的信息即可,请求第二页的内容,后端只需要返回第二页的内容。
select * from book_info limit ${offset},${limit}
在图书页添加一些数据以便展示
INSERT INTO `book_info` ( book_name, author, count, price, publish )
VALUES
( '图书2', '作者2', 29, 22.00, '出版社2' ),( '图书3', '作者2', 29, 22.00, '出版社3'),
( '图书4', '作者2', 29, 22.00, '出版社1' ),( '图书5', '作者2', 29, 22.00, '出版社1'),
( '图书6', '作者2', 29, 22.00, '出版社1' ),( '图书7', '作者2', 29, 22.00, '出版社1'),
( '图书8', '作者2', 29, 22.00, '出版社1' ),( '图书9', '作者2', 29, 22.00, '出版社1'),
( '图书10', '作者2', 29, 22.00, '出版社1'),( '图书11', '作者2', 29, 22.00, '出版社1'),
( '图书12', '作者2', 29, 22.00, '出版社1'),( '图书13', '作者2', 29, 22.00, '出版社1'),
( '图书14', '作者2', 29, 22.00, '出版社1'),( '图书15', '作者2', 29, 22.00, '出版社1'),
( '图书16', '作者2', 29, 22.00, '出版社1'),( '图书17', '作者2', 29, 22.00, '出版社1'),
( '图书18', '作者2', 29, 22.00, '出版社1'),( '图书19', '作者2', 29, 22.00, '出版社1'),
( '图书20', '作者2', 29, 22.00, '出版社1'),( '图书21', '作者2', 29, 22.00, '出版社1');
一共24条数据,设计一页展示10条,共三页。第一页返回1-10,第二页返回11-20,第三页返回21-24
通过MySQL语句实现
select * from book_info limit 0,10;
select * from book_info limit 10,10;
select * from book_info limit 20,4;
后端翻页select * from book_info limit ${offset},${limit}
limit就是每一页要显示的条数pageSize
前端需要告知的内容:
1.当前页:currentPage
2.每页显示的条数
后端返回结果:
1.当前页的内容:records
2.后端总条数
分页插件jqPaginator
jqPaginator分页组件https://jqpaginator.keenwon.com/
$('#id').jqPaginator({
totalPages: 100,
visiblePages: 10,
currentPage: 1,
onPageChange: function (num, type) {
$('#text').html('当前第' + num + '页');
}
});
前端: 引入此插件的使用:
设置图书状态(枚举)
设置价格精度
添加图书
接口定义
/book/addBook
参数:BookInfo
返回结果:告诉前端是否添加成功
1)Boolean true:添加成功 false:添加失败
2)String " " 添加成功,不为空添加失败
3)对象 Boolean result 是否添加成功
String errorMsg 出错时候的错误原因
这里采用第二种方式:返回字符串
前后端实现
BookInfoMapper.java
@Insert("insert into book_info (book_name,author,count,price,publish,status)" +
"values(#{bookName},#{author},#{count},#{price},#{publish},#{status})")
Integer insertBook(BookInfo bookInfo);
BookController.java
@RequestMapping("/addBook")
public String addBook(BookInfo bookInfo) {
log.info("接收到添加图书请求,bookInfo:{}",bookInfo);
//参数校验
if (!StringUtils.hasLength(bookInfo.getBookName())
|| !StringUtils.hasLength(bookInfo.getAuthor())
|| bookInfo.getCount() < 0
|| bookInfo.getPrice() == null
|| !StringUtils.hasLength(bookInfo.getPublish())){
return "参数校验失败,请检查输入";
}
Integer result=bookService.addBook(bookInfo);
if(result<0){
log.error("添加图书出错:bookInfo:{}",bookInfo);
return "添加图书出错,请联系管理员";
}
return "";
}
BookService.java
//添加图书
public Integer addBook(BookInfo bookInfo){
Integer result=0;
try{
bookInfoMapper.insertBook(bookInfo);
}catch (Exception e){
log.error("添加图书出错,e:{}",e);
}
return result;
}
前端book_add.html前后端交互部分
<script>
function add() {
$.ajax({
type:"post",
url:"/book/addBook",
data:$("#addBook").serialize(),//提交整个form表单
success:function(result){
if(result==""){
//图书添加成功
location.href = "book_list.html";
}else{
alert(result);
}
}
});
}
</script>
测试前后端
后端
测试添加图书,使用postman测试:
每次测试前端页面时,记得进入页面看前端代码是否已经修改成最新的。如果没有则按F12再右键网页刷新按键。
再次确认页面代码是否为修改的最新版本。
Fiddlere抓包测试
在线解码工具查看发送的url内容是否正确
修改图书
1.点击修改按钮时,希望把当前图书的信息显示出来
2.点击确定时把修改后的结果进行保存
接口定义
1.查询图书信息,根据id
/book/queryBookInfoById
参数:bookId
返回结果:对应的图书信息 BookInfo
2.修改图书
/book/updateBook
参数:BookInfo
返回结果:是否修改成功(String " "空为成功。不为空,表示失败,返回错误信息。)
后端代码及测试
BookController.java
@RequestMapping("/updateBook")
public String updateBook(BookInfo bookInfo){
log.info("更新图书,接受到更新图书的请求,bookInfo:{}",bookInfo);
Integer result=bookService.updateBook(bookInfo);
if(result==0){
log.error("更新图书失败,请联系管理员");
return "更新图书失败,请联系管理员";
}
return "";
}
BookService.java
public Integer updateBook(BookInfo bookInfo){
Integer result=0;
try {
result=bookInfoMapper.updateBook(bookInfo);
}catch (Exception e){
log.error("更新图书失败,e:{}",e);
}
return result;
}
BookInfoMapper.java
Integer updateBook(BookInfo bookInfo);
使用xml配置文件动态操作数据库
application.properties文件中添加配置
mybatis.mapper-locations= classpath:mapper/**Mapper.xml
resource下建立文件目录结构
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.SpringBookaliyun.mapper.BookInfoMapper">
<!--namespace是对应要操作的Mapper类下的包的路径+该Mapper类-->
</mapper>
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.SpringBookaliyun.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!=null">
publish=#{publish},
</if>
<if test="status!=null">
status=#{status}
</if>
</set>
where id=#{id}
</update>
</mapper>
测试:postman
前端代码及测试
前后端交互部分
<script>
$.ajax({
type:"get",
url:"/book/queryBookInfoById"+location.search,
success:function(book){
if(book!=null){
//页面输入框的填充
$("#bookId").val(book.id);
$("#bookName").val(book.bookName);
$("#bookAuthor").val(book.author);
$("#bookStock").val(book.count);
$("#bookPrice").val(book.price);
$("#bookPublisher").val(book.publish);
$("#bookStatus").val(book.status);
}else{
alert("图书不存在")
}
}
})
function update() {
$.ajax({
type:"post",
url:"book/updateBook",
data:$("#updateBook").serialize(),
success:function(result){
if(result==""){
location.href="book_list.html";
}else{
alert(result);
}
}
});
}
删除图书
删除的几种方式:
1.物理删除:直接把数据删掉 delete
2.逻辑删除:软删除(假),通过字段标识,标识这个数据被删除掉了
3.物理删除+存档(逻辑删除+存档):存档表中的数据不会消失
实际开发中,删除选择逻辑删除,这里采用比较简单的逻辑删除
接口定义
当采用物理删除时
/book/deleteBook
参数:bookId
返回内容:" "空表示删除成功,不为空表示删除失败,并返回失败的原因。
逻辑删除
接口定义发生变化
book/updateBook
参数:
bookId=
status=0
返回:和updateBook保持一样
" "表示删除成功。不为空则表示删除失败,并返回失败原因
实现
使用update实现删除的逻辑思路:
后端其实已经实现了,就是update的代码,现在只需要完成前端的代码即可。
function deleteBook(bookId) {
var isDelete = confirm("确认删除?");
if (isDelete) {
//删除图书
$.ajax({
type:"post",
url:"/book/updateBook",
data:{
id:bookId,
status:0
},
success:function(result){
if(result==""){
//删除成功
location.href="book_list.html";
}else{
alert(result);
}
}
});
}
}
批量删除
因为采用逻辑删除,所以批量删除就是批量更新
ids:需要批量删除的图书的集合
接口定义:
book/batchDelete
参数:List<Integer> ids
返回结果:" "表示删除成功。不为空则表示删除失败,并返回失败原因
实现
后端
BookController.java
@RequestMapping("/batchDelete")
public String batchDelete(@RequestParam List<Integer> ids){
log.info("接收请求,批量删除图书,图书ID:{}",ids);
Integer result= bookService.batchDelete(ids);
if(result<=0){
log.error("批量删除失败,ids:{}",ids);
return "批量删除失败,请联系管理员";
}
return "";
}
BookService.java
public Integer batchDelete(List<Integer> ids){
Integer result=0;
try{
result=bookInfoMapper.batchDelete(ids);
}catch (Exception e){
log.error("批量删除图书失败,ids:{}",ids);
}
return result;
}
BookInfoMapper.java
Integer batchDelete(List<Integer> ids);
BookInfoMapper.xml
<update id="batchDelete">
update book_info
set status=0
where id in
<!-- (1,2,3,4)-->
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</update>
前端:
function batchDelete() {
var isDelete = confirm("确认批量删除?");
if (isDelete) {
//获取复选框的id
var ids = [];
$("input:checkbox[name='selectBook']:checked").each(function () {
ids.push($(this).val());
});
console.log(ids);
$.ajax({
type:"post",
url:"book/batchDelete?ids="+ids,
success:function(result){
if(result==""){
//删除成功
location.href="book_list.html";
}else{
alert(result);
}
}
});
强制登录
为了让前端可以看见执行的状态,未登录、登录成功、登录失败。在controller层内写上各自的执行状态,代码会有许多重复的部分。因此我们可以把相同的部分提取成一个类。
@Data
public class Result {
//业务状态码
private ResultCode code;//0-成功 -1失败 -2未登录
//错误信息
private String errMsg;
//数据
private Object data;
public static Result success(Object data){
Result result=new Result();
result.setCode(ResultCode.SUCCESS);
result.setErrMsg("");
result.setData(data);
return result;
}
public static Result fail(String errMsg ){
Result result=new Result();
result.setCode(ResultCode.FAIL);
result.setErrMsg(errMsg);
result.setData(null);
return result;
}
public static Result fail(String errMsg,Object data ){
Result result=new Result();
result.setCode(ResultCode.FAIL);
result.setErrMsg(errMsg);
result.setData(data);
return result;
}
public static Result unLogin(){
Result result=new Result();
result.setCode(ResultCode.UNLOGIN);
result.setErrMsg("用户未登录");
result.setData(null);
return result;
}
}
同时修改controller中的代码
@Slf4j
@RequestMapping("/book")
@RestController
public class BookController {
@Autowired
private BookService bookService;
@RequestMapping("/getBookListByPage")
public Result getBookListByPage(PageRequest pageRequest, HttpSession session){
//用户登录校验
UserInfo userInfo=(UserInfo) session.getAttribute(Constants.SESSION_USER_KEY);
if(userInfo==null||userInfo.getId()<=0||"".equals(userInfo.getUserName())){
//用户未登录
return Result.unLogin();
}
//校验成功
log.info("查询翻页信息,pageRequest:{}",pageRequest);
//校验失败
if(pageRequest.getPageSize()<0 || pageRequest.getCurrentPage()<1){
return Result.fail("参数校验失败");
}
PageResult<BookInfo> bookInfoPageResult=null;
try {
bookInfoPageResult= bookService.selectBookInfoByPage(pageRequest);
return Result.success(bookInfoPageResult);
}catch (Exception e){
log.error("查询翻页信息错误,e:{}",e);
return Result.fail(e.getMessage());
}
}
@RequestMapping("/addBook")
public String addBook(BookInfo bookInfo) {
log.info("接收到添加图书请求,bookInfo:{}",bookInfo);
//参数校验
if (!StringUtils.hasLength(bookInfo.getBookName())
|| !StringUtils.hasLength(bookInfo.getAuthor())
|| bookInfo.getCount() < 0
|| bookInfo.getPrice() == null
|| !StringUtils.hasLength(bookInfo.getPublish())){
return "参数校验失败,请检查输入";
}
Integer result=bookService.addBook(bookInfo);
if(result<0){
log.error("添加图书出错:bookInfo:{}",bookInfo);
return "添加图书出错,请联系管理员";
}
return "";
}
@RequestMapping("/queryBookInfoById")
public BookInfo queryBookInfoById(Integer bookId){
log.info("根据ID查询图书,bookId:"+bookId);
try {
BookInfo bookInfo=bookService.queryBookInfoById(bookId);
return bookInfo;
}catch (Exception e){
log.error("查询图书失败,e:{}",e);
}
return null;
}
@RequestMapping("/updateBook")
public String updateBook(BookInfo bookInfo){
log.info("更新图书,接受到更新图书的请求,bookInfo:{}",bookInfo);
Integer result=bookService.updateBook(bookInfo);
if(result==0){
log.error("更新图书失败,请联系管理员");
return "更新图书失败,请联系管理员";
}
return "";
}
@RequestMapping("/batchDelete")
public String batchDelete(@RequestParam List<Integer> ids){
log.info("接收请求,批量删除图书,图书ID:{}",ids);
Integer result= bookService.batchDelete(ids);
if(result<=0){
log.error("批量删除失败,ids:{}",ids);
return "批量删除失败,请联系管理员";
}
return "";
}
}
实现展示
登录页
图书列表页
修改图书
添加图书
删除图书
批量删除图书
原文地址:https://blog.csdn.net/weixin_67793092/article/details/135286159
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!