☰
JavaWeb实验室预约系统:Servlet+JSP+JDBC实战指南
2026/9/28 16:56:44 网站建设 项目流程

简介:本资源是一套完整的JavaWeb实验室预约管理系统实战项目,面向高校计算机专业学生、Java初学者及Web开发入门者,解决多角色协同管理实验室资源的核心业务场景。系统采用B/S架构,支持管理员、教师、学生三类用户差异化操作,涵盖用户管理、实验室维护、预约排期、公告发布、课堂任务布置、实验资料上传等全流程功能,适合作为课程设计、毕业设计或实训项目参考。压缩包为ZIP格式,大小42.69MB,包含可运行源码、MySQL数据库脚本及配套说明文档,文件结构清晰,含核心Servlet/JSP模块、DAO层封装与基础前端页面,便于理解MVC分层逻辑与数据库交互实践。目前已有467人学习下载,读者可直接部署运行、调试代码、拓展功能模块,并结合文档快速掌握JavaWeb开发中权限控制、表单验证、数据导出与动态排期等关键技能。

1. 为什么一个“实验室预约管理系统”能成为JavaWeb课程设计的压舱石?

你手头正赶着JavaWeb课设 deadline,老师要求“完整可运行、含数据库、有文档”,但翻遍 GitHub 和 CSDN,不是只有半截 JSP 页面、就是 Spring Boot 项目硬套 JavaWeb 标签——部署到 Tomcat 一跑就 404,连登录页都出不来;或者数据库脚本缺字段、文档里写着“管理员账号 admin/admin”,结果登录时提示密码错误,查源码才发现密码是 MD5 加盐但没给盐值……这不是个系统,这是个玄学黑匣子。

这个标题里的“基于JavaWeb实验室预约管理系统(源码+数据库+文档)”,指的是一套严格遵循 Servlet + JSP + JDBC 经典三层架构、适配 Tomcat 8/9、MySQL 5.7+、无任何框架封装、所有逻辑裸写可调试的落地项目。它不炫技,但每行代码都经得起打断点:用户预约→检查实验室空闲时段→生成带唯一编号的预约单→邮件通知教师→管理员后台审核→状态实时更新。它解决的不是“能不能跑”,而是“为什么能跑、哪里会卡、改一行代码怎么验证”。适合两类人:一是大三学生靠它交课设、过答辩、理解 MVC 真实流转;二是刚转 Java 的后端新人,用它反向吃透 request/response 生命周期、session 管理边界、JDBC 手动事务控制——不是看 Spring Boot 自动装配,而是亲手写conn.setAutoCommit(false)后再 rollback。

它不是玩具,是能直接部署进学院机房、支撑 200+ 学生并发预约的真实轻量级生产模型。下面,我就按当年自己搭第一版时踩过的坑、重装三次 Tomcat 的血泪经验,带你从零拉起这个系统:不跳步、不省略配置细节、不回避中文乱码和时区错位这种“小问题”——因为它们才是让项目在答辩现场翻车的主力。


2. 搭建环境:Tomcat + MySQL + IDEA 的最小可行组合

2.1 选版本:为什么坚持 Tomcat 8.5.99 而不是 10+

很多同学一上来就装 Tomcat 10,结果 JSP 编译报错javax.servlet包找不到。原因很简单:Tomcat 10 开始全面迁移到 Jakarta EE 9+ 命名空间,javax.*全部改为jakarta.*,而本项目源码中所有import javax.servlet.*都不会自动转换。强行改包名?那得动上百处 import 和 web.xml 中的 listener/class 元素,且部分第三方 JAR(如 commons-dbutils)尚未适配 jakarta,会引发 ClassDefNotFound。

提示:本项目明确依赖javax.servlet-api:4.0.1(对应 Tomcat 8.5.x),因此必须用 Tomcat 8.5.99 或 9.0.83(二者均兼容 javax)。下载地址为官网 archive 版本页,不要下最新版。解压后记下路径,例如D:\apache-tomcat-8.5.99,后续 IDEA 配置需指向此目录。

2.2 MySQL 配置:字符集与时区必须显式锁定

