【JavaEE进阶】MyBatis(4)-完善图书管理系统

欢迎关注个人主页:逸狼


创造不易,可以点点赞吗

如有错误,欢迎指出~


前⾯图书管理系统,咱们只完成了⽤⼾登录和图书列表,并且数据是Mock的.接下来我们把其他功能进 ⾏完善.

功能列表: 1. ⽤⼾登录 2. 图书列表 3. 图书的增删改查 4. 翻⻚功能

创建数据库book_test

复制代码
-- 创建数据库 
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驱动依赖

修改pom⽂件

复制代码
<dependency>
 <groupId>org.mybatis.spring.boot</groupId>
 <artifactId>mybatis-spring-boot-starter</artifactId>
 <version>3.0.3</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: 111111
    driver-class-name: com.mysql.cj.jdbc.Driver
  application:
    name: demo-book
mybatis:
  configuration:
    map-underscore-to-camel-case: true #配置驼峰自动转换
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印sql语句
logging:
  file:
    name: spring-book.log

Model创建

UserInfo

复制代码
import lombok.Data;
import java.util.Date;
@Data

public class UserInfo {
 private Integer id;
 private String userName;
 private String password;
 private Integer deleteFlag;
 private Date createTime;
 private Date updateTime;
}

BookInfo

复制代码
@Data

public class BookInfo {
 //图书ID 
 private Integer id;
 //书名 
 private String bookName;
 //作者 
 private String author;
 //数量 
 private Integer count;
 //定价 
 private BigDecimal price;
 //出版社 
 private String publish;
 //状态 0-⽆效 1-允许借阅 2-不允许借阅 
 private Integer status;
 private String statusCN;
 //创建时间 
 private Date createTime;
 //更新时间 
 private Date updateTime;
}

接口1: ⽤⼾登录

约定前后端交互接⼝

  • 请求\] /user/login Content-Type: application/x-www-form-urlencoded; charset=UTF-8

  • 响应\] true //账号密码验证正确, 否则返回false

实现服务器代码

从数据库中,根据名称查询⽤⼾,如果可以查到,并且密码⼀致,就认为登录成功

控制层UserController
复制代码
@Slf4j
@RestController
@RequestMapping("/user")

public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public boolean login(String userName, String password, HttpSession session){
        //参数输入,打印日志
        log.info("接收到参数: " + userName);
        Boolean result = userService.checkUserAndPassword(userName, password, session);

        log.info("用户登入结果: name:{}, password:{}, 结果: {}", userName,password , result);
        return result;
    }
}
业务层userService
复制代码
package com.example.demo.service;

import com.example.demo.Constants;
import com.example.demo.Mapper.UserInfoMapper;
import com.example.demo.model.UserInfo;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

@Service
public class UserService {

    @Autowired
    private UserInfoMapper userInfoMapper;

    public Boolean checkUserAndPassword(String userName, String password, HttpSession session){

        //账号, 密码为空
        if(!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)){
            return false;
        }
        //在数据库中查询是否存在用户名
        UserInfo userInfo = userInfoMapper.queryUserByName(userName);
        if(userInfo == null){
            return false;
        }

        if( password.equals(userInfo.getPassword())){
            //存储在Session中
            //账号密码正确
            session.setAttribute(Constants.SESSION_USER_KEY,userName);
            return true;
        }
        return false;
    }
}
数据层userInfoMapper
复制代码
@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通常都被认为是数据库层

接口2: 添加图书

约定前后端交互接⼝

  • 请求\] /book/addBook Content-Type: application/x-www-form-urlencoded; charset=UTF-8

  • 响应\] "" //失败信息, 成功时返回空字符串

实现服务器代码控制层: 在BookController补充代码 先进⾏参数校验,校验通过了进⾏图书添加

实际开发中,后端开发⼈员不关注前端是否进⾏了参数校验,⼀律进⾏校验 ,原因是:后端接⼝可能会被⿊客攻击,不通过前端来访问,如果后端不进⾏校验,会产⽣脏数据

实现服务器代码

BookController

