基于my Batis优化图书管理系统(总)

news2024/11/15 20:11:14

1.准备工作

1.1  数据库表设计

-- 创建数据库
DROP DATABASE IF EXISTS book_manage;

CREATE DATABASE book_manage DEFAULT CHARACTER SET utf8mb4;
use book_manage;

-- 用户表
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,
--                              decimal双精度浮点数数据类型,7表示数据的长度为7位,2表示小数点的位数,表示价格的数据类型为xxxx.xx
                             `publish` VARCHAR ( 256 ) NOT NULL,
                             `status` TINYINT ( 4 ) DEFAULT 1 COMMENT '0-无效, 1-正常, 2-不允许借阅',
--                             1 tinyint是一个字节,类似于1byte
                             `create_time` DATETIME DEFAULT now(),
--                              datatime是日期类型
                             `update_time` DATETIME DEFAULT now() ON UPDATE now(),
--                              on update指的是更新时间,即将之前的时间更新为当前时间
                             PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;
-- 1.ENGINE=InnoDB使用innodb引擎,从zhidaoMySQL 5.6开始默认使用该引擎
-- 2.DEFAULT CHARSET=utf8 数据库默认编码为utf-8

-- 初始化数据
INSERT INTO user_info ( user_name, PASSWORD ) VALUES ( "shenmengyao", "111111" );
INSERT INTO user_info ( user_name, PASSWORD ) VALUES ( "yuanyiqi", "222222" );

-- 初始化图书数据
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, '塞纳河出版社');

1.2 引⼊驱动依赖

        引⼊MyBatis 和MySQL 驱动依赖,修改xml文件,更新依赖

 <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

1.3 配置数据库&⽇志

server:
  port: 8082