项目数据库脚本(.sql文件)默认建表语句含CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci,但若 MySQL 服务端未全局设置,插入中文实验室名称或学生姓名时会变问号。更隐蔽的是时区问题:预约时间存的是DATETIME,但 Java 获取new Date()默认用系统时区,MySQL 服务端若为 UTC,而 Windows 本地为 CST(UTC+8),会导致预约时间平移 8 小时。

执行以下 SQL 强制统一(需 root 权限):

-- 修改 MySQL 全局字符集(重启生效) SET GLOBAL character_set_server = 'utf8mb4'; SET GLOBAL collation_server = 'utf8mb4_unicode_ci'; -- 修改时区为东八区(立即生效,持久化需改 my.ini) SET GLOBAL time_zone = '+08:00';

然后编辑my.ini(Windows)或my.cnf(Linux),在[mysqld]下追加:

[mysqld] character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci default-time-zone = '+08:00'

参数说明:utf8mb4是 MySQL 对真正 UTF-8 的实现(支持 emoji 和四字节汉字),utf8在 MySQL 中仅是阉割版(最多三字节);+08:00显式声明时区,避免 JavaSimpleDateFormat解析时因 JVM 时区与 DB 不一致导致2024-05-20 14:00:00存成2024-05-20 06:00:00。

2.3 IDEA 导入:不是“Open”,而是“Add Framework Support”

很多同学双击.iml文件或直接 Open 项目根目录,结果 IDEA 报Cannot resolve symbol 'servlet'。正确流程是:

  1. 启动 IDEA →File → New → Project from Existing Sources
  2. 选择项目根目录(含src/,WebContent/,lib/,sql/的文件夹)
  3. 在向导中勾选"Create project from external model" → "Maven"(即使无 pom.xml,也选此项以启用依赖管理)
  4. 完成后右键项目根目录 →Add Framework Support → Web Application→ 设置 Web resource directory 为WebContent
  5. 再右键lib文件夹 →Add as Library,确保servlet-api.jar、mysql-connector-java-5.1.49.jar等在 Module Dependencies 中

逻辑说明:IDEA 的 Web Application 框架支持会自动生成web.xml模板路径、识别WEB-INF/web.xml、绑定 Tomcat 运行配置。若跳过此步,IDEA 不会将WebContent视为 Web Root,JSP 编译路径和静态资源映射全部失效。


3. 数据库设计与初始化:从 ER 图到可执行 SQL 的关键取舍

3.1 核心四张表:为什么只用 lab, user, appointment, admin_log?

项目数据库精简到仅 4 张表,不是功能少,而是聚焦“预约”主链路,剔除冗余:

表名字段(关键)作用设计理由
labid, name, capacity, status(0=空闲/1=维护), description实验室元数据status字段替代“删除”,避免外键断裂;capacity用于预约人数校验
userid, username, password, realname, role(0=学生/1=教师/2=管理员), email用户身份中心role单字段控制权限,不建 role 表——课设级项目够用且易懂
appointmentid, lab_id, user_id, start_time, end_time, status(0=待审/1=通过/2=拒绝/3=已用), remark预约主表start_time/end_time为 DATETIME,非 TIMESTAMP(后者受时区影响大);status支持状态机流转
admin_logid, operator_id, action_type, target_id, create_time管理员操作审计记录谁在何时审批了哪条预约,满足课设“可追溯”要求

避坑 / 常见问题 / 排查
现象 1:插入预约时提示Data truncation: Incorrect datetime value: '2024-05-20 14:00'
原因:MySQL 严格模式开启,且start_time字段定义为DATETIME NOT NULL,但 Java 传入字符串格式为"yyyy-MM-dd HH:mm"(缺秒),MySQL 拒绝隐式补:00。
解决:Java 层用SimpleDateFormat("yyyy-MM-dd HH:mm:ss")格式化,或数据库字段改为DATETIME DEFAULT CURRENT_TIMESTAMP并允许 NULL,插入时传null由 DB 补全。

现象 2:user.username唯一索引失效,两个学生注册同名账号成功
原因:MySQL 中VARCHAR(50)默认排序规则为utf8mb4_general_ci,对大小写不敏感,Admin和admin被视为相同。
解决:修改字段排序规则为utf8mb4_bin(二进制比较):ALTER TABLE user MODIFY username VARCHAR(50) COLLATE utf8mb4_bin;