复制代码
//添加图书
    @RequestMapping(value = "/addBook", produces = "application/json")
    public ResultVO<String> addBook(BookInfo bookInfo) {
        log.info("添加图书, bookInfo:{}", bookInfo);

        //参数校验
        if (!StringUtils.hasLength(bookInfo.getBookName())
                ||!StringUtils.hasLength(bookInfo.getAuthor())
                || bookInfo.getCount() == null
                || bookInfo.getPrice() == null
                ||!StringUtils.hasLength(bookInfo.getPublish())) {
            return new ResultVO<>(400, "参数不合法,图书名称、作者、库存、价格、出版社均为必填项", "");
        }

        //2,插入数据
        try {
            Integer result = bookService.insertBook(bookInfo);
            if (result == 1) {
                return new ResultVO<>(200, "图书添加成功", "");
            }
            return new ResultVO<>(500, "图书添加失败,插入结果异常", "");
        } catch (Exception e) {
            log.error("数据插入发生异常, e: ", e);
            return new ResultVO<>(500, "数据插入失败,请联系管理员,具体异常:" + e.getMessage(), "");
        }
    }

BookService

复制代码
 public Integer insertBook(BookInfo bookInfo) {
        return bookMapper.insertBook(bookInfo);
    }

BookMapper

复制代码
Integer insertBook(BookInfo bookInfo);

ResultVO类

复制代码
package com.example.demo.model;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class ResultVO<T> {
    private Integer code; // 状态码,例如 200 表示成功,其他表示失败
    private String message; // 提示信息
    private T data; // 数据部分,如果有需要返回的数据
}

接口3: 图书列表

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

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

  • 第1⻚:显⽰1-10条的数据
  • 第2⻚:显⽰11-20条的数据
  • 第3⻚:显⽰21-30条的数据
  • 以此类推...

要想实现这个功能,从数据库中进⾏分⻚查询,我们要使⽤ LIMIT 关键字,格式为:limit开始索引 每⻚显⽰的条数(开始索引从0开始)

查询第1⻚的SQL语句: SELECT * FROM book_info LIMIT 0,10

查询第2⻚的SQL语句: SELECT * FROM book_info LIMIT 10,10

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

对于后端而言,前端需要提供参数: currentPage, pageSize

后端给前端返回: 当前页的记录

实现服务器代码

BookController

复制代码
    //查询图书信息
    @RequestMapping("/getListByPage")
    public PageResponse<BookInfo> getListByPage(PageRequest pageRequest, HttpServletRequest request) {
        log.info("获取图书列表, pageRequest: {}", pageRequest);
        //

        //参数校验省略
        PageResponse<BookInfo> bookInfoPageResponse = bookService.getListByPage(pageRequest);
        return bookInfoPageResponse;
    }

BookService

复制代码
    //查询图书信息
    public PageResponse<BookInfo> getListByPage(PageRequest pageRequest){
        //1, 总记录数
        Integer count = bookMapper.count();

//        List<BookInfo> bookInfos = bookDao.mockData();
        //        //2. 当前页记录数

        List<BookInfo> bookInfos = bookMapper.queryBookByPage(pageRequest.getOffset(),pageRequest.getPageSize());
        for(BookInfo bookInfo : bookInfos){
//            if(bookInfo.getStatus() == 0){
//                bookInfo.setStatusCN("删除");
//            }else if(bookInfo.getStatus() == 1){
//                bookInfo.setStatusCN("可借阅");
//            }else{
//                bookInfo.setStatusCN("不可借阅");
//            }
            bookInfo.setStatusCN(BookStatusEnum.getStatusByCode(bookInfo.getStatus()).getDesc());
        }

        return new PageResponse<BookInfo>(count,bookInfos,pageRequest);


    }

BookMapper

复制代码
    //查询图书信息
    @Select("select * from book_info order by id limit #{offset}, #{limit}")
    List<BookInfo> queryBookByPage(Integer offset, Integer limit);

接口4: 修改图书

约定前后端交互接⼝

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