# 数据库连接配置
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/book_manage?characterEncoding=utf8&useSSL=false
    username: root
    password: 111111
    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语句
  mapper-locations: classpath:mapper/**Mapper.xml

1.4 model包创建

1.userinfo类

@Data
public class UserInfo {
    private Integer id;
    private String userName;
    private String password;
    private Integer delete_flag;
    private Date createTime;
    private Date updateTime;
}

2、bookinfo类

package com.example.book_manage_240827.model;

import lombok.Data;

import java.math.BigDecimal;
import java.util.Date;

@Data
public class BookInfo {
    private Integer id;
    private String bookName;
    private String author;
    private Integer count;
    private BigDecimal price;
    private String publish;
    private Integer status;//   映射--> 1-可借阅   2-不可借阅  0-已删除
    private String stateCN;
    private Date createTime;
    private Date updateTime;
}

2. 用户登录

2.1 约定前后端交互接⼝

[请求]
/user/login
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
[参数]
username=shenmengyao&password=111111
[响应]
true //账号密码验证正确, 否则返回false

        浏览器给服务器发送 /user/login 这样的 HTTP 请求, 服务器给浏览器返回了⼀个Boolean 类型 的数据. 返回true表示 账号密码验证正确;

2.2 实现服务器代码

        控制层:从数据库中, 根据名称查询用户, 如果可以查到, 并且密码⼀致, 就认为登录成功

        创建UserController:

@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;
        }
        //判断数据库的密码和用户输入的密码是否一致
        //查询数据库, 得到数据库的密码
        UserInfo userInfo = userService.queryByName(userName);
        if(userInfo == null){
            return false;
        }
        if (password.equals(userInfo.getPassword())) {
            userInfo.setPassword("");
            //密码正确
            session.setAttribute("session_user_key", userInfo);
            return true;
        }
        return false;
    }
}

        业务层:

        创建UserService:

@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;
    public UserInfo queryByName(String userName){
        return userInfoMapper.queryByName(userName);
    }
}

        数据层:

        创建UserInfoMapper:

@Mapper
public interface UserInfoMapper {
    @Select("select * from user_info where delete_flag=0 and user_name=#{userName}")
    UserInfo queryByName(String userName);
}

        访问数据库, 使⽤MyBatis来实现, 所以把之前dao路径下的⽂件可以删掉, ⽤mapper⽬录来代替, 创建UserInfoMapper 当然, 继续使⽤dao⽬录也可以;dao和mapper通常都被认为是数据库层

 2.3 测试

         部署程序, 验证服务器是否能正确返回数据 (使⽤ URL http://127.0.0.1:8082/user/login? userName=shenmengyao&password=111111 即可);

1、使用浏览器进行访问上述地址,出现如下所示记访问成功;

2、使用前端页面进行访问:

 输入正确的密码点击登录;

成功进入图书列表页;

 3. 添加图书

2.1 约定前后端交互接⼝

[请求]
/book/addBook
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
[参数]
bookName=图书1&author=作者1&count=23&price=36&publish=出版社1&status=1
[响应]
"" //失败信息, 成功时返回空字符串

        我们约定, 浏览器给服务器发送⼀个 /book/addBook 这样的 HTTP 请求, form表单的形式来提交数据,服务器返回处理结果, 返回""表示添加图书成功, 否则, 返回失败信息.

2.2 实现服务器代码

        控制层:在BookController补充代码:

@Slf4j
@RequestMapping("/book")
@RestController
public class BookController {
    @Autowired
    private BookService bookService;

    @RequestMapping(value = "/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 "参数错误";
        }
        //添加图书
        try {
            bookService.insertBook(bookInfo);
            return "";
        } catch (Exception e) {
            log.error("添加图书失败, e:{}", e);
            return e.getMessage();
        }
    }
}

        业务层: 在BookService中补充代码

@Service
public class BookService {
    @Autowired
    private BookInfoMapper bookInfoMapper;

    public Integer insertBook(BookInfo bookInfo){
        return bookInfoMapper.insertBook(bookInfo);
    }
}

        数据层: 创建BookInfoMapper⽂件

@Mapper
public interface BookInfoMapper {
    @Insert("insert into book_info (book_name, author, count, price, publish) " +
            "values (#{bookName}, #{author}, #{count}, #{price}, #{publish} )")
    Integer insertBook(BookInfo bookInfo);
}

2.3 测试

        点击添加图书按钮, 跳转到添加图书的页面, 填写图书信息

点击确定之后,我们可以看到数据库中的信息已经添加成功了;

当我们添加错误的参数的时候就会发出错误信息警告,并且完成不了添加操作;

4.  图书列表

        添加图书之后, 跳转到图书列表⻚⾯, 并没有显⽰刚才添加的图书信息, 接下来我们来实现图 书列表

4.1 需求分析

        当查询到我们的图书数据很多的时候,一个页可能存放不了,所以我们进行分页处理数据,并且分页进行查询;如果想看非当前页的数据,我们可以进行点击其他页码进行查询;

        分⻚时, 数据是如何展⽰的呢:

        第1⻚: 显⽰1-10 条的数据

        第2⻚: 显⽰11-20 条的数据 

        以此类推... 要想实现这个功能, 从数据库中进⾏分⻚查询,我们要使⽤ LIMIT 关键字,        

        格式为:limit 开始索引 每⻚显⽰的条数(开始索引从0开始

        查询第1⻚的SQL语句

         SELECT * FROM book_info LIMIT 0,10

        查询第2⻚的SQL语句

        SELECT * FROM book_info LIMIT 10,10

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

        我们继续基于前端⻚⾯, 继续分析, 得出以下结论:

        1. 前端在发起查询请求时,需要向服务端传递的参数 :

         currentPage 当前⻚码 //默认值为1 ,

         pageSize 每⻚显⽰条数 //默认值为10

        2、后端响应时, 需要响应给前端的数据 ◦:

        records 所查询到的数据列表(存储到List 集合中)

        total 总记录数 (⽤于告诉前端显⽰多少⻚, 显⽰⻚数为: (total + pageSize -1)/pageSize 显⽰⻚数;totalPage 计算公式为 : total % pagesize == 0 ? total / pagesize : (total / pagesize)+1 ;

        翻⻚请求和响应部分, 我们通常封装在两个对象中 ;

1、翻⻚请求对象

@Data
public class PageRequest {
    //当前页
    private Integer currentPage =1;
    //每页显示个数
    private Integer pageSize =10;
    /**
     * 从多少条记录开始查询
     */
    private Integer offset;

    public Integer getOffset() {
        return (currentPage-1) * pageSize;
    }
}

2、翻⻚列表结果类:

@AllArgsConstructor//全参构造
@NoArgsConstructor//无参构造
@Data
public class PageResult<T> {
    private List<T> records;
    //当前的t是bookinfo类型
    private Integer count;//所有的记录数
    private PageRequest pageRequest;//当前页的记录数据
}

4.2 约定前后端交互接⼝

[请求]
/book/getListByPage?currentPage=1&pageSize=10
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
[参数]
[响应]
Content-Type: application/json
{
 "total": 25,
 "records": [{
 "id": 25,
 "bookName": "图书21",
 "author": "作者2",
 "count": 29,
 "price": 22.00,
 "publish": "出版社1",
 "status": 1,
 "statusCN": "可借阅"
 }, {
 ......
 } ]
}

4.3 实现服务器代码

        控制层: 完善 BookController

 @RequestMapping("/getListByPage")
    public PageResult<BookInfo> getListByPage(PageRequest pageRequest) {
        log.info("获取图书列表, pageRequest:{}", pageRequest);
        PageResult<BookInfo> pageResult =
                bookService.getListByPage(pageRequest);
        return pageResult;
    }

        业务层: BookService 

public PageResult<BookInfo> getBookListByPage(PageRequest pageRequest) {
 Integer count = bookInfoMapper.count();
 List<BookInfo> books = bookInfoMapper.queryBookListByPage(pageRequest);
 for (BookInfo book:books){
 if (book.getStatus()==0){
 book.setStatusCN("⽆效");
 }else if (book.getStatus()==1){
 book.setStatusCN("可借阅");
 }else {
 book.setStatusCN("不可借阅");
 }
 }
 return new PageResult<>(count,books);
}