现象 3:appointment表插入后id为 0,且无法关联查询
原因:建表时未设id为主键 + 自增,或AUTO_INCREMENT起始值被误设为 0。
解决:执行ALTER TABLE appointment ADD PRIMARY KEY (id); ALTER TABLE appointment MODIFY id INT AUTO_INCREMENT FIRST;

3.2 初始化脚本:如何让.sql文件一次执行成功

提供的init_db.sql文件需满足三个条件才能被 MySQL Client 无报错执行:

  1. 头部声明编码:首行必须为SET NAMES utf8mb4;,否则中文注释会导致解析中断;
  2. 建表顺序符合外键依赖:先建user和lab,再建appointment(含user_id,lab_id外键),最后建admin_log;
  3. 插入语句用 VALUES 批量而非多条 INSERT:减少网络往返,且避免单条失败中断整个脚本。

示例片段:

SET NAMES utf8mb4; CREATE DATABASE IF NOT EXISTS lab_booking DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci; USE lab_booking; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COLLATE utf8mb4_bin, password VARCHAR(100) NOT NULL, realname VARCHAR(20), role TINYINT DEFAULT 0, email VARCHAR(100) ); -- 插入初始管理员(密码为 123456 的 MD5 值) INSERT INTO user (username, password, realname, role, email) VALUES ('admin', 'e10adc3949ba59abbe56e057f20f883e', '系统管理员', 2, 'admin@lab.edu');

参数说明:e10adc3949ba59abbe56e057f20f883e是"123456"的 MD5(非加盐),课设级安全足够;UNIQUE COLLATE utf8mb4_bin确保用户名大小写敏感;TINYINT节省空间,比ENUM更易被 JDBC 读取。


4. 核心业务代码解析:从 login.jsp 到 AppointmentServlet 的请求链路

4.1 登录流程:为什么不用 Filter 做权限拦截?

项目采用最直白的 JSP + Servlet 控制流:login.jsp提交表单 →LoginServlet处理 → 查询user表 → 校验密码 → 设置session.setAttribute("user", user)→ 跳转首页。没有用 Filter,因为课设要求“逻辑清晰可见”,Filter 会把权限判断下沉到容器层,学生调试时看不到session是何时创建、何时失效。

LoginServlet.java关键片段:

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username = request.getParameter("username"); String password = request.getParameter("password"); // 1. MD5 加密(课设简化,不加盐) String md5Pass = DigestUtils.md5Hex(password); // 2. JDBC 查询(使用 DbUtils 简化) QueryRunner qr = new QueryRunner(JdbcUtil.getDataSource()); User user = qr.query("SELECT * FROM user WHERE username=? AND password=?", new BeanHandler<>(User.class), username, md5Pass); if (user != null) { // 3. 写入 session,注意 setMaxInactiveInterval(30*60) 是 30 分钟超时 HttpSession session = request.getSession(); session.setAttribute("user", user); session.setMaxInactiveInterval(30 * 60); // 秒 // 4. 重定向到首页,避免 F5 刷新重复提交 response.sendRedirect(request.getContextPath() + "/index.jsp"); } else { request.setAttribute("msg", "用户名或密码错误"); request.getRequestDispatcher("/login.jsp").forward(request, response); } }

逻辑说明:DigestUtils.md5Hex()来自commons-codec,比手写 MessageDigest 简洁;QueryRunner是commons-dbutils的核心类,自动关闭连接,避免手动 try-catch;response.sendRedirect()是重定向,URL 变为/index.jsp,而forward()是服务器端转发,URL 不变——此处必须重定向,否则登录后地址栏仍是/login,用户刷新会重新提交表单。

4.2 预约提交:事务控制在哪一层?为什么不在 Service?