请求\] /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表单的形式来 提交数据 服务器返回处理结果,返回""表⽰添加图书成功,否则,返回失败信息. 实现服务器代码 ### 实现服务器代码 #### BookController //更新图书 @RequestMapping("/updateBook") public ResultVO updateBook(BookInfo bookInfo) { log.info("更新图书, bookInfo: {}", bookInfo); try { Integer result = bookService.updateBook(bookInfo); if (result > 0) { return new ResultVO<>(200, "图书更新成功", ""); } else if (result == 0) { return new ResultVO<>(200, "图书内容未发生变化,无需更新", ""); } return new ResultVO<>(500, "图书更新失败", ""); } catch (Exception e) { log.error("更新图书失败,e:", e); return new ResultVO<>(500, "数据库操作失败:" + e.getMessage(), ""); } } #### BookService //更新图书 public Integer updateBook(BookInfo bookInfo) { return bookMapper.updateBook(bookInfo); } #### BookMapper //更新图书,用xml的方式 注解和xml的方式可以混用 Integer updateBook(BookInfo bookInfo); #### BookMapperInfo.xml update book_info book_name =#{bookName}, author =#{author}, count =#{count}, price =#{price}, publish =#{publish}, status =#{status}, where id = #{id} ## 接口5: 通过id查询图书 ### 实现服务器代码 #### BookController //通过id查询图书 @RequestMapping("/queryBookById") public BookInfo queryBookById(Integer bookId){ log.info("获取图书信息, bookId: "+ bookId); //参数校验,不能为null,不能<=0...省略 return bookService.queryBookById(bookId); } #### BookService //通过id查询图书 public BookInfo queryBookById(Integer bookId) { return bookMapper.queryBookById(bookId); } #### BookMapper //通过id查询图书 @Select("select * from book_info where id = #{bookId}") BookInfo queryBookById(Integer bookId); ## 接口6:删除图书 约定前后端交互接⼝ 删除分为逻辑删除和物理删除 1. 逻辑删除也称为软删除、假删除、SoftDelete,即不真正删除数据,⽽在某⾏数据上增加类型 is_deleted的删除标识,⼀般使⽤UPDATE语句 2. 物理删除也称为硬删除,从数据库表中删除某⼀⾏或某⼀集合数据,⼀般使⽤DELETE语句 删除图书的两种实现⽅式 逻辑删除: update book_info set status=0 where id = 1 物理删除: delete from book_info where id=25 ### 实现服务器代码 #### BookController @RequestMapping("/deleteBook") public String deleteBook(Integer bookId){ log.info("删除图书, bookId: {}", bookId); try{ BookInfo bookInfo = new BookInfo(); bookInfo.setId(bookId); bookInfo.setStatus(BookStatusEnum.DELETED.getCode()); Integer result = bookService.updateBook(bookInfo); if(result > 0){//1 return ""; } return "图书删除失败"; }catch(Exception e){ log.error("删除图书失败,e:",e); return "数据库操作失败"; } } ## 接口5: 批量删除图书 批量删除,其实就是批量修改数据 约定前后端交互接⼝ 1. \[请求\] /book/batchDeleteBook Content-Type: application/x-www-form-urlencoded; charset=UTF-8 2. \[参数\] ids = \[1, 2, 3, 4

  1. 响应\] boolean true //删除成功 false //删除失败

实现服务器代码

BookController

复制代码
    //批量删除
    @RequestMapping("/batchDeleteBook")
    public Boolean batchDeleteBook(@RequestParam List<Integer> ids){
        log.info("批量删除图书, ids:{}", ids);
        try{
            //执行sql
            bookService.batchDeleteBook(ids);
            return true;

        }catch(Exception e){
            log.error("批量删除图书, ids:{}", ids);
        }
        return false;
    }

BookService

复制代码
    public void batchDeleteBook(List<Integer> ids) {
         bookMapper.batchDelete(ids);
    }

BookMapper

复制代码
 void batchDelete(List<Integer> ids);

BookMapperInfo.xml

复制代码
<!--    批量删除-->
    <update id="batchDelete">
        update book_info
        set status = 0
        where id in
        <foreach collection="ids" item="id" open="(" close=")" separator=",">
            #{id}
        </foreach>
    </update>

接口6: 强制登录

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

