ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot+MyBatis+JSP图书管理系统实战:避坑与进阶技巧

SpringBoot+MyBatis+JSP图书管理系统实战:避坑与进阶技巧 简介这是一套基于SpringBoot、MyBatis与JSP构建的图书管理系统完整项目源码面向具备Java Web基础、希望深入理解企业级开发流程的开发者与在校学生。系统覆盖图书增删改查、分类管理、借阅归还、分页查询等核心业务采用MVC架构整合Spring Security实现权限控制并以RESTful风格提供后端接口适合作为课程设计、毕业设计或技术练手的参考方案。资源包共1192个文件包含648个class编译文件、173个jsp页面、148个xml配置、65个jar依赖及51个java源码另有properties配置、js与css静态资源等压缩包约95.3MB目录结构完整便于按模块查阅与二次开发。目前已有2207人学习下载。通过研读源码读者可掌握SpringBoot自动配置、MyBatis映射与事务管理、JSP视图渲染及分页插件等关键技术的落地方式积累从数据库设计到部署运行的完整实践经验。1. SpringBoot MyBatis JSP 图书管理系统为什么这套“老技术栈”还在被反复搜索如果你在招聘网站搜 Java 后端岗位会发现一个有意思的现象大量中小企业的内部系统、校园项目、外包交付件仍然跑在 SpringBoot MyBatis JSP 这套组合上。图书管理系统就是其中最典型的载体——它足够小能一周跑通又足够完整能覆盖增删改查、分页、事务、登录拦截、文件上传这些真实需求。很多人第一次接触 SpringBoot 整合 JSP 时都会卡在同一个地方明明 Controller 返回了视图名浏览器却直接下载了一个文件或者报 404。这不是你代码写错了而是 JSP 在 SpringBoot 里的定位和 Thymeleaf 完全不同它依赖外部 Servlet 容器做编译打成 jar 包时默认根本找不到页面。这篇笔记就围绕这个图书管理系统把依赖怎么配、目录怎么放、MyBatis 分页和缓存怎么接、打包为什么必须用 war 这些事一次讲透。适合正在做课程设计、接手老项目维护、或者想用最小成本验证一个管理后台的开发者。2. 把 JSP 塞进 SpringBoot依赖、目录与视图解析器的三处硬配置2.1 为什么 SpringBoot 默认不支持 JSP以及必须补哪几个依赖SpringBoot 内置的 Tomcat 在启动时对 JSP 的支持是“有条件”的。官方文档写得很清楚JSP 在可执行 jar 中有限制推荐打成 war 包部署到外部容器。原因在于 JSP 需要先被 Jasper 编译器编译成 Servlet 类而 jar 包运行时的工作目录结构不满足 Jasper 的扫描路径。所以第一步不是写代码而是把工程类型和依赖摆正。在pom.xml里打包方式必须声明为 warpackagingwar/packaging紧接着补三个依赖。第一个是 SpringBoot 内置 Tomcat 对 JSP 的支持包第二个是 JSTL 标签库第三个是 Jasper 编译器。缺任何一个页面要么 404要么标签不解析!-- 让内置 Tomcat 支持 JSP 解析 -- dependency groupIdorg.apache.tomcat.embed/groupId artifactIdtomcat-embed-jasper/artifactId scopeprovided/scope /dependency !-- JSTL 标签库页面里用 c:forEach 必须靠它 -- dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency !-- Jasper 编译器负责把 .jsp 编译成 Servlet -- dependency groupIdorg.apache.tomcat/groupId artifactIdtomcat-jasper/artifactId version9.0.56/version scopeprovided/scope /dependency这里有个参数细节tomcat-embed-jasper和tomcat-jasper的scope都建议设为provided因为最终 war 包会部署到外部 Tomcat容器自己带了这些类打进去反而可能引起版本冲突。JSTL 的 1.2 是稳定版本不要用 1.11.1 的 URI 和 1.2 不同页面标签会报错。2.2 目录结构JSP 到底该放在哪里这是新手翻车最多的地方。SpringBoot 的静态资源默认从src/main/resources/static找但 JSP 不是静态资源它需要放在src/main/webapp/WEB-INF/jsp/下。注意是webapp不是resources。如果你用 IDEA 新建项目时没看到webapp目录需要手动创建并在项目结构里把它标记为 Web 资源目录。一个能跑通的图书管理系统目录长这样src ├── main │ ├── java │ │ └── com/example/book │ │ ├── controller │ │ ├── service │ │ ├── mapper │ │ └── entity │ ├── resources │ │ ├── application.yml │ │ └── mapper │ │ └── BookMapper.xml │ └── webapp │ └── WEB-INF │ └── jsp │ ├── login.jsp │ ├── book_list.jsp │ └── book_edit.jspWEB-INF这层目录很关键。放在它下面的 JSP 无法被浏览器直接访问必须经过 Controller 转发这正好符合 MVC 的意图也避免用户绕过登录直接敲 URL 看到页面。2.3 视图解析器配置prefix 和 suffix 怎么写才不 404在application.yml里配置视图解析器的前缀和后缀spring: mvc: view: prefix: /WEB-INF/jsp/ suffix: .jspController 里返回book_listSpringMVC 就会拼成/WEB-INF/jsp/book_list.jsp。这里有两个容易写错的地方前缀末尾的斜杠不能少后缀开头的点不能少。少一个斜杠路径变成/WEB-INF/jspbook_list.jsp直接 404后缀写成jsp没有点同样找不到。提示如果你用的是 IDEA改完application.yml后建议执行一次mvn clean因为旧的编译产物可能还带着之前的配置导致你改了没生效白白排查半小时。3. MyBatis 接图书管理分页插件、XML 映射与二级缓存的落地参数3.1 分页插件 PageHelper 的接入与三个必调参数图书列表页一定要分页否则几百条数据一次性渲染JSP 页面会卡到怀疑人生。MyBatis 本身不带分页常见做法是引入 PageHelper。在pom.xml加依赖dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper-spring-boot-starter/artifactId version1.4.6/version /dependency然后在application.yml里配置pagehelper: helper-dialect: mysql reasonable: true support-methods-arguments: true params: countcountSql四个参数逐个说清楚。helper-dialect指定数据库方言写mysql后插件会自动把limit拼到 SQL 后面如果你用的是 Oracle 或达梦这里要换成对应方言否则分页 SQL 语法直接报错。reasonable设为true后页码小于 1 会查第一页大于总页数会查最后一页避免前端传了脏参数导致空结果。support-methods-arguments允许分页参数从方法参数里自动识别配合params里的countcountSql可以让插件自动生成 count 查询不用你手写一条统计总数的 SQL。Service 层调用时分页代码必须紧挨着查询语句public PageInfoBook listBooks(int pageNum, int pageSize, String keyword) { // 开启分页这行必须放在查询方法的前一行 PageHelper.startPage(pageNum, pageSize); ListBook books bookMapper.selectByKeyword(keyword); // 用 PageInfo 包装后JSP 里可以直接拿到总页数、总记录数 return new PageInfo(books); }逻辑说明startPage只对紧随其后的第一条 MyBatis 查询生效如果你中间插了别的查询分页会作用到错误的语句上。参数说明pageNum是当前页码从 1 开始pageSize是每页条数图书列表一般设 10 到 20太大前端渲染慢太小翻页频繁。3.2 BookMapper.xml 的编写与 resultMap 映射图书实体和数据库字段往往不是一一对应比如数据库用book_name实体用bookName。这时候需要resultMap显式映射resultMap idBookResultMap typecom.example.book.entity.Book id columnid propertyid/ result columnbook_name propertybookName/ result columnauthor propertyauthor/ result columnprice propertyprice/ result columnstock propertystock/ /resultMap select idselectByKeyword resultMapBookResultMap select id, book_name, author, price, stock from book where if testkeyword ! null and keyword ! book_name like concat(%, #{keyword}, %) /if /where order by id desc /selectwhere标签会自动处理第一个条件前的and避免手写where 11这种不优雅的写法。concat是 MySQL 的字符串拼接函数配合#{}占位符能防止 SQL 注入。注意#{}和${}的区别#{}会预编译成问号${}是直接字符串替换图书名称这种用户输入必须用#{}用${}就是给注入留后门。3.3 二级缓存的开启条件与失效场景MyBatis 二级缓存默认关闭开启需要在 Mapper.xml 顶部加一行cache evictionLRU flushInterval60000 size512 readOnlytrue/参数含义eviction是淘汰策略LRU 表示最近最少使用flushInterval是刷新间隔单位毫秒60000 表示一分钟清一次size是最多缓存对象数readOnly设为 true 时返回缓存对象的同一实例性能好但要求实体不能被修改。二级缓存生效有三个前提实体类必须实现Serializable接口必须在同一个 SqlSessionFactory 范围内执行过 commit 或 close 后缓存才真正写入。图书管理系统的图书详情查询适合开缓存但库存扣减这类写操作频繁的接口不要开否则会出现读到旧库存的玄学问题。血泪经验是只要涉及库存、余额这类字段二级缓存能不开就不开省下的那点查询时间不值得用数据一致性去换。4. 图书管理系统的避坑与排查五个真实翻车现场4.1 页面返回 404但 Controller 日志显示已执行现象浏览器访问/book/list后端日志打印了查询结果但页面显示 404。原因JSP 文件没有放在src/main/webapp/WEB-INF/jsp/下或者视图解析器的 prefix 写成了/WEB-INF/jsp少了末尾斜杠。解决检查目录层级确认webapp目录被 IDEA 识别为 Web 资源根目录。在项目结构里看webapp图标是否带蓝色小点没有的话右键标记为 Web 资源目录。然后核对application.yml里 prefix 的斜杠。4.2 打成 jar 包后 JSP 全部失效现象IDEA 里运行正常mvn package生成 jar 后java -jar启动访问页面全部 404 或下载文件。原因SpringBoot 可执行 jar 不包含webapp目录下的 JSP内置 Tomcat 在 jar 模式下对 JSP 的支持受限。解决把packaging改成war部署到外部 Tomcat。如果必须用 jar只能放弃 JSP 改用 Thymeleaf没有折中方案。这是 JSP 在 SpringBoot 里的硬边界不要试图用配置绕过。4.3 PageHelper 分页后 total 始终为 0现象列表数据能出来但PageInfo.getTotal()返回 0前端分页条显示不出总页数。原因PageHelper.startPage()之后执行的查询返回类型不是List或者中间被其他查询打断导致插件没有正确拦截。解决确认startPage紧挨着目标查询且查询方法返回List。如果用了Select注解方式PageHelper 同样支持但注解 SQL 里不要自己写limit否则插件再拼一次会语法错误。4.4 JSP 页面中文乱码现象图书名称在数据库里正常页面上显示问号或乱码。原因JSP 文件本身的编码、pageEncoding、以及响应头的 Content-Type 三者不一致。解决在 JSP 第一行加% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %同时确认 IDEA 的文件编码设置为 UTF-8。数据库连接 URL 上加useUnicodetruecharacterEncodingutf8。三处都对齐乱码才会消失。4.5 MyBatis 的 update 执行慢日志显示影响行数为 0现象修改图书信息时接口响应很慢日志里 update 语句执行了但影响行数是 0。原因传入的实体主键为 null或者 where 条件用了#{}但参数名对不上导致 SQL 实际匹配不到记录。另外如果表数据量大且 where 字段没有索引也会慢。解决在 Mapper 接口参数上加Param注解明确参数名update 语句的 where 条件字段确认有索引。图书管理系统的 book 表一般数据量不大慢多半是参数没传对先看日志里打印的 SQL 参数值。5. 进阶技巧用拦截器 JSTL 把登录和列表页做扎实登录拦截是图书管理系统绕不开的一环。常见做法是写一个HandlerInterceptor在preHandle里检查 session 中是否有用户信息没有就重定向到登录页。注册拦截器时要注意放行登录接口和静态资源Configuration public class WebConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns(/**) // 登录页、登录接口、静态资源必须放行否则会死循环 .excludePathPatterns(/login, /doLogin, /css/**, /js/**); } }参数说明addPathPatterns(/**)拦截所有请求excludePathPatterns里列出的路径不拦截。登录接口如果也被拦截用户永远登不进去这是新手常见的死循环。列表页用 JSTL 渲染时c:forEach配合PageInfo的list属性% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table c:forEach items${pageInfo.list} varbook tr td${book.bookName}/td td${book.author}/td td${book.price}/td td a href/book/edit/${book.id}编辑/a /td /tr /c:forEach /tablepageInfo.list是当前页数据pageInfo.total是总记录数pageInfo.pages是总页数。分页条可以用c:forEach从 1 循环到pageInfo.pages生成页码链接。一个验证技巧在浏览器开发者工具里看 Network确认每次翻页请求的pageNum参数确实变了且返回的 HTML 里表格行数等于pageSize。如果行数不对回到 Service 层检查startPage的位置。我自己的习惯是每接一个新模块先把分页和登录拦截跑通再写业务逻辑因为这两块一旦出问题后面所有页面都受影响。图书管理系统虽然简单但把 JSP 的目录、war 打包、PageHelper 的调用顺序这三件事做对基本就成功了一大半。希望帮到你。本文还有配套的精品资源点击获取
返回列表