本项目未分 Service 层,事务直接在AppointmentServlet中控制,原因:课设级项目复杂度低,且要让学生看清“ACID 如何落地”。当学生点击“提交预约”时,需同时做两件事:1)插入appointment记录;2)更新lab表的status(若满员则标为维护)。这两步必须原子性。

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // ... 参数校验(时间不能早于当前、实验室不能已满等)... Connection conn = null; try { conn = JdbcUtil.getConnection(); // 从 Druid 连接池获取 conn.setAutoCommit(false); // 关闭自动提交 QueryRunner qr = new QueryRunner(); // 插入预约 Object[] params1 = {labId, userId, startTime, endTime, 0, remark}; qr.update(conn, "INSERT INTO appointment(lab_id,user_id,start_time,end_time,status,remark) VALUES(?,?,?,?,?,?)", params1); // 更新实验室状态(伪代码:实际需查当前预约数) int currentCount = qr.query(conn, "SELECT COUNT(*) FROM appointment WHERE lab_id=? AND status=1", new ScalarHandler<>(), labId); if (currentCount >= getLabCapacity(labId)) { qr.update(conn, "UPDATE lab SET status=1 WHERE id=?", labId); // 标为维护 } conn.commit(); // 全部成功才提交 request.setAttribute("msg", "预约提交成功,等待审核"); } catch (SQLException e) { if (conn != null) { try { conn.rollback(); } catch (SQLException ex) {} } request.setAttribute("msg", "预约失败:" + e.getMessage()); } finally { JdbcUtil.close(conn); // 必须关闭,否则连接池耗尽 } request.getRequestDispatcher("/appoint_list.jsp").forward(request, response); }

参数说明:conn.setAutoCommit(false)是手动事务起点;rollback()在 catch 块中显式调用,确保异常时不残留脏数据;JdbcUtil.close(conn)封装了if(conn!=null && !conn.isClosed()) conn.close(),防止空指针。


5. 文档与部署:从 README.md 到真实机房的最后 100 米

5.1 文档结构:为什么必须包含“部署检查清单”?

提供的文档不是 Word 写的长篇大论,而是docs/目录下的 4 个纯文本文件:

  • README.md:项目简介、技术栈、目录结构(用树形图)、快速启动命令;
  • DEPLOY_CHECKLIST.md:最关键,列明上线前必须核对的 12 项,例如:
    - [ ] Tomcat bin/startup.bat 中 JAVA_HOME 指向 JDK 1.8(非 JRE) - [ ] WebContent/WEB-INF/web.xml 中 <welcome-file-list> 指向 index.jsp - [ ] lib/mysql-connector-java-5.1.49.jar 已复制到 Tomcat lib/ 目录(双份!) - [ ] 数据库连接 URL 中的 IP 改为机房内网地址(非 localhost)
  • API_REF.md:所有 Servlet 的 URL 路径、接收参数、返回 JSON 结构(如/delete_appoint?id=123);
  • TEST_CASES.md:10 个可手工执行的测试用例,含输入、预期输出、实际结果栏(供答辩演示填写)。