存在的问题:

        1. 翻⻚信息需要返回数据的总数和列表信息, 需要查两次SQL

        2. 图书状态: 图书状态和数据库存储的status有⼀定的对应关系 如果后续状态码有变动, 我们需要修改项⽬中所有涉及的代码, 这种情况, 通常采⽤枚举类来处理映射关系

         创建enmus⽬录, 创建BookStatus类:

package com.example.book_manage_240827.enums;

public enum BookStatusEnums {
    DELETE(0,"无效"),
    NORMAL(1,"可借阅"),
    FORBIDDEN(2,"不可借阅"),
    ;
    private int code;
    private String desc;

    BookStatusEnums(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }
    //根据code, 获取描述
    public static BookStatusEnums getDescByCode(int code){
        switch (code){
            case 0: return BookStatusEnums.DELETE;
            case 1: return BookStatusEnums.NORMAL;
            case 2: return BookStatusEnums.FORBIDDEN;
        }
        return BookStatusEnums.DELETE;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }
}

        getNameByCode: 通过code来获取对应的枚举, 以获取该枚举对应的中⽂名称 后续如果有状态变更, 只需要修改该枚举类即可

        BookService的代码, 可以修改如下: 

 public PageResult<BookInfo> getListByPage(PageRequest pageRequest) {
        //1. 查询记录的总数
        Integer count = bookInfoMapper.count();
        //2. 查询当前页的数据
        List<BookInfo> bookInfos = bookInfoMapper.queryListBypage(pageRequest);
        for (BookInfo bookInfo:bookInfos){
            //根据状态, 设置描述
            bookInfo.setStateCN(BookStatusEnums.getDescByCode(bookInfo.getStatus()).getDesc());
        }
        return new PageResult<>(bookInfos,count,pageRequest);
    }

        BookInfoMapper :

        图书列表按id降序排列:

 /**
     * 查询总数
     */
    @Select("select count(1) from book_info where `status` <>0")
    Integer count();
    /**
     * 查询当前页的数据
     */
    @Select("select * from book_info where `status` <>0 order by id desc limit #{offset}, #{pageSize}")
    List<BookInfo> queryListBypage(PageRequest pageRequest);

        访问http://127.0.0.1:8082/book/getListByPage ,查询第一页的数据;

        访问第二页的数据,http://127.0.0.1:8082/book/getListByPage?currentPage=2 ,

 4.4 实现客户端代码

        浏览器访问 book_list.html ⻚⾯时, 就去请求后端, 把后端返回数据显⽰在⻚⾯上 调⽤后端请求: /book/getListByPage?currentPage=1

 getBookList();
        getBookList();
        function getBookList() {
            $.ajax({
                type: "get",
                url: "/book/getListByPage",
                success: function (result) {
                    if (result != null) {
                        var finalHtml = "";
                        for (var book of result) {
                            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><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></td>';
                            finalHtml += "</tr>";
                        }
                        $("tbody").html(finalHtml);
                    }
                }
            });
        }

         此时, url还未设置 currentPage 参数 ,我们直接使⽤ location.search 从url中获取参数信息即可 ;

        location.search : 获取url的查询字符串 (包含问号) 如: url:         http://127.0.0.1:8082/book_list.html?currentPage=1

        location.search : ?currentPage=1

 $.ajax({
                type: "get",
                url: "/book/getListByPage"+ location.search,

         接下来处理分页信息

        分页插件 --->本案例中, 分⻚代码采⽤了⼀个分⻚组件 ;

        分⻚组件⽂档介绍: jqPaginator分⻚组件 ,使⽤时, 只需要按照 [使⽤说明]部分的⽂档, 把代码复制粘贴进来就可以了(提供的前端代码中, 已经包含该部分内容)

        onPageChange :回调函数,当换⻚时触发(包括初始化第⼀⻚的时候),会传⼊两个参数:         1、"⽬标⻚"的⻚码,Number类型

        2、触发类型,可能的值:"init"(初始化),"change"(点击分⻚)

        我们在图书列表信息加载之后, 需要分⻚信息, 同步加载:

        分⻚组件需要提供⼀些信息:

        totalCounts: 总记录数,

        pageSize: 每⻚的个数,

        visiblePages: 可视⻚数

        currentPage: 当前⻚码

        这些信息中, pageSize 和 visiblePages 前端直接设置即可. totalCounts 后端已经提供, currentPage 也可以从参数中取到, 但太复杂了, 咱们直接由后端返回即可.

        修改后端代码

        1. 为避免后续还需要其他请求处的信息, 我们直接在PageResult 添加 PageRequest 属性

         2. 处理返回结果, 填充 PageRequest 

PageResult:

package com.example.book_manage_240827.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;
    //当前的t是bookinfo类型
    private Integer count;//所有的记录数
    private PageRequest pageRequest;//当前页的记录数据

    public PageResult(Integer count, PageRequest pageRequest, List<T> records)
    {
        this.count = count;
        this.pageRequest = pageRequest;
        this.records = records;
    }

}

BookService:

    public PageResult<BookInfo> getListByPage(PageRequest pageRequest) {
        //1. 查询记录的总数
        Integer count = bookInfoMapper.count();
        //2. 查询当前页的数据
        List<BookInfo> bookInfos = bookInfoMapper.queryListBypage(pageRequest);
        for (BookInfo bookInfo:bookInfos){
            //根据状态, 设置描述
            bookInfo.setStateCN(BookStatusEnums.getDescByCode(bookInfo.getStatus()).getDesc());
        }
        return new PageResult<>(count,pageRequest,bookInfos);
    }

        后端数据返回后, 我们加载⻚⾯信息, 把分⻚代码挪到getBookList⽅法中;

   getBookList();
        getBookList();
        function getBookList() {
            $.ajax({
                type: "get",
                url: "/book/getListByPage"+ location.search,
                success: function (result) {
                    if (result != null) {
                        var finalHtml = "";
                        for (var book of result) {
                            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><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></td>';
                            finalHtml += "</tr>";
                        }
                        $("tbody").html(finalHtml);
                        //翻页信息
                        $("#pageContainer").jqPaginator({
                            totalCounts: 100, //总记录数
                            pageSize: 10,    //每页的个数
                            visiblePages: 5, //可视页数
                            currentPage: 1,  //当前页码
                            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);
                            }
                        });
                    }
                }
            });
        }

        完善⻚⾯点击代码: 当点击⻚码时: 跳转到⻚⾯: book_list.html?currentPage=? 修改上述代码代码:

