ARTICLE DETAIL

资讯详情

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

Java Web课设实战:Servlet+JDBC图书馆系统深度改造指南

Java Web课设实战:Servlet+JDBC图书馆系统深度改造指南 简介本资源是一套面向Java Web初学者与课程设计实践者的完整图书馆管理系统源码包聚焦Web应用开发、数据库交互与MVC基础架构训练。项目基于ServletJSP技术栈采用MySQL实现图书借阅、用户管理、数据查询等核心功能覆盖从环境搭建、DAO层封装、前后端交互到基础安全防护如密码处理、SQL注入防范的全流程实践。压缩包共179个文件含50个JSP页面负责用户界面与数据展示、33个Java源文件含Book/Reader/Borrow等业务实体及DAO、Servlet逻辑、33个编译后Class文件以及SQL建库脚本、JAR依赖库、配置文件和静态资源整体4.49MB结构清晰、模块职责分明便于逐层理解与调试。目前已有657人学习下载读者可直接导入Eclipse运行快速掌握Java Web项目开发规范、JDBC数据库操作、EL/JSTL页面渲染及典型三层架构落地方法。1. 这不是“又一个Java Web课设”而是用真实业务逻辑锤炼工程能力的最小闭环系统你手头这个.zip文件表面看是课程设计作业——但真正拉开差距的从来不是“能不能跑起来”而是能不能在不改一行前端代码的前提下把借阅超期罚款规则从固定5元改成按天阶梯计费能不能在不重写DAO层的情况下把MySQL换成PostgreSQL并保持事务一致性能不能让管理员导出的Excel报表自动带水印、防篡改、字段对齐。这不是玩具项目它是用图书馆业务当沙盒逼你把Servlet生命周期、JDBC连接池、MVC分层、SQL注入防护、事务边界划分这些抽象概念一拳一脚砸进真实日志、真实报错、真实并发请求里。适合两类人刚学完《Java Web编程》但写不出完整登录校验链路的新手以及能写CRUD却总在面试时被问“如果并发还书失败你怎么保证库存和借阅记录原子性”的中级开发者。它不教Spring Boot自动装配但教会你手动配DataSource时为什么maxActive不能设成100它不讲MyBatis缓存原理但让你亲手在web.xml里加Filter拦截所有请求验证session超时后跳转是否真走的是/login.jsp而不是某个静态资源路径。这才是课设该有的样子小而全、糙而实、错得明明白白、修得清清楚楚。2. 从解压到首页渲染三步跑通最小可运行路径2.1 解压后先确认这4个关键文件结构别急着IDE打开提示很多翻车始于目录结构误判。这个项目不是Maven标准结构而是传统Web应用布局必须手动核对物理路径。解压后你会看到类似这样的根目录LibrarySystem/ ├── src/ # Java源码注意不是src/main/java │ └── servlet/ # Servlet类 │ └── dao/ # 数据访问对象 │ └── bean/ # 实体类Book.java, User.java等 │ └── util/ # 工具类DBUtil.java最关键 ├── WebContent/ # 等价于标准Web应用的webapp目录 │ ├── WEB-INF/ │ │ ├── web.xml # 核心配置映射URL到Servlet │ │ └── lib/ # JAR包mysql-connector-java-5.1.47.jar必在 │ ├── index.jsp │ ├── login.jsp │ └── admin/ ├── database/ # SQL脚本重点别漏掉init.sql │ └── init.sql # 建库建表初始数据含管理员账号 └── README.txt # 通常只写“导入数据库即可运行”但实际要动3处关键动作打开database/init.sql确认建表语句中user表是否有role字段值为admin或reader这是登录权限判断依据检查src/util/DBUtil.java中getConnection()方法里的URL、用户名、密码——90%的“404”或“拒绝连接”错误源于这里没改WebContent/WEB-INF/web.xml中servlet-mapping的url-pattern是否为/login而非/LoginServlet大小写敏感WebContent/WEB-INF/lib/下必须有mysql-connector-java-5.1.47.jar低版本MySQL驱动高版本如8.0需换jar且URL加?serverTimezoneUTC。2.2 本地部署Tomcat 8.5 MySQL 5.7 是最稳组合注意用Tomcat 10会因Servlet API 4.0与项目中的javax.servlet.*包冲突直接报错MySQL 8.0默认caching_sha2_password认证插件会导致连接失败。步骤拆解命令行操作拒绝IDE一键部署启动MySQL并导入数据库# 登录MySQL假设root密码为空 mysql -u root -p # 执行建库建表注意路径替换成你解压后的绝对路径 source /path/to/your/LibrarySystem/database/init.sqlinit.sql中关键语句解析CREATE DATABASE library DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;→ 必须用utf8mb4否则图书名含emoji会乱码INSERT INTO user(username, password, role) VALUES(admin, 123456, admin);→ 默认管理员账号密码明文存储课设允许但你要知道生产环境必须BCrypt加密ALTER TABLE book ADD COLUMN status TINYINT DEFAULT 1;→status1表示在馆0表示已借出这是借还逻辑核心字段。配置Tomcat并部署WAR包# 将WebContent目录整体复制到Tomcat的webapps下并重命名为library路径必须小写 cp -r /path/to/LibrarySystem/WebContent $TOMCAT_HOME/webapps/library # 启动TomcatLinux/macOS $TOMCAT_HOME/bin/startup.sh # Windows用户用 startup.bat验证是否成功浏览器访问http://localhost:8080/library/index.jsp若出现“HTTP Status 404”检查$TOMCAT_HOME/webapps/library/下是否存在WEB-INF/web.xml查看Tomcat日志$TOMCAT_HOME/logs/catalina.out搜索SEVERE关键字常见错误是java.lang.ClassNotFoundException: com.mysql.jdbc.Driver驱动jar没放对位置。首次登录测试访问http://localhost:8080/library/login.jsp输入账号admin密码123456成功跳转到admin/home.jsp即代表基础链路打通。3. 深度改造把“能用”变成“可用”的3个硬核改造点3.1 改造登录校验从明文密码到BCrypt加盐哈希原项目用SELECT * FROM user WHERE username? AND password?直接比对明文密码这是安全红线。必须替换为BCrypt方案且不改动任何JSP页面和Servlet逻辑。改造步骤在pom.xml若无则手动创建中添加依赖Tomcat 8.5兼容dependency groupIdorg.mindrot/groupId artifactIdjbcrypt/artifactId version0.4/version /dependency注意不要用Spring Security的BCrypt课设项目无Spring上下文jbcrypt最轻量。修改src/dao/UserDAO.java中的login方法// 原代码危险 String sql SELECT * FROM user WHERE username? AND password?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, password); // 明文传入 // 改为关键用BCrypt.checkpw校验 String sql SELECT id, username, password, role FROM user WHERE username?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, username); ResultSet rs ps.executeQuery(); if (rs.next()) { String hashedPassword rs.getString(password); if (BCrypt.checkpw(password, hashedPassword)) { // password是用户输入的明文 User user new User(); user.setId(rs.getInt(id)); user.setUsername(rs.getString(username)); user.setRole(rs.getString(role)); return user; } } return null;重置数据库密码必须做否则旧密码失效-- 用BCrypt生成新密码例如123456的hash -- 在任意Java环境执行 // System.out.println(BCrypt.hashpw(123456, BCrypt.gensalt())); -- 输出类似$2a$10$Zz9qXQvY...长度60字符 UPDATE user SET password$2a$10$Zz9qXQvY... WHERE usernameadmin;参数说明BCrypt.gensalt(10)中10是log_rounds值越大越安全但越慢课设用10足够生产环境建议12。3.2 改造借阅逻辑用数据库事务兜底并发风险原项目借书流程是查询图书status1更新book.status0插入borrow_record记录→ 若第2步成功、第3步失败图书状态已变但记录未生成导致“书丢了”。必须用事务包裹且在DAO层而非Servlet层控制// src/dao/BorrowDAO.java public boolean borrowBook(int userId, int bookId) throws SQLException { Connection conn null; PreparedStatement ps1 null, ps2 null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关键关闭自动提交 // 步骤1检查图书是否在馆SELECT FOR UPDATE锁住该行 String checkSql SELECT status FROM book WHERE id? FOR UPDATE; ps1 conn.prepareStatement(checkSql); ps1.setInt(1, bookId); ResultSet rs ps1.executeQuery(); if (!rs.next() || rs.getInt(status) ! 1) { throw new SQLException(图书不可借阅); } // 步骤2更新图书状态 String updateSql UPDATE book SET status0 WHERE id?; ps2 conn.prepareStatement(updateSql); ps2.setInt(1, bookId); ps2.executeUpdate(); // 步骤3插入借阅记录 String insertSql INSERT INTO borrow_record(user_id, book_id, borrow_date) VALUES(?, ?, NOW()); PreparedStatement ps3 conn.prepareStatement(insertSql); ps3.setInt(1, userId); ps3.setInt(2, bookId); ps3.executeUpdate(); conn.commit(); // 全部成功才提交 return true; } catch (SQLException e) { if (conn ! null) conn.rollback(); // 任一失败回滚 throw e; } finally { DBUtil.close(ps1, conn); DBUtil.close(ps2, null); } }关键细节SELECT ... FOR UPDATE在InnoDB中加行锁防止并发借同一本书conn.setAutoCommit(false)必须在获取Connection后立即设置DBUtil.close()必须在finally块中调用避免连接泄漏。3.3 改造分页查询从SQL拼接到参数化预编译原项目列表页如admin/book_list.jsp用字符串拼接SQLString sql SELECT * FROM book LIMIT offset , pageSize;→ 极易SQL注入若offset来自request.getParameter且未校验。改为PreparedStatement分页MySQL语法// src/dao/BookDAO.java public ListBook findBooksByPage(int pageNum, int pageSize) throws SQLException { int offset (pageNum - 1) * pageSize; String sql SELECT id, name, author, isbn, status FROM book ORDER BY id DESC LIMIT ?, ?; ListBook books new ArrayList(); Connection conn null; PreparedStatement ps null; ResultSet rs null; try { conn DBUtil.getConnection(); ps conn.prepareStatement(sql); ps.setInt(1, offset); // 第一个? → offset ps.setInt(2, pageSize); // 第二个? → pageSize rs ps.executeQuery(); while (rs.next()) { Book book new Book(); book.setId(rs.getInt(id)); book.setName(rs.getString(name)); book.setAuthor(rs.getString(author)); book.setIsbn(rs.getString(isbn)); book.setStatus(rs.getInt(status)); books.add(book); } } finally { DBUtil.close(rs, ps, conn); } return books; }为什么用LIMIT ?, ?而不是LIMIT :offset, :pageSize因为MySQL JDBC驱动要求LIMIT子句的参数必须是int类型且顺序严格对应?占位符位置不能用命名参数。4. 避坑这5个血泪经验让90%的人少踩3小时4.1 现象登录后跳转到空白页浏览器地址栏显示/library/servlet/LoginServlet原因web.xml中servlet-class写成了LoginServlet但实际类名是com.library.servlet.LoginServlet缺完整包路径解决打开src/servlet/LoginServlet.java顶部package com.library.servlet;→web.xml中必须写全限定名servlet-classcom.library.servlet.LoginServlet/servlet-class4.2 现象图书列表页中文乱码显示为??但数据库里是正常的原因JDBC URL缺少字符集参数且Tomcat未配置URIEncoding解决修改DBUtil.java中连接URLjdbc:mysql://localhost:3306/library?useUnicodetruecharacterEncodingutf8mb4修改$TOMCAT_HOME/conf/server.xml在Connector标签内加URIEncodingUTF-8JSP页面顶部加% page contentTypetext/html;charsetUTF-8 %4.3 现象点击“还书”按钮无反应浏览器控制台报405 Method Not Allowed原因ReturnBookServlet只写了doPost()方法但前端form的methodget解决方案A推荐修改return_book.jsp中form的methodpost方案B在Servlet中补全doGet()内容与doPost()一致课设可接受但违背REST原则。4.4 现象管理员删除用户后再登录该账号仍成功原因删除操作只删了user表但borrow_record表中仍有该用户记录且登录逻辑未校验用户状态如is_deleted0解决给user表加is_deleted TINYINT DEFAULT 0字段0正常1已删除修改UserDAO.login()的SQLWHERE username? AND is_deleted0删除用户时用UPDATE user SET is_deleted1 WHERE id?而非DELETE FROM user软删除更安全。4.5 现象导出Excel功能生成的文件打不开提示“文件损坏”原因ExportExcelServlet用response.getOutputStream()写入数据但未设置正确的Content-Type和Header解决在doPost()开头添加response.setContentType(application/vnd.ms-excel); response.setHeader(Content-Disposition, attachment;filenamebooks.xls); response.setCharacterEncoding(UTF-8);注意若用.xlsx格式Content-Type应为application/vnd.openxmlformats-officedocument.spreadsheetml.sheet且需引入poi-ooxml依赖。5. 进阶验证用3个真实场景压力测试你的改造成果5.1 场景1模拟100并发用户同时借同一本书目标验证事务行锁是否真正生效避免超借。工具Apache Benchab或简单Java线程池。脚本Linux终端# 模拟100个用户请求借书bookId1 ab -n 100 -c 100 http://localhost:8080/library/servlet/BorrowServlet?bookId1userId1预期结果Tomcat日志中应出现若干次java.sql.SQLException: 图书不可借阅说明SELECT FOR UPDATE锁住了第一笔请求后续请求查到status0最终book表中status仍为0仅1次成功借出borrow_record表新增记录数1。若失败所有请求都成功 → 检查SELECT ... FOR UPDATE是否在事务内执行且隔离级别是否为REPEATABLE READMySQL默认。5.2 场景2故意触发数据库异常验证事务回滚完整性目标证明借书失败时图书状态不会被错误更新。操作修改BorrowDAO.borrowBook()在ps2.executeUpdate()后、ps3.executeUpdate()前强行抛异常ps2.executeUpdate(); throw new SQLException(模拟插入失败); // 临时加这行用浏览器借一本书。预期结果页面报500错误查数据库book.status仍为1未被更新borrow_record表无新增记录。若失败status变0但无记录→ 说明conn.rollback()未执行检查catch块中是否遗漏conn.rollback()或DBUtil.close()提前关闭了连接。5.3 场景3用Burp Suite篡改请求参数验证SQL注入防护目标确认预编译参数化查询真正生效。操作访问http://localhost:8080/library/admin/book_list.jsp用Burp截获请求将?page1改为?page1%20OR%2011URL编码后的1 OR 11发送请求。预期结果页面仍只显示第1页数据10条而非全部图书Tomcat日志无Syntax error报错。若失败返回全部数据→ 说明分页SQL仍是字符串拼接未用LIMIT ?, ?。我带过3届学生做这个课设最深的教训是永远先写验证脚本再写业务代码。比如借书逻辑我一定先写个TestBorrowConcurrency.java用10个线程抢同一本书看着控制台打印“成功1次、失败9次”才敢提交。因为课设不是交差是给自己建一座桥——桥墩是事务隔离级别桥面是参数化查询护栏是BCrypt哈希。每次加固都是把“可能出错”变成“必然可控”。希望帮到你。本文还有配套的精品资源点击获取
返回列表