多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Servlet高校图书管理系统源码解析:从环境配置到核心模块实战

Servlet高校图书管理系统源码解析:从环境配置到核心模块实战 很多同学拿着这套“servlet高校图书管理信息系统”的源码找到我第一句话都是学长代码我打开看了下一步该干什么还有同学直接双击运行发现打不开跑来问我是不是源码有问题。其实问题不在源码在于大部分人根本没搞明白这套东西的运行逻辑——它不像现在前后端工程那样 npm install 一把梭而是需要你手工伺候好 Servlet 容器、数据库和你本机的环境。这篇文章就围绕这套课题最常用的图书管理核心功能展开从为什么选 Servlet 讲到怎么把代码跑起来再带你逐层拆开登录、借书、还书、图书检索这些模块的实现方式。不管你拿它是做 JavaWeb 课程设计、毕业设计还是想复习一遍 Servlet 的本质这篇文章都能让你少走弯路。1. 先别急着跑项目想清楚这门课设到底在考什么1.1 高校图书管理系统的业务边界其实是固定的市面上打着“高校图书管理信息系统”名号的课程设计很多但你仔细对比会发现它们从功能层面几乎都是一套模板出来的。无非是三类对象学生读者、图书、借阅记录。再往上加一层管理员的系统操作。我先帮你把功能边界理一理你心里有数之后看代码会快很多读者管理新增学生信息、查询读者、删除读者、修改读者资料。这一块对应到数据库里的 t_reader 或者 user 表。图书管理录入图书书名、作者、出版社、ISBN、分类、总册数、库存支持按书名模糊检索、按分类筛选。对应 t_book 表。借阅管理核心是借书和还书两条链路借书要检查库存和学生是否已有逾期记录还书要更新库存并且计算是否超期。系统管理管理员登录、密码修改、读者账号启停。你会发现这套系统真正考的不是业务有多复杂而是你能不能把这几张表的 CRUD 操作和状态流转讲清楚。借书的时候为什么库存减少、还书的时候为什么库存增加、逾期天数怎么算这些就是答辩时老师张口就会问的问题。1.2 为什么课程设计要选 Servlet而不直接上 Spring Boot很多学生拿到这套古早技术栈的第一反应是都什么年代了还学 Servlet老老实实用 Spring Boot 不香吗这个问题我在带课设的时候被问过无数次。答案其实很简单Servlet 是你理解 JavaWeb 全流程的基础。Spring Boot 帮你把 Tomcat 内嵌了、把请求分发封装了、把对象的创建和注入接管了但你根本不知道一次 HTTP 请求是怎么被容器接收、怎么被解析成对象、又是怎么经过过滤器链条最终到达我们写的业务代码里的。而这些正是答辩老师判断你有没有真做过项目的核心依据。另外从学校课程的编排逻辑来看数据库、Java 基础、Web 开发这三门课往往是同期或前后脚开的Servlet JSP JDBC 这种组合恰好能把三门课的知识点全部串起来。你用 Spring Boot 做课设数据库那门课的老师还敢给你高分但 Web 课的老师大概率会追问你 MVC 的底层执行路径到时候答不上来反而尴尬。1.3 Servlet 课程设计的命名规律和垃圾源码如何识别标题里带了“附源码 26779”这类编号圈内人都懂这是某类源码分享平台自动生成的编号。这类源码包质量参差不齐有的很干净有的则是从 GitHub 搬运后改了改数据库字段名就拿出来二次分发。怎么在拿到源码后快速判断它值不值得深入我一般用三分钟做以下检查看 lib 目录必须有 servlet-api.jar、jstl.jar、mysql-connector-java.jar 这几个关键依赖。缺了任何一个你的项目都没法直接编译运行。看文件结构是否严格按 MVC 分层有没有把 Java 业务代码直接埋在 JSP 里。如果你的 package 里全是 com.book.servlet 和 com.book.dao那说明写出这套代码的人至少懂分层如果所有逻辑都在 JSP 里这个源码可改造成本很高。看数据库脚本正常项目会提供 book.sql 或者 init.sql里面至少包含三张以上的核心表。只有两张表的比如只有用户和图书说明借还逻辑就没做完整。这套源码我基于上述标准大致评估过框架完整包名规范三层结构比较清晰适合作为课程设计的底子去改。2. 把源码跑起来的完整操作链环境、数据库、Tomcat 一样都不能省2.1 环境准备阶段最容易被忽略的三个细节先把环境版本对齐以下是我验证过在 Windows 上最稳的一套组合JDK1.8 或 8u 系列不要一上来装 JDK 17。很多老项目的 web.xml 头声明、JSP 编译方式在 JDK 8 上兼容性最好。数据库MySQL 5.7 或 8.0 均可。注意8.0 的 JDBC 驱动和 5.7 的驱动类名稍微有点区别连接串写法也不同这套老源码里通常给的是 5.x 驱动写法。Tomcat8.5.x 或 9.0.x避开 Tomcat 10。因为 Tomcat 10 把包名从 javax.servlet 换成了 jakarta.servlet旧代码直接跑会报包找不到装半天也是白费。IDEIDEA 2020 以后的版本基本都能正常识别 Web 项目只需在 Project Structure 里把 Web Facet 和 Artifact 配置一遍。JDBC 驱动用 mysql-connector-java 5.1.49 搭配 MySQL 5.7 最稳用 8.0.33 搭配 MySQL 8.0 也可以。建议直接检查 lib 里已有的驱动版本不轻易换。三个最容易翻车的细节第一Tomcat 的端口被占用是日常操作项目配置里写的是 8080 的话先检查本机有没有其他服务占着这个端口。第二数据库用户名默认是 root密码默认是 root 或者 123456但每个人本机的密码都不一样一定记得去改 DBUtil 里的连接配置。第三MySQL 的时区问题连接串里最好加上 serverTimezoneAsia/Shanghai否则跑起来日期相关的查询会报错。2.2 从解压到页面出现一条完整的启动路径第一步解压源码确认目录结构。正常你会看到 src存放 Java 代码、web或者 WebContent、webapp 目录存放 JSP 页面和静态资源、数据库脚本文件.sql、以及项目配置文件。第二步用 IDEA 打开项目。这里建议选择 Open 然后直接选到源码根目录IDEA 会尝试自动识别模块类型。如果你的项目没有自动变成 Web 工程需要手动操作File - Project Structure - Facets - 添加 Web把 Web 资源目录指向 web 文件夹然后在 Artifacts 里创建 Web Application: Exploded 包。第三步初始化数据库。在 MySQL 里执行脚本推荐用终端或者 Navicat先创建一个数据库名比如库名就叫 bookdb然后执行 sql 文件导入表结构和初始数据。第四步修改数据库连接配置。找到 com.book.util 包下的 DBUtil 类核心就是改三行private static final String URL jdbc:mysql://localhost:3306/bookdb?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USERNAME root; private static final String PASSWORD 你的数据库密码;这里我特别提醒CharacterEncoding 参数千万别省图书管理系统里有大量中文数据录入和查询编码不对轻则乱码重则连登录都进不去。第五步配置 Tomcat。Run - Edit Configurations - 添加 Tomcat Server - Local在 Deployment 里把刚才建好的 Artifact 加进去Application context 建议直接写/这样访问路径就是http://localhost:8080/不用带项目名省得记错。第六步启动项目。正常情况下控制台会输出 Tomcat 启动日志浏览器输入http://localhost:8080/跳转到登录页。如果跳到了 404 页面优先检查 Deployment 的 Application context 跟你浏览器访问的地址是否一致。2.3 常见报错排查表照着查比你瞎试快得多报错现象根本原因处理方法404 找不到页面Artifact 配置错了或 context 路径不对检查 Deployment 里的 Application context 是否和访问路径一致500 异常控制台报 ClassNotFoundExceptionlib 里的 jar 没有被部署到 Tomcat打开 Artifacts把 lib 目录加入 WEB-INF/lib 输出Access denied for user root数据库密码配置错误回 DBUtil 确认密码注意区分 root 的密码和空密码Unknown database bookdbSQL 脚本没执行或库名不一致在 MySQL 里执行建库语句或者改 URL 里的库名中文乱码页面编码和数据库编码不一致统一 characterEncodingutf8且 JSP 页面 contentType 用 UTF-8Communications link failureMySQL 8 的驱动与旧 MySQL 5 驱动混用根据数据库版本更换对应版本的驱动 jar这些报错我几乎每次跑老项目都会碰到一轮遇到别慌优先看控制台最底部的异常堆栈它告诉你的信息远比浏览器页面显示的要多得多。3. 核心模块代码拆解登录、借书、还书是怎么一层层落地的3.1 登录模块不只是查一次数据库那么简单这套系统的登录逻辑是标准的 MVC 三层JSP 发起请求Servlet 接收参数并调用 ServiceService 调 DAO 查询数据库最后把结果返回给页面。我看过不少学生写的登录代码很喜欢在 Servlet 里直接写一大段 JDBC 代码图省事。这样虽然也能跑通但答辩的时候老师问一句“你的代码分层在哪里”你很难自圆其说。这套源码里比较好的点在它把登录分成了两层验证第一层是没连接数据库前的参数校验——用户名和密码非空校验。这层放在 Servlet 里做最快不用浪费数据库连接。第二层是连接数据库去查用户表。注意这里用的是 PreparedStatement不是 Statement原因后面专门讲。登录成功以后最关键的是把用户信息放进 SessionHttpSession session request.getSession(); session.setAttribute(currentUser, user); session.setAttribute(role, user.getRole());Session 的目的是让后续的每个页面请求都能识别出“我是谁”这和 HTTP 本身无状态的特性是对应的。一个请求处理完了服务器就忘了你是谁只有 Session 这个存到服务器内存或持久化的小本本能跨请求记住状态。登录失败则要区分“用户不存在”“密码错误”两种提示。很多课设把这两者合并成“用户名或密码错误”从安全角度说是合理的——防止别人通过报错信息猜测合法用户名——但从课程设计演示角度分开提示反而能展示你处理业务分支的能力。这个看个人取舍我是建议你把两种情况分开答辩时更好讲。3.2 图书模糊检索里的 SQL 拼接坑和 PreparedStatement 为什么是标准答案图书管理的检索功能是另一个答辩高频考点。需求通常是输入关键字按书名模糊匹配同时还能按分类缩小范围。不少同学的思路很直接既然是模糊查询那就把 SQL 直接拼出来String sql SELECT * FROM t_book WHERE bookName LIKE % keyword %;这种写法最大的问题不是性能而是安全。如果用户在搜索框里输入% OR 11拼接出来的 SQL 就变成SELECT * FROM t_book WHERE bookName LIKE %% OR 11%这个条件永远为真查询结果会返回全表数据。放在登录场景里甚至可以通过构造万能密码绕过登录。这就是 SQL 注入。PreparedStatement 的好处是预编译它会把 SQL 的结构先发给数据库字段值通过参数占位符传递数据库只把占位符当作具体的数据值来匹配而不是把它当作 SQL 语句的一部分执行String sql SELECT * FROM t_book WHERE bookName LIKE ?; PreparedStatement pstmt conn.prepareStatement(sql); pstmt.setString(1, % keyword %); ResultSet rs pstmt.executeQuery();这里有个细节特别值得注意模糊查询的百分号要拼在参数里不能拼在 SQL 模板里。我见过学生写成LIKE ?%或者?%之类的变体结果查询永远查不出来或者把%写进 SQL 模板导致参数含义混乱。你记住一个原则LIKE ?是模板% keyword %是参数两者分工明确不会错。3.3 借书和还书的事务边界为什么需要 commit 和 rollback借书的业务链路看起来简单判断库存是否大于零库存减一插入一条借阅记录。但这里有一个隐藏的事务问题。假设学生把书借走了你执行了“库存减一”但紧接着“插入借阅记录”执行失败。此时如果没用事务那数据库里就会出现一个诡异的状态库存少了但没有对应的借阅记录。对图书馆场景来说这意味着书“凭空消失”了。正确的做法是让两步操作共享同一个数据库连接并且放在同一个事务里conn.setAutoCommit(false); try { // 更新库存 // 插入借阅记录 conn.commit(); } catch (Exception e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); }这套源码里的事务处理在 Dao 层实现使用的是最原始的 JDBC Connection 事务控制。Spring 的开发者在看到这种代码时可能会觉得“老土”但恰恰是这种最直白的代码把“事务的边界在哪”“什么时候该 commit”“异常了怎么回滚”展示得淋漓尽致。你还书的时候类似库存要加一借阅记录的状态要更新为已归还这两步同样需要事务保护。还有一个细节是还书时计算逾期天数。常规实现是拿当前日期减去应还日期如果大于零就是逾期。计算用的是 java.util.Date 或者 java.time.LocalDate这套老代码里一般用的是前者。你如果做二次开发我建议顺手换成 LocalDate日期时间 API 用起来更顺手也可以避免 java.util.Date 里月份从 0 开始这种诡异设计。4. 数据库设计是一场考试表结构、字段约束和“为什么这么建”4.1 三张核心表的结构和它们之间的血缘关系图书管理系统的数据库设计如果只让你向老师介绍三张表那就是以下的角色图书表t_book图书的静态信息。读者表t_reader读者的基本信息。借阅记录表t_borrow记录谁在什么时间借了什么书、什么时候还了没有。借阅记录表是连接图书表和读者表的中间表它同时持有读者 ID 和图书 ID 两个外键。为什么不能只在图书表里加一个“当前借阅人”字段因为一本书在历史上有过无数个借阅人如果你只在图书表里记录当前借阅人那“查询某个学生历史上借过哪些书”就完全无从谈起。关系型数据库设计的核心之一就是通过独立的关系表来保留“过程数据”而不是只保留“当前状态”。我见过有学生的设计是把借阅记录直接塞进读者表用“借阅历史”字段存逗号分隔的书 ID。这种设计在答辩时基本属于送命题老师随便问一个“你能查出来某本书被谁借过几次”你的 SQL 就只能做无底洞式的字符串拼接。尽早用外键关联才是正规做法。4.2 库存字段的意义和为什么不建议每次都算一遍在借数量图书表里通常会有两个关键数字总册数和库存量。有学生会提出疑问库存量不等于总册数减去在借数吗那我何必额外维护一个库存字段直接在查询的时候动态计算行数不就完了理论上的确可以但你得想清楚使用场景。图书管理系统最频繁的操作就是借书这个动作发生一次就要执行一次“计算总册数减去在借数”的查询再判断结果够不够。假如你这套系统的并发不高那没问题但如果你后面想优化预计算的库存字段配合行级锁能让“判断库存并扣减”变成一条 UPDATE 语句完成效率和可维护性都更好。这里的字段约束也很讲究总册数total和库存stock应该是整数且大于等于零用 Unsigned Int 存储。借阅状态字段一般用 0 表示在借、1 表示已还而不是直接存字符串理由很简单字符串字段不易建索引、冗余更高、容易写错。用 tinyint 存状态位配合注释是行业里的常规选择。ISBN 字段建议定义为字符类型因为 ISBN 可能带有横线或者 X 结尾用 Int 或 Bigint 都不合适。4.3 索引一眼定生死哪一列该建索引哪一列建了也是白建答辩老师很喜欢问的一个进阶问题是这些表里哪些字段需要建索引我的答案很直接先用实际业务场景去推而不要盲目给每个字段都建索引。图书表里书名bookName是模糊检索的高频字段按书名查必须走索引借阅记录表里读者 ID 和图书 ID 是外键关联查询时必须有索引借阅日期用于计算逾期属于排序和范围查询也可以考虑索引。反过来像图书的出版社字段、读者的年龄字段这种低区分度的字段建了索引收益很小。索引不是越多越好每多一个索引写入时的维护成本就多一分对一张写多读少的表来说无脑建索引纯属自我感动。MySQL 中创建索引的语句如下ALTER TABLE t_book ADD INDEX idx_book_name (bookName); ALTER TABLE t_borrow ADD INDEX idx_borrow_reader_id (readerId);你要是能主动在课设说明书里补一段“索引设计说明”老师对你的评分预期会明显高一层。5. 答辩是二次学习老师最爱追问的问题和几个低成本加分项5.1 关于 POST、GET、重定向和转发的几个经典追问把项目跑通只是第一步答辩才是真正拉开差距的地方。图书管理系统的答辩问题有很强的规律性其中最高频的三个方向HTTP 方法、跳转方式、Session 生命周期。为什么登录用 POST 而不用 GET 最直接的答案POST 请求的数据放在请求体中浏览器地址栏不会暴露密码GET 请求的数据拼在 URL 后面一串密码明文摆在地址栏里后面站着人全看见了。同时 URL 会被浏览器历史记录、代理服务器日志等多个环节记录密码等于裸奔。添加图书成功后用重定向sendRedirect还是转发forward 标准答案是增删改操作成功后使用重定向查询操作转发即可。原因在于如果你用了转发地址栏的 URL 不会变化用户刷新浏览器时上一次的增删改请求会被再一次提交可能造成重复插入数据或重复扣减库存。重定向会让浏览器重新发出一个全新的 GET 请求天然避免了这个问题。浏览器关闭之后访问页面为什么还要登录 这就要讲 Session 的默认有效期了Tomcat 的默认值一般是 30 分钟无操作就销毁。Session 是存在服务器端的服务器知道你是谁Cookie 是存在浏览器端的里面存的是 SessionId。浏览器关闭Cookie 关了下次打开浏览器时带不上原来的 SessionId服务器就觉得你是新访客自然要重新登录。5.2 低成本高回报的加分项改掉这几点老师会高看你一眼源码拿过来直接交的话最多少出错但如果花一个晚上加上以下几个改动老师说出来的评价会完全不同。第一明文密码改成 MD5 加盐存储。原始源码通常直接明文存密码虽然对于课程设计来说够用但作为一个信息管理系统明文存储密码在答辩时容易被老师抓包。改进很简单在注册或添加用户时用MD5Util.md5(password salt)方法处理后再入库登录校验时对输入值做同样的处理再比对。第二给全文加一个统一的权限过滤器。用一个 Filter 拦截所有请求判断当前 Session 里有没有登录用户。没有的话直接重定向到登录页。这个改动用到的是 Servlet 三大组件里的 Filter属于课设大纲就要覆盖的知识点你加上去之后等于把“过滤器”这个考点从纸面落到了工程里。第三将 DBUtil 里每次查询都新建连接的方式改为数据库连接池。原始课设的写法往往是每次请求来都 DriverManager.getConnection性能较差也容易被老师追问“如果有 100 个人同时登录会不会卡死”。你改成 Druid 或者 C3P0代码量不大但技术含量提升明显。// 改造前 Class.forName(com.mysql.jdbc.Driver); Connection conn DriverManager.getConnection(url, user, pass); // 改造后 ComboPooledDataSource dataSource new ComboPooledDataSource(); dataSource.setDriverClass(com.mysql.jdbc.Driver); Connection conn dataSource.getConnection();第四把 JSP 页面里的 Java 脚本片段清理干净改用 JSTL 标签和 EL 表达式。老源码里 JSP 页面混着大段% %是很常见的改写成c:forEach和${book.bookName}之后页面好不好看是一回事关键是它直接呼应了“MVC 中视图层只负责展示”这一条铁律。5.3 从一个图书管理系统出发怎么往 Spring Boot 方向迁移很多学生课程设计交完了想把项目往简历上写但心里没底用 Servlet 写的东西面试官还会看吗我的看法是你不需要把整个系统重写成 Spring Boot 再贴到 GitHub 上但你可以把它当作一个迁移练习梳理出一条明确的迁移路径Servlet 类迁移成 WebServlet 注解本质上还是 Servlet。JDBC DBUtil 迁移到 Spring 的 JdbcTemplate 或 MyBatis数据库操作层直接重构。DAO 手工实例化改成 Spring 的依赖注入。Filter 迁移成 Spring MVC 的拦截器HandlerInterceptor。事务控制从手动 begin/commit/rollback 换成 Transactional 注解。这几个步骤每一步你都能讲明白的话面试的时候你不但能说“我会用 Spring Boot”还能说“我理解 Spring Boot 帮我省掉了哪些事”。后者比前者值钱得多。6. 源码是一本教材但不是抄作业纸怎么读、怎么改、怎么超过它6.1 按什么顺序读源码才能最快掌握它在干什么拿到这套源码如果你从 LoginServlet 开始一行行读会很痛苦而且容易陷进细节里出不来。我建议按如下的阅读顺序先读 web.xml或看有没有 WebServlet 注解把 URL 映射搞清楚。看哪个请求归哪个 Servlet 管脑子里形成一张地图。读 DBUtil 类搞懂数据库连接是怎么建立的、连接参数在哪里配置这是整个项目的地基。读一个完整的“增删改查”链路比如图书管理模块里的 BookServlet。它的流程一定是接收 action 参数 - 判断动作类型 - 调用 service - 跳转 JSP。读 JSP 页面里的表单提交地址和 EL 表达式理解页面如何把数据交给 ServletServlet 又如何把数据塞给 JSP。最后再深入“借书”“还书”这种有多步操作的逻辑体会事务处理。这个顺序的核心逻辑是先看骨架web.xml再看地基DBUtil再看一条完整数据流Servlet 的增删改查最后看复杂业务借还书。照这个步骤读下来你基本就知道项目运行的全貌了。6.2 什么时候你完全可以不看源码自己写我经常被问学长我是不是要把源码背下来才能过答辩不需要。源码是一根拐杖你是要学会自己走路。当你能够合上源码在白板上画出一张用户表、一张图书表、一张借阅表表里面有哪几个字段能够说出登录用的是 Session、跳转用的是重定向、模糊查询用的是 PreparedStatement 的 LIKE能够手写出一段 JDBC 增删改查的骨架代码——这时候源码对你来说就可以扔了。课程设计的目的从来不是让你当一个源码复制粘贴工程师而是让你掌握一种拆解业务的方法。图书管理系统是一个特别经典的例子它麻雀虽小但五脏俱全有会话管理、有权限区分、有事务边界、有模糊搜索、有数据关联。你把这套思路学会了以后换一个“酒店管理系统”“教务管理系统”“用品租借系统”换汤不换药你完全可以从零写一套。6.3 别把资源全消耗在“美化”上二开的优先级应该这样排如果你时间有限又想在这套源码上做出差异我的建议是先别急着给页面换皮。美观是加分项但业务完整性和技术亮点的优先级更高。我的二开建议排序如下优先级最高重构分层确保 Servlet 里不直接写 JDBC保存三层结构。其次给密码加密、加权限过滤器保证系统基本的安全底线。再次把借阅历史查询做成独立的模块支持按读者或按图书维度查询。然后引入分页图书列表和借阅记录不再一次查出全部数据。最后如果还有余力再考虑换一套好看的 CSS 框架或者现代化页面模板。按这个顺序推进会比较稳妥。一上来就折腾前端皮肤往往是页面改了三天答辩时老师凑过来看的第一眼还是那个老旧的 JDBC 代码那你改页面的功夫就全白费了。把后端的技术点打磨到位才是课设拿高分的正道。
返回列表