避坑 / 常见问题 / 排查
现象 1:部署到机房 Tomcat 后,登录页 CSS 样式丢失,按钮错位
原因:<link rel="stylesheet" href="css/style.css">中路径为相对路径,而机房 Tomcat 配置了虚拟路径(如http://lab.edu:8080/booking/),实际资源应为/booking/css/style.css。
解决:JSP 中改用<link rel="stylesheet" href="${pageContext.request.contextPath}/css/style.css">,${pageContext.request.contextPath}动态获取应用上下文路径。

现象 2:管理员审核预约后,学生端“我的预约”列表不刷新
原因:appoint_list.jsp使用request.setAttribute()传递数据,但审核操作在AuditServlet中完成,未重定向回该页面,导致页面缓存旧数据。
解决:AuditServlet审核后response.sendRedirect(request.getContextPath() + "/appoint_list.jsp");,强制刷新。

现象 3:机房防火墙拦截邮件发送,MailUtil.send()报Connection refused
原因:课设中邮件通知用的是javax.mail直连 QQ 邮箱 SMTP,但机房出口屏蔽 465 端口。
解决:临时注释MailUtil.send()调用,或改用学院 SMTP 服务器(需提供 host/port/auth 信息),文档中明确标注“邮件功能需按实际环境配置”。

5.2 一键部署脚本:Windows 下的 deploy.bat

为应对答辩现场重装系统,编写deploy.bat(Windows)和deploy.sh(Linux),内容高度收敛:

@echo off echo 正在停止 Tomcat... call "D:\apache-tomcat-8.5.99\bin\shutdown.bat" echo 正在清空 webapps... rd /s /q "D:\apache-tomcat-8.5.99\webapps\lab_booking" mkdir "D:\apache-tomcat-8.5.99\webapps\lab_booking" echo 正在复制项目文件... xcopy /E /I "WebContent\*" "D:\apache-tomcat-8.5.99\webapps\lab_booking\" xcopy /E /I "lib\*" "D:\apache-tomcat-8.5.99\webapps\lab_booking\WEB-INF\lib\" echo 正在导入数据库... mysql -u root -p123456 lab_booking < sql\init_db.sql echo 正在启动 Tomcat... call "D:\apache-tomcat-8.5.99\bin\startup.bat" echo 部署完成!访问 http://localhost:8080/lab_booking/login.jsp pause

逻辑说明:xcopy /E /I递归复制并自动创建目录;mysql -u root -p123456直接传密码(仅限课设环境,生产禁用);脚本末尾pause防止窗口闪退,方便查看错误。此脚本让答辩学生 30 秒内重装整个系统,是评委眼中“工程素养”的具象化。


6. 进阶技巧:让这个 JavaWeb 项目在答辩中脱颖而出的 3 个细节

6.1 时间控件:用原生 HTML5 input[type="datetime-local"] 替代 jQuery UI

很多项目还在用老旧的jquery.datetimepicker.js,加载慢、样式丑、移动端不兼容。本项目在appoint_form.jsp中直接用 HTML5 原生控件:

<input type="datetime-local" id="start_time" name="start_time" min="<%= new SimpleDateFormat("yyyy-MM-dd'T'HH:mm").format(new Date()) %>" required>

min属性动态设为当前时间(格式2024-05-20T14:30),浏览器自动禁用历史时间选项。无需引入任何 JS 库,Chrome/Firefox/Edge 均支持,iOS Safari 需 iOS 16.4+(课设环境足够)。

参数说明:<%= ... %>是 JSP 表达式,服务端渲染时计算new Date(),避免客户端 JS 时间与服务器不同步;required属性触发浏览器原生校验,比if(!val) alert()更专业。

6.2 数据库连接池:Druid 而非 C3P0,监控页面开箱即用

项目lib/中包含druid-1.2.16.jar,JdbcUtil.java初始化代码如下:

public class JdbcUtil { private static DataSource dataSource; static { Properties props = new Properties(); props.load(JdbcUtil.class.getClassLoader().getResourceAsStream("druid.properties")); dataSource = DruidDataSourceFactory.createDataSource(props); } public static DataSource getDataSource() { return dataSource; } }

druid.properties内容精简:

driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/lab_booking?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=GMT%2B8 username=root password=123456 initialSize=5 maxActive=20 minIdle=5 timeBetweenEvictionRunsMillis=60000 validationQuery=SELECT 1 testWhileIdle=true

部署后访问http://localhost:8080/lab_booking/druid/index.html(Druid 内置监控页),可实时查看连接池活跃数、SQL 执行耗时、慢 SQL 日志——答辩时打开此页,评委立刻感知“这不是 demo,是真系统”。

6.3 日志埋点:用 log4j2 记录关键业务动作

在LoginServlet和AppointmentServlet中加入日志,不为运维,为答辩举证:

private static final Logger logger = LogManager.getLogger(LoginServlet.class); // ... logger.info("用户[{}]登录成功,角色:{},IP:{}", username, user.getRole(), request.getRemoteAddr());

log4j2.xml配置滚动日志:

<RollingFile name="FileAppender" fileName="logs/app.log" filePattern="logs/app-%d{yyyy-MM-dd}-%i.log.gz"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> <Policies> <TimeBasedTriggeringPolicy /> <SizeBasedTriggeringPolicy size="10 MB"/> </Policies> </RollingFile>

答辩时展示logs/app.log中的一行:14:22:03.123 [http-nio-8080-exec-2] INFO c.l.s.LoginServlet - 用户[student001]登录成功,角色:0,IP:192.168.1.102,证明系统具备可观测性。

我带过三届课设,学生常犯的错是:花两周调通功能,却在答辩前一晚发现web.xml里 servlet-mapping 路径写错,或者init_db.sql忘了改密码。所以现在我教学生第一件事,就是写完代码立刻跑deploy.bat,再打开 Chrome 无痕窗口输一遍http://localhost:8080/lab_booking/login.jsp——能一键部署、一键验证、一键回滚,才是 JavaWeb 的真功夫。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询