//页面初始化和页码点击时都会执行
  onPageChange: function (page, type) {
       if (type != 'init') {
           location.href = "book_list.html?currentPage=" + page;
         }

}

4.5 测试

        浏览器访问地址http://127.0.0.1:8082/book_list.html ,即是图书列表第一页,结果如下:

        点击第二页,结果如下:

 

5.修改图书

5.1 约定前后端交互接⼝

        1、进⼊修改⻚⾯, 需要显⽰当前图书的信息:

[请求]
/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, 获取当前图书的信息;

2、点击修改按钮, 修改图书信息

[请求]
/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表单的形式来 提交数据 ,服务器返回处理结果, 返回""表⽰修改图书成功,;否则, 返回失败信息;

5.2 实现服务器代码

        BookController:

   @RequestMapping("/queryBookById")
    public BookInfo queryBookById(Integer bookId){
        if (bookId==null || bookId<=0){
            return new BookInfo();
        }
        BookInfo bookInfo = bookService.queryBookById(bookId);
        return bookInfo;
    }
    @RequestMapping("/updateBook")
    public String updateBook(BookInfo bookInfo) {
        log.info("修改图书:{}", bookInfo);
        try {
            bookService.updateBook(bookInfo);
            return "";
        } catch (Exception e) {
            log.error("修改图书失败", e);
            return e.getMessage();
        }
    }

        业务层: BookService:

  public BookInfo queryBookById(Integer bookId) {
        return bookInfoMapper.queryBookById(bookId);
    }

    public Integer updateBook(BookInfo bookInfo) {
        return bookInfoMapper.updateBook(bookInfo);
    }

        数据层: 根据图书ID,查询图书信息

  /**
     * 根据ID查询图书信息
     */
    @Select("select * from book_info where `status` <>0 and id= #{id}")
    BookInfo queryBookById(Integer id);
    /**
     * 根据ID, 修改图书信息
     */
    Integer updateBook(BookInfo bookInfo);

        对于update接口,我们采⽤xml的⽅式来实现;

        定义Mapper接⼝: BookInfoMapper

 Integer updateBook(BookInfo bookInfo);

        xml实现:

        创建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.book_manage_240827.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">
                publish = #{publish},
            </if>
            <if test="status!=null">
                status =#{status},
            </if>
        </set>
        where id = #{id}
    </update>
</mapper>

5.3 实现客户端代码

        1、在列表⻚时, 我们已经补充了[修改] 的链接:

 <div>
   <button class="btn btn-outline-info" type="button" onclick="location.href='book_add.html'">添加图书</button>
    </div>

        2、点击[修改] 链接时, 就会⾃动跳转到 http://127.0.0.1:8082/book_update.html?bookId=2 (2为对应的图书ID)

        进⼊[修改图书]⻚⾯时, 需要先从后端拿到当前图书的信息, 显⽰在⻚⾯上:

$.ajax({
            type: "get",
            url: "/book/queryBookById"+location.search,
            success: function (result) {
                //前端根据后端返回结果, 针对不同的情况进行处理
                    if (result != null) {
                        console.log(result)
                        $("#bookId").val(result.id);
                        $("#bookName").val(result.bookName);
                        $("#bookAuthor").val(result.author);
                        $("#bookStock").val(result.count);
                        $("#bookPrice").val(result.price);
                        $("#bookPublisher").val(result.publish);
                        $("#bookStatus").val(result.status);
                    }
                }
        });

        补全修改图书的⽅法:

