JavaWeb实战:从零开发图书借阅管理系统,掌握Servlet到JDBC核心架构
简介一套可供课程设计和毕业设计直接参考的基于JavaWeb的图书借阅管理系统采用JSPJavaBeanMySQLTomcat实现覆盖读者端和管理员端完整功能流程。读者可完成注册登录、查询借阅图书、查看借阅历史、归还图书及个人信息修改管理员可对图书进行增删改查同时管理读者信息并查询全部借阅记录。资源压缩包共38个文件体积1.76MB以17个JSP页面为核心搭配SQL建库脚本、CSS样式、工程配置文件和运行说明能直观理解页面、业务逻辑与数据库之间的调用关系。已有531人学习下载适合JavaWeb初学者模仿练习也可作为期末项目的基础版本进行功能扩展。包内附数据库脚本、项目介绍文档和运行注意事项导入IDE后按说明修改数据库连接即可运行便于快速校验借阅系统各模块的实际效果。 做JavaWeb开发的这几年我见过太多人一上来就追Spring Boot、微服务结果连Servlet生命周期都说不清楚。如果你正处于学完Java基础、刚摸到Web开发门槛的阶段那我强烈建议你亲手做一个完整的图书借阅管理系统。它不大不小刚好能把Servlet、JSP、JDBC、MySQL、Session、Filter这些JavaWeb核心知识点串成一条线。做完这个项目你对“一个Web系统到底是怎么跑起来的”会有完全不一样的理解。这篇文章就按我实际开发这个项目的经验把从设计到落地的完整过程拆给你看每一步都讲清楚为什么这么做。1. 项目整体设计与技术选型怎么定1.1 为什么选JavaWeb而不是直接上Spring Boot这是个很现实的问题。现在培训班和网课都在推Spring Boot因为它简化了配置、内置了Tomcat开发效率确实高。但图书借阅管理系统这种体量的项目恰恰是用传统JavaWeb方式做最合适。传统JavaWeb的路子是Servlet JSP JDBC请求进来先到ServletServlet调业务层业务层通过JDBC操作MySQL数据库最后转发给JSP渲染页面。整个过程里你自己写了每一个环节所以能真正理解HTTP请求怎么被处理、连接池解决什么问题、Session是怎么维持登录状态的。这些底层逻辑搞清楚了再去用Spring Boot会觉得它只是帮你省了重复劳动而不是魔法。当然如果你是为了找工作做项目展示也可以在这个基础上换成SSMSpring SpringMVC MyBatis。我给你的建议是课程设计或毕设坚持用纯粹的JavaWeb方案因为答辩时老师更看重你能不能讲清楚底层原理就业导向的话用SSM改一版原理是一样的换个壳而已。1.2 三层架构与功能模块怎么划分做这个项目之前先把整体架构在脑子里立起来。我采用的是经典的三层架构表现层Web层负责接收请求、参数校验、页面跳转。对应Servlet和JSP。业务层Service层处理核心业务逻辑比如借书时判断库存、计算应还日期。数据层DAO层封装所有SQL操作只负责和数据库打交道。分层最大的好处是职责清晰。举个实际例子借书操作涉及到查询图书、检查库存、插入借阅记录、更新库存四个动作如果没人管业务逻辑这些代码就会散落在Servlet里改一个地方牵连一片。分层之后业务逻辑收拢在Service层Servlet只关心“调用哪个Service方法、成功跳哪个页面、失败提示什么”。核心功能模块我分成了五块用户模块登录注册、权限区分、图书模块增删改查、库存维护、借阅模块借书、还书、续借、超期处理、统计模块借阅排行、图书热门度、系统模块分类管理。五块功能覆盖了一个完整业务系统的基本CRUD又不至于复杂到失控。2. 数据库设计与借阅核心逻辑2.1 数据表怎么设计才合理数据库设计是整个项目的根基。表设计不合理后期写代码会各种别扭。我给这个项目设计了四张核心表用户表、图书表、图书分类表、借阅记录表。用户表要注意的是角色字段我用role字段区分管理员和普通读者1代表管理员0代表普通用户。为什么不直接用字符串因为数字类型查询效率高而且在Java里用枚举常量判断也更优雅。图书表的重点是库存字段一个stock整数类型就够了不要和“总藏书量”混在一起总藏书量可以通过stock borrowed_count计算得到。借阅记录表是核心中的核心字段设计直接影响后面写还书逻辑的复杂度。我的设计思路是CREATE TABLE borrow_record ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, book_id INT NOT NULL, borrow_date DATETIME NOT NULL, due_date DATETIME NOT NULL, return_date DATETIME DEFAULT NULL, status TINYINT DEFAULT 0, FOREIGN KEY (user_id) REFERENCES user(id), FOREIGN KEY (book_id) REFERENCES book(id) );status字段我用0表示借阅中1表示已归还2表示超期未还。判断超期不用定时任务去扫表而是查询时通过due_date NOW()动态算出来这样既省事又不会漏算。很多人喜欢在表里冗余一个is_overdue字段我的建议是不要因为它需要定时更新容易产生脏数据。2.2 借书还书的事务控制借书的业务流程表面上只有一步“把书借走”但拆开来看是五个动作的原子组合根据图书ID查询图书确认存在且库存大于0。向借阅记录表插入一条数据状态为借阅中。图书表的库存减1。如果失败所有操作回滚。这里最容易踩的坑是忘记加事务。如果只执行了插入记录、还没来得及扣库存程序就抛异常了那数据库里就会出现一条没有对应扣减的借阅记录。用JDBC操作时一定要把自动提交关掉手动控制commit和rollbackpublic void borrowBook(BorrowRecord record) throws SQLException { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); borrowRecordDao.insert(conn, record); bookDao.decreaseStock(conn, record.getBookId()); conn.commit(); } catch (SQLException e) { if (conn ! null) { conn.rollback(); } throw e; } finally { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } } }注意我这里的DAO方法都带Connection参数这是事务控制的关键点。如果每个DAO方法内部单独获取连接那事务就控制不住了各连各的库commit和rollback完全不受统一指挥。这个细节面试也常问能讲清楚说明你是真做过项目的。还书流程反过来更新借阅记录状态为已归还、写入实际归还日期、图书库存加1。还有一个容易忽略的点判断这本书是不是已经超期了如果是需要生成一条违规记录或者做罚款计算。我在设计里把超期罚款的金额做成可配置参数而不是写死在代码里。3. 核心功能模块的实操实现3.1 登录与权限控制登录功能是每个Web项目的标配但很多新手做得太简陋直接在Servlet里判断用户名密码就结束了。我在这个项目里把权限控制做成了完整的Filter链。用户登录成功后把用户对象放进Session然后定义一个LoginFilter拦截所有需要登录才能访问的路径。在web.xml里配置filter filter-nameLoginFilter/filter-name filter-classcom.library.filter.LoginFilter/filter-class /filter filter-mapping filter-nameLoginFilter/filter-name url-pattern/admin/*/url-pattern /filter-mappingFilter里的逻辑很简单判断Session里有没有用户对象没有就跳转到登录页。但这里有个细节值得说——管理员接口和普通用户接口要分开拦截我用了两个Filter或者一个Filter里做两次路径匹配。另外要放行静态资源CSS、JS、图片否则样式加载不出来新手经常在这卡半天。还有一点是我实际开发过程中踩过的坑用户登录成功后不要只存用户名要把完整的用户对象放Session。因为后面借书、查看记录都需要用户ID如果只存了用户名每次还得重新查数据库取ID白白多一次查询代码也难看。3.2 图书检索与分页图书搜索是读者用得最多的功能搜索条件一般有书名、作者、ISBN、分类。我实现了多条件组合查询所有条件都用PreparedStatement参数绑定坚决不用字符串拼接SQL。分页我是这么做的每页显示10条前端传pageNum和pageSize后端根据这两个参数计算偏移量执行LIMIT ?, ?。同时用一条COUNT(*)查询拿到总记录数计算总页数。分页组件封装成一个PageBean里面放当前页数据列表、当前页码、总页数、总记录数统一回传给前端。一个细节查询条件要维持住。用户搜了“三体”然后翻到第二页分页链接上的搜索条件不能丢否则第二页就变成全量图书了。我在分页标签里把搜索参数拼接进URL保证了翻页时搜索条件不丢失。3.3 借阅记录、统计排行与行为轨迹分析网上有个热搜词是“根据车辆轨迹计算在某条路上跑了几个来回、覆盖了多少”这个思路放到图书借阅系统里很有意思。我们的借阅记录本质上就是读者的“轨迹”每一条借阅行为都有时间、图书、频次完全可以用来做行为分析。我做了一个简单的统计模块计算每个读者总共借过几本书借阅频次、当前还借了几本在借数量、有没有超期记录信用度。本质是SQL里的分组聚合SELECT user_id, COUNT(*) AS borrow_count, SUM(CASE WHEN return_date IS NULL THEN 1 ELSE 0 END) AS borrowing_count, SUM(CASE WHEN status 2 THEN 1 ELSE 0 END) AS overdue_count FROM borrow_record GROUP BY user_id这个统计结果放到用户管理页管理员一眼就能看出哪些人借书频繁、哪些人有超期风险。图书热门榜也类似按图书ID分组统计借阅次数取前10名。这也是“数据轨迹分析”思维在小型系统里的落地方案原理和车辆轨迹分析是一样的先有完整的数据记录再按维度聚合最后得出有价值的结论。3.4 超期计算与提醒超期计算我写了两种方式。第一种是还书时实时计算拿return_date和due_date比较差几天算几天罚款。第二种是列表页展示时计算查询所有status 0的记录如果due_date小于当前时间就在页面上标红显示。这里涉及到日期的使用Java里操作日期一定要用LocalDateTime别用老旧的Date。LocalDateTime处理加减、比较都方便得多比如判断超期天数long overdueDays ChronoUnit.DAYS.between(record.getDueDate(), LocalDateTime.now());4. 常见问题与排查技巧实录4.1 中文乱码编码问题排查路径中文乱码是JavaWeb新手最容易遇到、也最烦人的问题。我整理了一个排查顺序按这个顺序检查基本一次搞定检查项位置解决方案JSP页面编码JSP文件头% page contentTypetext/html;charsetUTF-8 %请求参数编码Servlet/Filterrequest.setCharacterEncoding(UTF-8)且必须在获取任何参数之前响应编码Servletresponse.setContentType(text/html;charsetUTF-8)数据库连接编码JDBC URLuseUnicodetruecharacterEncodingutf8数据库表编码MySQL建表语句DEFAULT CHARSETutf8mb4Tomcat连接器编码server.xmlConnector URIEncodingUTF-8/我自己的经验是页面和数据库的编码设置一般不会有问题最容易漏的是request.setCharacterEncoding(UTF-8)。而且要注意这个方法必须放在request.getParameter()调用之前才生效很多人把Filter写好了却忘了注册或者Filter中执行顺序不对都会导致乱码。4.2 库存为负与并发事务问题图书库存的并发问题光靠check-then-update是不够的。举个例子两个人同时借同一本只剩1本的书两个请求都查到库存是1都判断可以借然后都执行库存减1结果库存变成-1还都借成功了。解决办法是使用乐观锁或数据库原子操作。最简单的是把扣库存写成一条带条件的UPDATE语句UPDATE book SET stock stock - 1 WHERE id ? AND stock 0如果更新影响行数为1说明扣减成功为0说明库存不足或被别人抢了直接返回“库存不足”。这条语句利用数据库行锁保证了原子性比自己查再更安全得多。4.3 SQL注入与N1查询SQL注入这个老生常谈的问题在图书检索里最容易出现。我见过有人直接把搜索框的值拼到SQL里输入 OR 11 --就能把全表数据都查出来。解决方式就一条所有的SQL参数一律用PreparedStatement的占位符不要用字符串拼接。N1查询问题是在借阅记录列表里出现的。我一开始查借阅记录时只查了user_id和book_id然后在页面上需要显示用户名和书名于是循环里又查了两次数据库100条记录就多出200条查询页面加载慢得不行。后来改用JOIN一次性查出关联的用户名和书名SQL长了一点但性能完全不一样SELECT br.*, u.real_name, b.book_name FROM borrow_record br JOIN user u ON br.user_id u.id JOIN book b ON br.book_id b.id ORDER BY br.borrow_date DESC LIMIT ?, ?这里需要说下工具选择的经验如果用的是纯JDBC建议封装一个简单的BeanUtil来做结果集到Java对象的映射别每次手动rs.getString(xxx)写几十行。如果你用的MyBatis那ResultMap和注解映射了解一下就能省很多事。4.4 分页查询慢的优化思路当数据量到几万条的时候LIMIT 100000, 10这种深分页会明显变慢因为MySQL要把前10万条数据全部查出来再丢弃。优化思路是延迟关联先查主键再回表SELECT br.* FROM borrow_record br JOIN (SELECT id FROM borrow_record ORDER BY borrow_date DESC LIMIT ?, ?) tmp ON br.id tmp.id这个技巧在数据量大时效果明显数据量小时感知不强但作为优化方案写进项目里面试时说出来会很加分。5. 部署上线与项目扩展方向5.1 本地部署与IDEA配置要点开发环境推荐用IntelliJ IDEA。如果你用VSCode也可以装Java Extension Pack和Tomcat插件跑起来但IDEA对JavaWeb的调试支持更顺手尤其是断点调试JSP时IDEA体验好得多。这里分享一个省事的项目部署方案Maven Tomcat7插件在pom.xml里配置好插件之后一条命令就能启动mvn tomcat7:run比手动把war包拷进Tomcat的webapps目录方便太多。做的时候注意端口别被占用默认8080被占就改port配置。5.2 从毕设到工业级项目的升级方向如果你做完基础功能还有余力我建议按这几个方向做增强。先用Redis缓存热门图书列表和会话信息减轻数据库压力再用拦截器把日志、权限、参数校验做成AOP切面然后把密码从MD5换成BCrypt加密增加安全性最后把服务器换到云服务器上部署配合Nginx做反向代理这就是一个完整的上线方案了。还有一个很容易加分的扩展点对接支付。虽然图书借阅一般不涉及在线支付但如果你把系统扩展到“押金缴纳”或“逾期罚款在线支付”的场景就可以用支付宝或微信的沙箱环境对接支付接口。做法上就是下单时生成支付二维码前端轮询后端支付结果接口回调里验签并更新订单状态。这个扩展点在答辩或面试时展示出来能直接体现你的工程能力。5.3 我做完这个项目最深的体会最后说点实在的。我当时做完这个项目最大的收获不是学会了几张表的CRUD而是建立起了一个完整的“系统思维”。之前学Servlet是单独的知识点学JDBC是单独的知识点学MySQL是单独的知识点做完项目才发现它们是怎么协同工作的——一个请求从浏览器出发经过Tomcat、Filter、Servlet、Service、DAO、MySQL再原路返回渲染成页面。代码写完一定要自己走一遍完整的测试用例用户注册、登录、修改密码、借书、还书、超期、罚款、还清、统计。每一步都体验一遍你就知道哪个环节容易出Bug了。我在测试还书流程时发现罚款金额没刷新就是因为还书之后重新查询借阅记录的SQL条件写错了这种问题不跑一遍根本发现不了。如果这篇文章对你有帮助建议不要光看自己动手把数据库表建好然后一天写一层你会发现JavaWeb没有想象中那么难。遇到卡住的地方先自己断点调试实在不行再查资料这样印象才深。本文还有配套的精品资源点击获取