【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;
    }
}
相关推荐
我有医保我先冲2 小时前
SQL复杂查询与性能优化:医药行业ERP系统实战指南
数据库·sql·性能优化
阳光_你好3 小时前
详细说明Qt 中共享内存方法: QSharedMemory 对象
开发语言·数据库·qt
喝醉酒的小白4 小时前
MySQL响应慢是否由堵塞或死锁引起?
数据库
Pasregret4 小时前
04-深入解析 Spring 事务管理原理及源码
java·数据库·后端·spring·oracle
jnrjian4 小时前
归档重做日志archived log (明显) 比redo log重做日志文件小
数据库·oracle
TDengine (老段)4 小时前
TDengine 中的命名与边界
大数据·数据库·物联网·oracle·时序数据库·tdengine·iotdb
鸽鸽程序猿5 小时前
【JavaEE】MyBatis - Plus
java-ee·mybatisplus
谁家有个大人5 小时前
MYSQL中对行与列的操作
数据库·mysql
0000ysl5 小时前
数据库基础-函数&约束
数据库
JavaPub-rodert6 小时前
Etcd用的是Raft算法
数据库·github·etcd·raft