function update() {
            $.ajax({
            type:"post",
            url: "/book/updateBook",
            data: $("#updateBook").serialize(),
            success:function(result){
                if(result == "") {
                    location.href = "book_list.html"
                }else{
                    console.log(result);
                    alert("更新失败"+result);
                }
            },
                error: function (error) {
                    console.log(error);
                }
                });
        }

        我们修改图书信息, 是根据图书ID来修改的, 所以需要前端传递的参数中, 包含图书ID. 有两种⽅式:

        1. 获取url中参数的值(⽐较复杂, 需要拆分url)

        2. 在form表单中, 再增加⼀个隐藏输⼊框, 存储图书ID, 随 $("#updateBook").serialize() ⼀起提交到后端

        下面我们采⽤第⼆种⽅式 在form表单中,添加隐藏输⼊框

<form id="updateBook">
 <input type="hidden" class="form-control" id="bookId" name="id">
 <div class="form-group">
 <label for="bookName">图书名称:</label>
 <input type="text" class="form-control" id="bookName" name="bookName">
 </div>

        hidden 类型的 <input>元素 :隐藏表单, 用户不可⻅、不可改的数据,在用户提交表单时,这些数据会⼀并发送出

        使⽤场景: 正被请求或编辑的内容的 ID. 这些隐藏的 input 元素在渲染完成的⻚⾯中完全不可⻅,⽽ 且没有⽅法可以使它重新变为可⻅.

        ⻚⾯加载时, 给该hidden框赋值

 $("#bookId").val(result.id);
                        $("#bookName").val(result.bookName);
                        $("#bookAuthor").val(result.author);
                        $("#bookStock").val(result.count);
                        $("#bookPrice").val(result.price);
                        $("#bookPublisher").val(result.publish);
                        $("#bookStatus").val(result.status);

5.4 测试 

        点击修改,

        出现修改页面:

        在修改正确的数据之后点击确定,返回到图书列表页;

        查看修改前与修改后的数据变化:

6. 删除图书

6.1 约定前后端交互接⼝

        删除分为 逻辑删除 和物理删除

        逻辑删除 :逻辑删除也称为软删除、假删除、Soft Delete,即不真正删除数据,⽽在某⾏数据上增加类型 is_deleted的删除标识,⼀般使⽤UPDATE语句 物理删除;

        物理删除也称为硬删除,从数据库表中删除某⼀⾏或某⼀集合数据,⼀般使⽤DELETE语句 删除图书的两种实现⽅式 逻辑删除

update book_info set status=0 where id = 1

        物理删除

 delete from book_info where id=25 

         本次采⽤逻辑删除的⽅式,逻辑删除的话, 依然是更新逻辑, 我们可以直接使⽤修改图书的接⼝;

[请求]
/book/updateBook
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
[参数]
id=1&status=0
[响应]
"" //失败信息, 成功时返回空字符串

6.2 实现客户端代码

        点击删除时, 调⽤delete()⽅法, 我们来完善delete⽅法

 function deleteBook(id) {
            var isDelete = confirm("确认删除?");
            if (isDelete) {
                //删除图书
                $.ajax({
                    type: "post",
                    url: "/book/updateBook",
                    data: {
                        id: id,
                        status: 0
                    },
                    success: function () {
                        //重新刷新⻚⾯
                        location.href = "book_list.html"
                    }
                });
            }
        }

6.3 测试

点击删除,出现以下弹窗,再次确认是否删除; 

点击取消,该图书不会发生任何变化;

点击确定,则该图书被删除,即id=11的图书被删除;

 

        即逻辑删除只是让其某一状态设置为不显示在客户端页面的状态,数据库里面的数据还是依旧存在的; 

7. 批量删除

        批量删除, 其实就是批量修改数据

7.1 约定前后端交互接⼝

[请求]
/book/batchDeleteBook
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
[参数]
[响应]
"" //失败信息, 成功时返回空字符串

        点击[批量删除]按钮时, 只需要把复选框选中的图书的ID,发送到后端即可,多个id, 我们使⽤List的形式来传递参数

7.2 实现服务器代码

        控制层: BookController

 @RequestMapping("/batchDelete")
    public boolean batchDelete(@RequestParam List<Integer> ids){
        log.info("批量删除数据, ids:{}", ids);
        try {
            Integer result = bookService.batchDelete(ids);
        }catch (Exception e){
            log.error("批量删除数据失败, ids:{}, e:{}", ids, e);
            return false;
        }
        return true;
    }

        业务层: BookService

   public Integer batchDelete(List<Integer> ids) {
        return bookInfoMapper.batchDelete(ids);
    }

        数据层: 批量删除需要⽤到动态SQL, 初建议动态SQL的部分, 都⽤xml实现         BookInfoMapper.java 接⼝定义:

 Integer batchDelete(List<Integer> ids);

        xml接⼝实现:

 <update id="batchDelete">
        update book_info
        SET `status`=0
        where id in
        <foreach collection="ids" open="(" close=")" separator="," item="id">
            #{id}
        </foreach>
    </update>

