• 图书管理系统(SpringBoot+SpringMVC+MyBatis)


    目录

    1.数据库表设计

    2.引入MyBatis和MySQL驱动依赖

    3.配置数据库&日志

    4.Model创建

    5.用户登录功能实现

     6.实现添加图书功能

    7.实现翻页功能


    1.数据库表设计

    数据库表是应⽤程序开发中的⼀个重要环节, 数据库表的设计往往会决定我们的应⽤需求是否能顺利实, 甚至决定我们的实现方式. 如何设计表以及这些表有哪些字段、关系也是非常重要.

    数据库表设计是依据业务需求来设计的,数据库表通常分两种: 实体表和关系表.

    创建数据库 book_test

    1. -- 创建数据库
    2. DROP DATABASE IF EXISTS book_test;
    3. CREATE DATABASE book_test DEFAULT CHARACTER SET utf8mb4;
    4. USE book_test;
    5. -- ⽤户表
    6. DROP TABLE IF EXISTS user_info;
    7. CREATE TABLE user_info (
    8. `id` INT NOT NULL AUTO_INCREMENT,
    9. `user_name` VARCHAR ( 128 ) NOT NULL,
    10. `password` VARCHAR ( 128 ) NOT NULL,
    11. `delete_flag` TINYINT ( 4 ) NULL DEFAULT 0,
    12. `create_time` DATETIME DEFAULT now(),
    13. `update_time` DATETIME DEFAULT now() ON UPDATE now(),
    14. PRIMARY KEY ( `id` ),
    15. UNIQUE INDEX `user_name_UNIQUE` ( `user_name` ASC )) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COMMENT = '⽤户表 ';
    16. -- 图书表
    17. DROP TABLE IF EXISTS book_info;
    18. CREATE TABLE `book_info` (
    19. `id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
    20. `book_name` VARCHAR ( 127 ) NOT NULL,
    21. `author` VARCHAR ( 127 ) NOT NULL,
    22. `count` INT ( 11 ) NOT NULL,
    23. `price` DECIMAL (7,2 ) NOT NULL,
    24. `publish` VARCHAR ( 256 ) NOT NULL,
    25. `status` TINYINT ( 4 ) DEFAULT 1 COMMENT '0-⽆效 , 1-正常 , 2-不允许借阅 ',
    26. `create_time` DATETIME DEFAULT now(),
    27. `update_time` DATETIME DEFAULT now() ON UPDATE now(),
    28. PRIMARY KEY ( `id` )
    29. ) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;
    30. -- 初始化数据
    31. INSERT INTO user_info ( user_name, PASSWORD ) VALUES ( "admin", "admin" );
    32. INSERT INTO user_info ( user_name, PASSWORD ) VALUES ( "zhangsan", "123456" );
    33. -- 初始化图书数据
    34. INSERT INTO `book_info` (book_name,author,count, price, publish) VALUES ('活着 ','Romised',100,12.2,'出版社');
    35. INSERT INTO `book_info` (book_name,author,count, price, publish) VALUES ('不活着', 'Romised', 100, 22.2, '出版社');
    36. INSERT INTO `book_info` (book_name,author,count, price, publish) VALUES ('想活着 ','Romised',100,32.2,'出版社');
    37. INSERT INTO `book_info` (book_name,author,count, price, publish) VALUES ('要活着 ','Romised',100,42.2,'出版社');

    2.引入MyBatis和MySQL驱动依赖

    修改pom.xml文件:

    1. <dependency>
    2. <groupId>org.mybatis.spring.bootgroupId>
    3. <artifactId>mybatis-spring-boot-starterartifactId>
    4. <version>2.3.1version>
    5. dependency>
    6. <dependency>
    7. <groupId>com.mysqlgroupId>
    8. <artifactId>mysql-connector-jartifactId>
    9. <scope>runtimescope>
    10. dependency>

    3.配置数据库&日志

    修改application.yml配置文件:

    1. # 数据库连接配置
    2. spring:
    3. datasource:
    4. url: jdbc:mysql://127.0.0.1:3306/book_test?characterEncoding=utf8&useSSL=false
    5. username: root
    6. password: 123456
    7. driver-class-name: com.mysql.cj.jdbc.Driver
    8. mybatis:
    9. configuration:
    10. map-underscore-to-camel-case: true #配置驼峰自动转换
    11. # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印sql语句
    12. mapper-locations: classpath:mapper/**Mapper.xml
    13. # 设置日志文件的文件名
    14. logging:
    15. file:
    16. name: logger/spring-book.log

    4.Model创建

    创建UserInfo类:

    1. @Data
    2. public class UserInfo {
    3. private Integer id;
    4. private String userName;
    5. private String password;
    6. private Integer deleteFlag;
    7. private Date createTime;
    8. private Date updateTime;
    9. }

    创建BookInfo类:

    1. @Data
    2. public class BookInfo {
    3. private Integer id;
    4. private String bookName;
    5. private String author;
    6. private Integer count;
    7. //前端展示精度
    8. @JsonFormat(shape = JsonFormat.Shape.STRING)
    9. private BigDecimal price;
    10. private String publish;
    11. private Integer status; //0-删除 1-可借阅,2-不可借阅
    12. private String statusCN;
    13. }

    5.用户登录功能实现

    约定前后端交互接口:

    1. [请求]
    2. /user/login
    3. Content-Type: application/x-www-form-urlencoded; charset=UTF-8
    4. [参数]
    5. name=zhangsan&password=123456 8
    6. [响应]
    7. true //账号密码验证正确 , 否则返回false

    浏览器给服务器发送/user/login这样的HTTP请求,服务器给浏览器返回Boolean类型的数据

    实现服务器代码

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

    创建UserController:

    1. @RequestMapping("/user")
    2. @RestController
    3. public class UserController {
    4. @Autowired
    5. private UserService userService;
    6. @RequestMapping("/login")
    7. public Boolean login(String userName, String password, HttpSession session){
    8. //校验参数是否为空
    9. if (!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)){
    10. return false;
    11. }
    12. //验证账号密码是否正确
    13. //1. 根据用户名去查找用户信息
    14. UserInfo userInfo = userService.getUserInfoByName(userName);
    15. //2. 比对密码是否正确
    16. if (userInfo==null || userInfo.getId()<=0){
    17. return false;
    18. }
    19. if (password.equals(userInfo.getPassword())){
    20. //账号密码正确
    21. //存Session
    22. userInfo.setPassword("");
    23. session.setAttribute(Constants.SESSION_USER_KEY,userInfo);
    24. return true;
    25. }
    26. return false;
    27. }
    28. }

    业务层:

    创建UserService:

    1. @Service
    2. public class UserService {
    3. @Autowired
    4. private UserInfoMapper userInfoMapper;
    5. public UserInfo getUserInfoByName(String name){
    6. return userInfoMapper.selectUserByName(name);
    7. }
    8. }

    数据层:

    创建UserInfoMapper:

    1. @Mapper
    2. public interface UserInfoMapper {
    3. /**
    4. * 根据用户名称查询用户信息
    5. * @param name
    6. * @return
    7. */
    8. @Select("select * from user_info where user_name=#{name}")
    9. UserInfo selectUserByName(String name);
    10. }

    这边使用*是为了方便观察,开发中需要挨个写出数据库字段名

     测试:

    部署程序,验证服务器是否能够正确返回数据,可以在Postman中输入URL进行测试,最好联动前端一起进行测试:

    输⼊错误的⽤户名和密码, 页面弹窗警告

     输入正确的用户名和密码, 页面正常跳转到booklist.html页面

     6.实现添加图书功能

    约定前后端交互接口:

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

    我们约定,浏览器给服务器发送book/addBook这样的HTTP请求,以from表单的形式提交数据

    务器返回处理结果, 返回""表示添加图书成功, , 返回失败信息.

    实现服务器代码 

    控制层:

    创建BookController:

    1. @Slf4j
    2. @RequestMapping("/book")
    3. @RestController
    4. public class BookController {
    5. @Autowired
    6. private BookService bookService;
    7. @RequestMapping("/getBookListByPage")
    8. public Result getBookListByPage(PageRequest pageRequest, HttpSession session){
    9. log.info("查询翻页信息, pageRequest:{}",pageRequest);
    10. //用户登录校验
    11. UserInfo userInfo = (UserInfo) session.getAttribute(Constants.SESSION_USER_KEY);
    12. if (userInfo==null|| userInfo.getId()<=0 || "".equals(userInfo.getUserName())){
    13. //用户未登录
    14. return Result.unlogin();
    15. }
    16. //校验成功
    17. if (pageRequest.getPageSize()<0 || pageRequest.getCurrentPage()<1){
    18. return Result.fail("参数校验失败");
    19. }
    20. PageResult bookInfoPageResult = null;
    21. try {
    22. bookInfoPageResult = bookService.selectBookInfoByPage(pageRequest);
    23. return Result.success(bookInfoPageResult);
    24. }catch (Exception e){
    25. log.error("查询翻页信息错误,e:{}",e);
    26. return Result.fail(e.getMessage());
    27. }
    28. }
    29. @RequestMapping(value = "/addBook", produces = "application/json")
    30. public String addBook(BookInfo bookInfo){
    31. log.info("接收到添加图书请求, bookInfo:{}",bookInfo);
    32. //参数校验
    33. if (!StringUtils.hasLength(bookInfo.getBookName())
    34. || !StringUtils.hasLength(bookInfo.getAuthor())
    35. || bookInfo.getCount()<0
    36. || bookInfo.getPrice()==null
    37. || !StringUtils.hasLength(bookInfo.getPublish())){
    38. return "参数校验失败, 请检查入参";
    39. }
    40. Integer result = bookService.addBook(bookInfo);
    41. if (result<=0){
    42. log.error("添加图书出错:bookInfo:{}",bookInfo);
    43. return "添加图书出错, 请联系管理人";
    44. }
    45. return "";
    46. }
    47. }

    业务层:

    创建BookService:

    1. @Slf4j
    2. @Service
    3. public class BookService {
    4. @Autowired
    5. private BookInfoMapper bookInfoMapper;
    6. /**
    7. * 添加图书
    8. *
    9. * @param bookInfo
    10. * @return
    11. */
    12. public Integer addBook(BookInfo bookInfo) {
    13. Integer result = 0;
    14. try {
    15. result = bookInfoMapper.insertBook(bookInfo);
    16. } catch (Exception e) {
    17. log.error("添加图书出错, e:{}", e);
    18. }
    19. return result;
    20. }
    21. }

    数据层:

    创建BookInfoMapper文件:

    1. @Mapper
    2. public interface BookInfoMapper {
    3. /**
    4. * 获取当前页的信息
    5. * @param offset
    6. * @param pageSize
    7. * @return
    8. */
    9. @Select("select * from book_info where status !=0 " +
    10. "order by id asc limit #{offset},#{pageSize}")
    11. List selectBookInfoByPage(Integer offset, Integer pageSize);
    12. @Insert("insert into book_info (book_name,author, count, price, publish, status) " +
    13. "values(#{bookName}, #{author}, #{count}, #{price},#{publish}, #{status})")
    14. Integer insertBook(BookInfo bookInfo);
    15. }

    前端代码中补全add():

    1. function add() {
    2. $.ajax({
    3. type:"post",
    4. url: "/book/addBook",
    5. data:$("#addBook").serialize(),//提交整个form表单
    6. success:function(result){
    7. if (result != null && result.code == "SUCCESS" && result.data=="") {
    8. //图书添加成功
    9. location.href = "book_list.html";
    10. }else{
    11. console.log(result.code);
    12. alert(result);
    13. }
    14. },
    15. error: function (error) {
    16. console.log(error);
    17. //用户未登录
    18. if (error != null && error.status == 401) {
    19. location.href = "login.html";
    20. }
    21. }
    22. });
    23. }

    7.实现翻页功能

    假设数据库中的数据有很多,一下子全部展示出来肯定不现实,我们可以使用分页来解决这个问题

    分页时, 数据是如何展示的呢 1: 显⽰1-10 条的数据、第2: 显⽰11-20 条的数据 3: 显⽰21-30 条的数据 以此类推...

    要想实现这个功能, 从数据库中进行分页查 ,我们要使用LIMIT关键字

    查询第一页的SQL语句:

     SELECT * FROM book_info LIMIT 0,10

    查询第二页的SQL语句:

     SELECT * FROM book_info LIMIT 10,10

    查询第三页的SQL语句:

     SELECT * FROM book_info LIMIT 20,10

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

     

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

    currentPage  当前页码默认值为1

    pageSize 每页显示条数默认值为10

    为了项⽬更好的扩展性, 通常不设置固定值,而是以参数的形式来进行传递 扩展性: 软件系统具备面对未来需求变化而进行扩展的能⼒

    比如当前需求⼀页显示10, 后期需求改为⼀页显示20, 后端代码不需要任何修改

    后端响应时, 需要响应给前端的数据

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

    total  总记录数 (用于告诉前端显示多少页, 显示页数:(total+pageSize-1)/pageSize

    显示页数totalPage 计算公式为 : total % pagesize == 0 ? total / pagesize : (total / pagesize)+1 ;

    pagesize - 1 total / pageSize 的最⼤的余数 ,所以(total + pagesize -1) / pagesize就得到总页数

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

    翻页请求对象:

    1. @Data
    2. public class PageRequest {
    3. private int currentPage = 1; // 当前页
    4. private int pageSize = 10; // 每页中的记录数
    5. private int offset;
    6. public int getOffset() {
    7. return (currentPage-1) * pageSize;
    8. }
    9. }

    翻页列表结果类:

    1. import lombok.Data;
    2. import java.util.List;
    3. @Data
    4. public class PageResult {
    5. private int total;//所有记录数
    6. private List records; // 当前页数据
    7. public PageResult(Integer total, List records) {
    8. this.total = total;
    9. this.records = records;
    10. }
    11. }

     返回结果中, 使⽤泛型来定义记录的类型 

    约定前后端交互接口

    1. [请求]
    2. /book/getListByPage?currentPage=1
    3. Content-Type: application/x-www-form-urlencoded; charset=UTF-8
    4. [参数]
    5. [响应]
    6. Content-Type: application/json 10
    7. {
    8. "total": 25,
    9. "records": [{
    10. "id": 25,
    11. "bookName": "图书21",
    12. "author": "作者2",
    13. "count": 29,
    14. "price": 22.00,
    15. "publish": "出版社1",
    16. "status": 1,
    17. "statusCN": "可借阅 "
    18. }, {
    19. ......
    20. } ]
    21. }

    我们约定,浏览器给服务器发送book/getListByPage这样的HTTP请求,通过currentPage参数告诉服务器当前请求为第几页的数据, 后端根据请求参, 返回对应页的数据

    实现服务器代码 

    控制层:

    完善 BookController:

    1. @Slf4j
    2. @RequestMapping("/book")
    3. @RestController
    4. public class BookController {
    5. @Autowired
    6. private BookService bookService;
    7. @RequestMapping("/getBookListByPage")
    8. public Result getBookListByPage(PageRequest pageRequest, HttpSession session){
    9. log.info("查询翻页信息, pageRequest:{}",pageRequest);
    10. // //用户登录校验
    11. // UserInfo userInfo = (UserInfo) session.getAttribute(Constants.SESSION_USER_KEY);
    12. // if (userInfo==null|| userInfo.getId()<=0 || "".equals(userInfo.getUserName())){
    13. // //用户未登录
    14. // return Result.unlogin();
    15. // }
    16. //校验成功
    17. if (pageRequest.getPageSize()<0 || pageRequest.getCurrentPage()<1){
    18. return Result.fail("参数校验失败");
    19. }
    20. PageResult bookInfoPageResult = null;
    21. try {
    22. bookInfoPageResult = bookService.selectBookInfoByPage(pageRequest);
    23. return Result.success(bookInfoPageResult);
    24. }catch (Exception e){
    25. log.error("查询翻页信息错误,e:{}",e);
    26. return Result.fail(e.getMessage());
    27. }
    28. }
    29. @RequestMapping(value = "/addBook", produces = "application/json")
    30. public String addBook(BookInfo bookInfo){
    31. log.info("接收到添加图书请求, bookInfo:{}",bookInfo);
    32. //参数校验
    33. if (!StringUtils.hasLength(bookInfo.getBookName())
    34. || !StringUtils.hasLength(bookInfo.getAuthor())
    35. || bookInfo.getCount()<0
    36. || bookInfo.getPrice()==null
    37. || !StringUtils.hasLength(bookInfo.getPublish())){
    38. return "参数校验失败, 请检查入参";
    39. }
    40. Integer result = bookService.addBook(bookInfo);
    41. if (result<=0){
    42. log.error("添加图书出错:bookInfo:{}",bookInfo);
    43. return "添加图书出错, 请联系管理人";
    44. }
    45. return "";
    46. }
    47. @RequestMapping("/queryBookInfoById")
    48. public BookInfo queryBookInfoById(Integer bookId){
    49. // long start = System.currentTimeMillis();
    50. log.info("根据ID查询图书, bookId:"+bookId);
    51. BookInfo bookInfo = null;
    52. try {
    53. bookInfo = bookService.queryBookInfoById(bookId);
    54. }catch (Exception e){
    55. log.error("查询图书失败, e:{}",e);
    56. }
    57. // long end = System.currentTimeMillis();
    58. // log.info("queryBookInfoById 执行耗时: "+ (end-start) + "ms");
    59. return bookInfo;
    60. }
    61. @RequestMapping(value = "/updateBook", produces = "application/json")
    62. public String updateBook(BookInfo bookInfo){
    63. log.info("接收到更新图书的请求, bookInfo:{}",bookInfo);
    64. Integer result = bookService.updateBook(bookInfo);
    65. if (result == 0){
    66. log.error("更新图书失败, 请联系管理员");
    67. return "更新图书失败, 请联系管理员";
    68. }
    69. return "";
    70. }
    71. @RequestMapping(value = "/batchDelete", produces = "application/json")
    72. public String batchDelete(@RequestParam List ids){
    73. log.info("接收请求, 批量删除图书, 图书ID:{}",ids);
    74. Integer result = bookService.batchDelete(ids);
    75. if (result<=0){
    76. log.error("批量删除失败, ids:{}",ids);
    77. return "批量删除失败, 请联系管理员";
    78. }
    79. return "";
    80. }
    81. }

    业务层:

    BookService

    1. @Slf4j
    2. @Service
    3. public class BookService {
    4. @Autowired
    5. private BookInfoMapper bookInfoMapper;
    6. public PageResult selectBookInfoByPage(PageRequest pageRequest) {
    7. if (pageRequest == null) {
    8. return null;
    9. }
    10. //获取总记录数
    11. Integer count = bookInfoMapper.count();
    12. //获取当前记录
    13. List bookInfos = bookInfoMapper.selectBookInfoByPage(pageRequest.getOffset(), pageRequest.getPageSize());
    14. if (bookInfos != null && bookInfos.size() > 0) {
    15. for (BookInfo bookInfo : bookInfos) {
    16. //根据status 获取状态的定义
    17. bookInfo.setStatusCN(BookStatusEnum.getNameByCode(bookInfo.getStatus()).getName());
    18. }
    19. }
    20. return new PageResult<>(bookInfos, count, pageRequest);
    21. }
    22. /**
    23. * 添加图书
    24. *
    25. * @param bookInfo
    26. * @return
    27. */
    28. public Integer addBook(BookInfo bookInfo) {
    29. Integer result = 0;
    30. try {
    31. result = bookInfoMapper.insertBook(bookInfo);
    32. } catch (Exception e) {
    33. log.error("添加图书出错, e:{}", e);
    34. }
    35. return result;
    36. }
    37. public BookInfo queryBookInfoById(Integer id) {
    38. return bookInfoMapper.queryBookInfoById(id);
    39. }
    40. /**
    41. * 更新图书
    42. * @param bookInfo
    43. * @return
    44. */
    45. public Integer updateBook(BookInfo bookInfo) {
    46. Integer result = 0;
    47. try {
    48. result = bookInfoMapper.updateBook(bookInfo);
    49. } catch (Exception e) {
    50. log.error("更新图书失败, e:{}", e);
    51. }
    52. return result;
    53. }
    54. public Integer batchDelete(List ids){
    55. Integer result =0;
    56. try {
    57. result = bookInfoMapper.batchDelete(ids);
    58. }catch (Exception e){
    59. log.error("批量删除图书失败, ids:{}",ids);
    60. }
    61. return result;
    62. }
    63. }

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

     图书状态: 图书状态和数据库存储的status有⼀定的对应关系

    如果后续状态码有变动, 我们需要修改项目中所有涉及的代码, 这种情况, 通常采用枚举类来处理映射关系

    数据层:

    翻页查询SQL

    1. @Mapper
    2. public interface BookInfoMapper {
    3. /**
    4. * 获取当前页的信息
    5. * @param offset
    6. * @param pageSize
    7. * @return
    8. */
    9. @Select("select * from book_info where status !=0 " +
    10. "order by id asc limit #{offset},#{pageSize}")
    11. List selectBookInfoByPage(Integer offset, Integer pageSize);
    12. /**
    13. * 获取总记录数
    14. * @return
    15. */
    16. @Select("select count(1) from book_info where status !=0")
    17. Integer count();
    18. }

     实现客户端代码:

    1. function getBookList() {
    2. $.ajax({
    3. type: "get",
    4. url: "/book/getBookListByPage" + location.search,
    5. success: function (result) {
    6. //真实的前端处理逻辑, 要比咱们代码复杂
    7. if (result.code == "UNLOGIN") {
    8. location.href = "login.html";
    9. return;
    10. }
    11. var finalHtml = "";
    12. //加载列表
    13. var pageResult = result.data;
    14. for (var book of pageResult.records) {
    15. //根据每一条记录去拼接html, 也就是一个tr
    16. finalHtml += '';
    17. finalHtml += 'id + '" id="selectBook" class="book-select">';
    18. finalHtml += '' + book.id + '';
    19. finalHtml += '' + book.bookName + '';
    20. finalHtml += '' + book.author + '';
    21. finalHtml += '' + book.count + '';
    22. finalHtml += '' + book.price + '';
    23. finalHtml += '' + book.publish + '';
    24. finalHtml += '' + book.statusCN + '';
    25. finalHtml += '
      ';
    26. finalHtml += 'id + '">修改';
    27. finalHtml += 'id + ')">删除';
    28. finalHtml += '
      ';
  • }
  • $("tbody").html(finalHtml);
  • //翻页信息
  • $("#pageContainer").jqPaginator({
  • totalCounts: pageResult.total, //总记录数
  • pageSize: 10, //每页的个数
  • visiblePages: 5, //可视页数
  • currentPage: pageResult.pageRequest.currentPage, //当前页码
  • first: '
  • 首页
  • ',
  • prev: '
  • 上一页<\/a><\/li>',
  • 下一页<\/a><\/li>',
  • 最后一页<\/a><\/li>',
  • {{page}}<\/a><\/li>',
  • 相关阅读:
    淘宝商品详情API接口(H5端和APP端),淘宝详情页,商品属性接口,商品信息查询
    Nacos注册中心11-Server端(处理服务发现请求)
    什么是 eCPM?它与 CPM 有何不同?
    面试经验分享 | 驻场安全服务工程师面试
    Vue的学习之安装Vue
    阻塞车间调度
    article-码垛机器人admas仿真
    重制版 day 18 CSV和excel文件操作
    Android设计模式--原型模式
    测试/开发程序员的思考,突破变得更强......
  • 原文地址:https://blog.csdn.net/Romised/article/details/139703716