实现思路分析 ⽤⼾登录时,我们已经把登录⽤⼾的信息存储在了Session中.那就可以通过Session中的信息来判断⽤ ⼾都是登录.

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

  2. 如果Session中取不到登录⽤⼾的信息,说明⽤⼾未登录,则跳转到登录⻚⾯. 以图书列表为例 现在图书列表接⼝返回的内容如下:

实现服务器代码

BookController

复制代码
    //查询图书信息,翻页使用
    @RequestMapping("/getListByPage")
    public Result<PageResponse<BookInfo>> getListByPage(PageRequest pageRequest, HttpServletRequest request) {
        log.info("获取图书列表, pageRequest: {}", pageRequest);

        HttpSession session = request.getSession(false);//如果拿不到session就会返回null
        if(session == null
        || session.getAttribute(Constants.SESSION_USER_KEY) == null
        || !StringUtils.hasLength((String)session.getAttribute(Constants.SESSION_USER_KEY))){
            //用户未登录
            return Result.unlogin();
        }
        //参数校验省略
        PageResponse<BookInfo> bookInfoPageResponse = new PageResponse<>();
        try{
            bookInfoPageResponse = bookService.getListByPage(pageRequest);

        }catch(Exception e){
            log.error("获取图书列表失败");
            return Result.fail();
        }
        return Result.success(bookInfoPageResponse);
    }

Constants

复制代码
package com.example.demo.constants;

public class Constants {
    //成功
    public static final int SUCCESS_CODE = 200;
    //程序出错
    public static final int FAIL_CODE = -2;
    //未登录
    public static final int UNLOGIN_CODE = -1;

    public static final String SESSION_USER_KEY = "session_user_key";

}

Result

复制代码
package com.example.demo.model;

import com.example.demo.constants.Constants;
import lombok.Data;

@Data
public class Result<T> {
    private int code; //200-成功  -1 用户未登录  -2 程序出错   业务状态码, 非http状态码
    private String errMsg;
    private T data;

    public static <T> Result success(T data){
        Result result = new Result();
        result.setCode(Constants.SUCCESS_CODE);
        result.setErrMsg("");
        result.setData(data);
        return result;
    }

    public static <T> Result unlogin(){
        Result result = new Result();
        result.setCode(Constants.UNLOGIN_CODE);
        result.setErrMsg("用户未登录");
        return result;
    }

    public static <T> Result fail(T data){
        Result result = new Result();
        result.setCode(Constants.FAIL_CODE);
        result.setErrMsg("程序发生错误!");
        return result;
    }

    public static <T> Result fail(String errMsg){
        Result result = new Result();
        result.setCode(Constants.FAIL_CODE);
        result.setErrMsg(errMsg);
        return result;
    }
    public static <T> Result fail(String errMsg, int code){
        Result result = new Result();
        result.setCode(code);
        result.setErrMsg(errMsg);
        return result;
    }
    public static <T> Result fail(){
        Result result = new Result();
        result.setCode(Constants.FAIL_CODE);
        result.setErrMsg("程序发生错误!");
        return result;
    }
}
相关推荐
IvorySQL1 小时前
PostgreSQL 分区表的 ALTER TABLE 语句执行机制解析
数据库·postgresql·开源
·云扬·1 小时前
MySQL 8.0 Redo Log 归档与禁用实战指南
android·数据库·mysql
IT邦德1 小时前
Oracle 26ai DataGuard 搭建(RAC到单机)
数据库·oracle
惊讶的猫1 小时前
redis分片集群
数据库·redis·缓存·分片集群·海量数据存储·高并发写
不爱缺氧i2 小时前
完全卸载MariaDB
数据库·mariadb
纤纡.2 小时前
Linux中SQL 从基础到进阶:五大分类详解与表结构操作(ALTER/DROP)全攻略
linux·数据库·sql
jiunian_cn2 小时前
【Redis】渐进式遍历
数据库·redis·缓存
橙露2 小时前
Spring Boot 核心原理:自动配置机制与自定义 Starter 开发
java·数据库·spring boot
冰暮流星2 小时前
sql语言之分组语句group by
java·数据库·sql
符哥20082 小时前
Ubuntu 常用指令集大全(附实操实例)
数据库·ubuntu·postgresql