7.3 实现客户端代码

        点击[批量删除]按钮时, 需要获取到所有选中的复选框的值:

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) {
                        console.log(result);
                        if (result == true) {
                            alert("删除成功");
                            //重新刷新⻚⾯
                            location.href = "book_list.html"
                        }
                    }
                });
            }
        }

7.4 测试 

8. 强制登录

        虽然我们做了用户登录, 但是我们发现, ⽤⼾不登录, 依然可以操作图书. 所以我们需要进⾏强制登录. 如果⽤⼾未登录就访问图书列表或者添加图书等⻚⾯, 强制跳转到登录⻚⾯. 

8.1 实现思路分析

        ⽤⼾登录时, 我们已经把登录⽤⼾的信息存储在了Session中. 那就可以通过Session中的信息来判断⽤⼾是否登录:

        1. 如果Session中可以取到登录⽤⼾的信息, 说明⽤⼾已经登录了, 可以进⾏后续操作

        2. 如果Session中取不到登录⽤⼾的信息, 说明⽤⼾未登录, 则跳转到登录⻚⾯

        当前后端接⼝数据返回类: 

@Data
public class PageResult<T> {
    private List<T> records;
    //当前的t是bookinfo类型
    private Integer count;//所有的记录数
    private PageRequest pageRequest;//当前页的记录数据

    public PageResult(Integer count, PageRequest pageRequest, List<T> records)
    {
        this.count = count;
        this.pageRequest = pageRequest;
        this.records = records;
    }
}

        如此结果, 前端没办法确认⽤⼾是否登录了,所以需要再增加⼀个属性告知后端的状态以及后端出错的原因. 修改如下:

@Data
public class PageResult<T> {
    private int status;
    private String errorMessage;
    private List<T> records;
    //当前的t是bookinfo类型
    private Integer count;//所有的记录数
    private PageRequest pageRequest;//当前页的记录数据

    public PageResult(Integer count, PageRequest pageRequest, List<T> records)
    {
        this.count = count;
        this.pageRequest = pageRequest;
        this.records = records;
    }
}

        我们尝试对所有后端返回的数据进⾏⼀个封装:

@Data
public class Result<T> {
 private int status;
 private String errorMessage;
 private T data;
}

        status 为后端业务处理的状态码, 也可以使⽤枚举来表⽰:

public enum ResultStatus {
        SUCCESS(200),
       UNLOGIN(-1),
          FAIL(-2);
         private Integer code;
        ResultStatus(int code) {
             this.code = code;
        }
        public Integer getCode() {
            return code;
         }
        public void setCode(Integer code) {
             this.code = code;
         }
}

        修改Result, 并添加⼀些常⽤⽅法:

package com.example.book_manage_240827.model;

import com.example.book_manage_240827.enums.ResultStatus;
import lombok.Data;

@Data
public class Result {
    private ResultStatus status;
    private String errorMessage;
    private Object data;
    /**
     * 业务执⾏成功时返回的⽅法
     *
     * @param data
     * @return
     */
    public static Result success(Object data) {
        Result result = new Result();
        result.setStatus(ResultStatus.SUCCESS);
        result.setErrorMessage("");
        result.setData(data);
        return result;
    }
    /**
     * 业务执⾏失败时返回的⽅法
     *
     * @param
     * @return
     */
    public static Result fail(String msg) {
        Result result = new Result();
        result.setStatus(ResultStatus.FAIL);
        result.setErrorMessage(msg);
        result.setData("");
        return result;
    }
    /**
     * 业务执⾏失败时返回的⽅法
     *
     * @param
     * @return
     */
    public static Result unlogin() {
        Result result = new Result();
        result.setStatus(ResultStatus.UNLOGIN);
        result.setErrorMessage("⽤⼾未登录");
        result.setData(null);
        return result;
    }
}

8.2 实现服务器代码

        修改图书列表接⼝, 进⾏登录校验

   @RequestMapping("/getListByPage")
    public Result getListByPage(PageRequest pageRequest, HttpSession session) {
        log.info("获取图书列表, pageRequest:{}", pageRequest);
        //判断⽤⼾是否登录
        if (session.getAttribute("session_user_key")==null){
            return Result.unlogin();
        }
        UserInfo userInfo = (UserInfo) session.getAttribute("user_session_key");
        if (userInfo==null || userInfo.getId()<0 ||
                "".equals(userInfo.getUserName())){
            return Result.unlogin();
        }
        //⽤⼾登录, 返回图书列表
        PageResult<BookInfo> pageResult =
                bookService.getListByPage(pageRequest);
        return Result.success(pageResult);
    }

        问题: 如果修改常量session的key, 就需要修改所有使⽤到这个key的地⽅, 出于⾼内聚低耦合的思想, 我 们把常量集中在⼀个类⾥

        创建类: Constants

public class Constants {
    public static final String USER_SESSION_KEY = "user_session_key";
}

        常量名命名规则: 常量命名全部⼤写, 单词间⽤下划线隔开, ⼒求语义表达完整清楚, 不要在意名字⻓度.

        正例: MAX_STOCK_COUNT / CACHE_EXPIRED_TIME

        反例: COUNT / TIME

        修改之前使⽤到 user_session_key ;

        登录接⼝:

        图书列表接⼝: 主要是关于session的修改:

@RequestMapping("/getListByPage")
    public Result getListByPage(PageRequest pageRequest, HttpSession session) {
        log.info("获取图书列表, pageRequest:{}", pageRequest);
        //判断⽤⼾是否登录
        if (session.getAttribute(Constants.USER_SESSION_KEY)==null){
            return Result.unlogin();
        }
        UserInfo userInfo = (UserInfo) session.getAttribute(Constants.USER_SESSION_KEY);
        if (userInfo==null || userInfo.getId()<0 ||
                "".equals(userInfo.getUserName())){
            return Result.unlogin();
        }
        //⽤⼾登录, 返回图书列表
        PageResult<BookInfo> pageResult =
                bookService.getListByPage(pageRequest);
        return Result.success(pageResult);
    }

8.3 实现客户端代码

        由于后端接⼝发⽣变化, 所以前端接⼝也需要进⾏调整:

        修改图书列表的⽅法:

 success: function (result) {
                    console.log("后端返回成功");
                    if (result == null ||  result.data == null) {
                        location.href = "login.html";
                        return;
                    }
                        var finalHtml = "";
                        var data = result.data;
                        for (var book of data.records) {
                            finalHtml += '<tr>';

8.4 测试

        浏览器访问http://127.0.0.1:8082/book_list.html,会直接返回到登录页面;

        用户正常登录之后,图书列表也中图书信息正常返回;

ps:图书管理项目2.0到这里就结束了,如果对你有所帮助的话,就请一键三连哦!!! 

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

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

相关文章

网站建设完成后, 功能性网站如何做seo

功能性网站的SEO优化关注于提高网站在搜索引擎中的排名&#xff0c;从而吸引更多用户并提升用户体验。以下是功能性网站SEO的详细解析&#xff1a; 关键词研究与布局 目标受众分析&#xff1a;了解目标受众的搜索习惯和需求&#xff0c;确定适合的关键词。使用工具如Google Ke…

反弹shell流量分析与检测

常用的隧道技术&#xff1a; 网络层&#xff1a;ipv6、Icmp、gre IPv6隧道&#xff1a;将ipv6报文放入ipv4作为载体进行传输&#xff0c;工具&#xff1a;socat、6tunnel ICMP隧道&#xff1a;将数据放入ping包中进行传输&#xff0c;工具&#xff1a;icmpsh、PingTunnel G…

日本麻将入门(二):牌效率【基础】

基础牌效率 引入 日麻&#xff0c;又称立直麻将。日麻的水平本质上与你是否会立直有很大关系&#xff08;参见常用役种&#xff1a;立直&#xff1a;优点&#xff09;&#xff0c;但立直最大的缺点就是不能副露&#xff0c;导致我们只能通过自己的摸切来完成听牌形的组成。在…

20. 筛选dataframe

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 筛选条件 基本筛选 要筛选DataFrame&#xff0c;首先需要了解筛选条件。Pandas提供了多种筛选条件&#xff0c;包括等于&#xff08;&#xff09;、不等于&#xff08;&#xff01;&#xff09;、大于&#xff08;…

Leetcode Day14排序算法

动态git可以看 :https://leetcode.cn/problems/sort-an-array/solutions/179370/python-shi-xian-de-shi-da-jing-dian-pai-xu-suan-fa/ 选择排序 def selection_sort(nums):n len(nums)for i in range(n):for j in range(i, n):if nums[i] > nums[j]:nums[i], nums[j] …

05.整合Axios+MockJs

1. 前言 作为前后端分离的项目&#xff0c;必不可少的当然是发请求向后端拿数据了&#xff0c; 但是不可能每次等到接口完成我们才开始开发前端&#xff0c;所以使用 mock.js 先模拟后端接口&#xff0c;等后端接口开发完成后&#xff0c;可以无缝衔接&#xff0c;直接替换为真…

EmguCV学习笔记 VB.Net 7.2 特征点检测

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 EmguCV是一个基于OpenCV的开源免费的跨平台计算机视觉库,它向C#和VB.NET开发者提供了OpenCV库的大部分功能。 教程VB.net版本请访问…

(24)(24.6) 基于OSD的参数菜单

文章目录 前言 1 Copter默认屏幕 2 Plane默认屏幕 3 实例 前言 这允许使用 ArduPilot 机载 OSD 和 RC 发射机的杆输入设置和调整参数。还有两个额外的 OSD 屏幕可用&#xff08;OSD5 和 OSD6&#xff09;&#xff0c;每个屏幕有 9 个“插槽”来保存参数。屏幕首先显示一组…

taro ui 小程序at-calendar日历组件自定义样式+选择范围日历崩溃处理

taro ui 日历文档 目录 单选标记时间&#xff1a; 效果&#xff1a; template&#xff1a; data&#xff1a; methods: 日历--范围选择&#xff1a; 效果&#xff1a; template&#xff1a; data&#xff1a; methods&#xff1a; 日历--间隔多选&#xff1a;利用标…

详细分析python中QRCode生成二维码的基本知识(附Demo)

目录 前言1. 基本知识2. Demo3. 彩蛋3.1 文件路径3.2 Image.LANCZOS 前言 以下主要利用python中的QRCode来生成二维码的基本知识 1. 基本知识 简单易用&#xff0c;并且可以生成高质量的二维码图像 支持多种自定义设置&#xff0c;例如二维码的大小、边框、容错级别、颜色等…

java在项目中实现excel导入导出

一、初识EasyExcel* 1. Apache POI 先说POI&#xff0c;有过报表导入导出经验的同学&#xff0c;应该听过或者使用。 Apache POI是Apache软件基金会的开源函式库&#xff0c;提供跨平台的Java API实现Microsoft Office格式档案读写。但是存在如下一些问题&#xff1a; 1.1 …

C语言阴阳迷宫

目录 开头程序程序的流程图程序游玩的效果下一篇博客要说的东西 开头 大家好&#xff0c;我叫这是我58。 程序 #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <Windows.h> enum WASD {W…

【传输层协议】TCP协议(上) {TCP协议段格式;确认应答机制;超时重传机制;连接管理机制:三次握手、四次挥手}

TCP&#xff08;Transmission Control Protocol&#xff09;是一种面向连接的、可靠的、基于字节流的传输层协议&#xff0c;用于在网络上可靠地传输数据。TCP是互联网协议套件&#xff08;TCP/IP&#xff09;中的一个主要协议&#xff0c;它在IP&#xff08;Internet Protocol…

基于STM32开发的智能恒温系统

目录 引言环境准备工作 硬件准备软件安装与配置系统设计 系统架构硬件连接代码实现 系统初始化温度检测与恒温控制OLED显示与状态提示Wi-Fi通信与远程监控应用场景 家庭环境的智能恒温管理实验室或工业环境的精确温度控制常见问题及解决方案 常见问题解决方案结论 1. 引言 智…

VIVADO自定义 IP封装

简介 本章节主要针对VIVAO 2020.2版本做IP自定义封装&#xff0c;其中涉及到IP寄存器读写配置&#xff0c;自定义接口封装等介绍。 IP封装 IP标准自定义步骤一般有创建工程&#xff0c;封装IP&#xff0c;自定义内容&#xff0c;添加自定义库这4个步骤&#xff0c;下面…

探秘紫白洋桔梗花语:勇气、爱情、希望与清晰的象征解读

在缤纷多彩的花卉世界中&#xff0c;紫白洋桔梗宛如一位神秘而优雅的仙子&#xff0c;悄然绽放着独特的魅力。它那淡雅的色彩与别致的花形&#xff0c;令人一见倾心&#xff0c;而其背后蕴含的丰富花语&#xff0c;更是如同隐藏的宝藏一般&#xff0c;等待着我们去细细探寻与解…

蓝花楹花语探秘:从宁静忧郁到等待爱情的深刻寓意

在时光的长河中&#xff0c;有一种花朵宛如梦幻的精灵&#xff0c;每当它绽放之时&#xff0c;那一片绚烂的蓝紫色便如同璀璨的星空倾洒人间&#xff0c;它就是蓝花楹。蓝花楹那独特的身姿和醉人的色彩&#xff0c;仿佛自带一种神秘的魔力&#xff0c;吸引着无数人驻足凝望。而…

vue3+ts+vite+electron+electron-store+electron-builder打包可安装包

yarn create vite yarn add electron yarn add electron-store yarn add electron-builder 新增main.js、preload.js // main.js const { app, BrowserWindow, ipcMain, globalShortcut } require(electron) const path require(path) let store // 我们将在稍后动态导入 el…

Flink优化之--旁路缓存和异步IO

Apache Flink 是一个开源流处理框架&#xff0c;以其高吞吐量、低延迟和事件驱动的处理能力著称。随着大数据和实时处理需求的不断增加&#xff0c;Flink 在许多行业和应用场景中得到了广泛应用&#xff0c;如金融风控、物联网数据处理、实时数据分析等。然而&#xff0c;随着数…

如何学习Linux性能优化?

你是否也曾跟我一样&#xff0c;看了很多书、学了很多Linux性能工具&#xff0c;但在面对Linux性能问题时&#xff0c;还是束手无策&#xff1f;实际上&#xff0c;性能分析和优化始终是大多数软件工程师的一个痛点。但是&#xff0c;面对难题&#xff0c;我们真的就无解了吗&a…