简介这是一套面向Java Web初学者与课程设计者的会议室预约系统完整源码基于JSPServletJavaBean技术栈实现可解决企业或组织内部会议室资源冲突、预约流程繁琐的问题。系统区分管理员与员工两类角色管理员负责部门、员工账户、会议室信息及系统公告的维护员工则可查看公告、按日期与时段提交或取消会议室预订覆盖权限控制、数据库CRUD与前端表单验证等典型业务场景。压缩包共744个文件约5.01MB以gif、js、html、css等前端资源为主另含jsp页面、jar依赖、sql脚本及少量java源码便于直接部署到Tomcat运行。目前已有1657人学习下载适合作为毕业设计、课程实训或JSP入门练手项目帮助读者快速理解MVC分层结构、Session身份验证与数据库操作流程。1. 从一张排班表说起这套 JSP 会议室预约系统到底解决什么问题很多公司行政还在用 Excel 排会议室谁先抢到谁用冲突了就在群里吵。这套 JAVA JSP 会议室预约系统就是冲着这个场景来的——把会议室资源、时间段、申请人三者用一张预约表锁死前端用 JSP 渲染页面后端用 Servlet 处理逻辑数据落 MySQL。它适合两类人一是想拿一个完整 CRUD 项目练手 JSP Servlet 的在校开发者二是小团队里需要一个能跑在内网 Tomcat 上的轻量预约工具。整个项目没有 Spring 那套重框架纯原生 JSP JDBC部署简单改起来也直接。下面我按「能干什么 → 怎么跑 → 怎么改」的顺序拆一遍。2. 技术选型与目录结构为什么用 JSP Servlet 而不是上框架2.1 这套技术栈的取舍逻辑先说实话JSP Servlet 放到今天不算时髦但这个项目选它是有道理的。会议室预约系统的业务复杂度很低用户登录、会议室列表、提交预约、查看我的预约、管理员审批满打满算五六个功能点。上 Spring Boot 反而要引入一堆依赖和配置部署包也大。用 JSP Servlet 的好处是Tomcat 一扔就能跑JSP 里直接写 Java 代码片段调试时改完刷新页面就生效不用重启整个应用。具体技术组成是这样的层次技术作用视图层JSP JSTL渲染会议室列表、预约表单、审批页面控制层Servlet接收请求、调用 Service、转发页面业务层普通 Java 类预约冲突校验、权限判断数据层JDBC MySQL增删改查预约记录和会议室信息运行环境Tomcat 8/9 JDK 8Web 容器和编译环境这里有个关键点JSP 里不要写业务逻辑只做展示。我见过太多项目把 JDBC 代码直接写在 JSP 的% %里后期改一个字段要翻十几个页面。这套项目的正确做法是 Servlet 收请求、Service 做校验、DAO 管数据库JSP 只负责把数据摆出来。2.2 目录结构长什么样一个标准的 JSP Web 项目目录大致如下我按实际开发习惯补全了包结构MeetingRoom/ ├── src/ │ └── com/ │ └── meeting/ │ ├── dao/ # 数据库访问层 │ │ ├── UserDao.java │ │ ├── RoomDao.java │ │ └── BookingDao.java │ ├── model/ # 实体类 │ │ ├── User.java │ │ ├── Room.java │ │ └── Booking.java │ ├── service/ # 业务逻辑 │ │ └── BookingService.java │ ├── servlet/ # 控制器 │ │ ├── LoginServlet.java │ │ ├── RoomListServlet.java │ │ └── BookingServlet.java │ └── util/ │ └── DBUtil.java # 数据库连接工具 ├── WebContent/ │ ├── css/ │ ├── js/ │ ├── WEB-INF/ │ │ ├── web.xml # Servlet 映射配置 │ │ └── lib/ # 依赖 jar 包 │ ├── login.jsp │ ├── room_list.jsp │ ├── booking_form.jsp │ └── my_bookings.jsp └── sql/ └── init.sql # 建表和初始数据WEB-INF目录下的 JSP 不能被浏览器直接访问必须经过 Servlet 转发这是做权限控制的基础。lib目录放 MySQL 驱动和 JSTL 的 jar 包Tomcat 启动时会自动加载。2.3 数据库表设计三张核心表就能撑起整个系统-- 用户表 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, role TINYINT DEFAULT 0 -- 0普通用户 1管理员 ); -- 会议室表 CREATE TABLE t_room ( id INT PRIMARY KEY AUTO_INCREMENT, room_name VARCHAR(50) NOT NULL, capacity INT DEFAULT 10, location VARCHAR(100), status TINYINT DEFAULT 1 -- 1可用 0停用 ); -- 预约表 CREATE TABLE t_booking ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, room_id INT NOT NULL, book_date DATE NOT NULL, start_time TIME NOT NULL, end_time TIME NOT NULL, status TINYINT DEFAULT 0, -- 0待审批 1已通过 2已拒绝 create_time DATETIME DEFAULT CURRENT_TIMESTAMP );t_booking表是核心book_date、start_time、end_time三个字段决定了冲突判断的逻辑。注意status字段预约提交后默认是待审批状态管理员通过后才算真正占用。3. 核心模块拆解从登录到预约冲突校验的完整链路3.1 数据库连接工具类先看最底层的DBUtil所有 DAO 都靠它拿连接public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/meeting_room?useSSLfalseserverTimezoneUTC; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL驱动加载失败, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, Statement stmt, ResultSet rs) { // 依次关闭注意顺序rs - stmt - conn try { if (rs ! null) rs.close(); } catch (SQLException ignored) {} try { if (stmt ! null) stmt.close(); } catch (SQLException ignored) {} try { if (conn ! null) conn.close(); } catch (SQLException ignored) {} } }这里serverTimezoneUTC是必须的不加会报时区错误。close方法的关闭顺序不能反先关结果集再关语句再关连接反了会抛异常。生产环境应该用连接池但练手项目用DriverManager够了。3.2 登录 Servlet 与 Session 管理登录逻辑不复杂但 Session 处理是新手最容易翻车的地方WebServlet(/login) public class LoginServlet extends HttpServlet { private UserDao userDao new UserDao(); protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username req.getParameter(username); String password req.getParameter(password); User user userDao.findByUsername(username); if (user null || !user.getPassword().equals(password)) { req.setAttribute(msg, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); return; } // 登录成功写入 Session HttpSession session req.getSession(); session.setAttribute(currentUser, user); session.setMaxInactiveInterval(30 * 60); // 30分钟超时 resp.sendRedirect(req.getContextPath() /rooms); } }session.setAttribute(currentUser, user)这行是关键后续所有需要登录的页面都从 Session 里取用户。sendRedirect和forward的区别要搞清楚登录成功用重定向避免刷新页面重复提交登录失败用转发保留错误提示。3.3 预约冲突校验整个系统最核心的一段逻辑预约系统最怕的就是同一时间段被两个人订走。校验逻辑放在 Service 层public class BookingService { private BookingDao bookingDao new BookingDao(); /** * 检查时间段是否冲突 * return true表示可以预约false表示已被占用 */ public boolean isAvailable(int roomId, String date, String startTime, String endTime) { // 查询该会议室当天所有已通过的预约 ListBooking existing bookingDao.findByRoomAndDate(roomId, date); for (Booking b : existing) { // 时间重叠判断新开始 旧结束 且 新结束 旧开始 if (startTime.compareTo(b.getEndTime()) 0 endTime.compareTo(b.getStartTime()) 0) { return false; } } return true; } }时间重叠的判断是这段代码的灵魂。用字符串比较是因为TIME类型格式固定为HH:mm:ss字典序和实际时间顺序一致。条件startTime b.endTime endTime b.startTime覆盖了所有重叠情况完全包含、部分重叠、被包含。这个逻辑写错了要么放行冲突预约要么把正常预约误判为冲突。3.4 预约提交 ServletServlet 负责串起校验和入库WebServlet(/booking/submit) public class BookingServlet extends HttpServlet { private BookingService bookingService new BookingService(); protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session req.getSession(); User user (User) session.getAttribute(currentUser); if (user null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } int roomId Integer.parseInt(req.getParameter(roomId)); String date req.getParameter(bookDate); String start req.getParameter(startTime); String end req.getParameter(endTime); // 基础校验 if (start.compareTo(end) 0) { req.setAttribute(msg, 结束时间必须晚于开始时间); req.getRequestDispatcher(/booking_form.jsp).forward(req, resp); return; } if (!bookingService.isAvailable(roomId, date, start, end)) { req.setAttribute(msg, 该时间段已被预约请换一个时间); req.getRequestDispatcher(/booking_form.jsp).forward(req, resp); return; } bookingService.createBooking(user.getId(), roomId, date, start, end); resp.sendRedirect(req.getContextPath() /booking/my); } }注意这里做了两层校验先校验时间本身是否合法结束晚于开始再校验是否与已有预约冲突。顺序不能反否则会出现「时间都不合法还去查数据库」的浪费。4. 部署运行从零把项目跑起来的完整步骤4.1 环境准备与数据库初始化先把环境搭好缺一样都跑不起来JDK 8 或以上java -version能输出版本号Tomcat 8.5 或 9解压到无中文无空格的路径MySQL 5.7 或 8.0记住 root 密码IDE 用 Eclipse 或 IntelliJ IDEA 都行配好 Tomcat 服务器数据库初始化执行sql/init.sqlmysql -u root -p sql/init.sql执行完检查一下USE meeting_room; SHOW TABLES; SELECT * FROM t_user;应该能看到三张表和一条管理员账号。如果表是空的说明 SQL 没执行成功检查文件编码是不是 UTF-8。4.2 修改数据库配置并部署打开DBUtil.java把 URL、用户名、密码改成你本地的private static final String URL jdbc:mysql://localhost:3306/meeting_room?useSSLfalseserverTimezoneUTCcharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 你的密码;characterEncodingutf8建议加上否则中文会议室名会乱码。改完在 IDE 里把项目部署到 Tomcat启动服务器浏览器访问http://localhost:8080/MeetingRoom/login.jsp。4.3 验证功能是否正常按这个顺序点一遍能走通说明部署成功用管理员账号登录看能否进入会议室列表新增一个会议室刷新列表确认出现退出用普通账号登录提交一个预约切回管理员审批通过该预约再用普通账号提交同一时间段应该被拒绝第 5 步是验证冲突校验有没有生效的关键。如果还能提交成功回去检查isAvailable方法里的时间比较逻辑。提示Tomcat 启动报ClassNotFoundException: com.mysql.cj.jdbc.Driver说明 MySQL 驱动 jar 没放进WEB-INF/lib放进去重新部署即可。5. 避坑与常见问题排查5.1 中文乱码现象、原因、解决现象会议室名称、用户名显示成问号或方块。原因JSP 页面、Servlet 响应、数据库连接三处编码不一致。常见的是 JSP 没声明pageEncoding或者数据库连接串没加characterEncoding。解决三处统一成 UTF-8。JSP 顶部加% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %Servlet 里resp.setContentType(text/html;charsetUTF-8)数据库连接串加characterEncodingutf8。三处缺一处都可能乱码。5.2 预约冲突判断失效现象明明同一时间段已有预约系统还是放行。原因多半是isAvailable里只查了status1的记录但新提交的预约状态是 0导致自己和自己不冲突却和已通过的冲突判断漏了。或者时间比较用了而不是边界情况判断错误。解决查询已有预约时只查status1已通过的记录待审批和已拒绝的不算占用。时间比较严格用startTime b.endTime endTime b.startTime。5.3 Session 失效导致空指针现象登录后过一段时间操作页面报NullPointerException。原因Session 超时后getAttribute(currentUser)返回 null代码没做判空就直接调方法。解决每个需要登录的 Servlet 开头都加判空null 就重定向到登录页。别嫌麻烦这是最省事的防御性写法。5.4 部署后 404现象访问login.jsp报 404。原因项目上下文路径不对或者 JSP 放在了WEB-INF下被保护了。解决确认访问路径是http://localhost:8080/项目名/login.jsp项目名要和 Tomcat 部署时的 context path 一致。放在WEB-INF下的 JSP 必须通过 Servlet 转发访问不能直接输 URL。5.5 数据库连接泄漏现象用一段时间后 Tomcat 报连接数超限。原因DAO 里拿了连接没关或者异常路径下没走到 close。解决把close放在finally块里确保无论是否异常都执行。更好的做法是用 try-with-resources。6. 二次开发把预约系统改成你要的样子拿到这套代码后最值得改的三个方向我按优先级排一下。第一加连接池。现在每次请求都新建数据库连接并发一高就崩。引入 Druid 或 HikariCP把DBUtil改成从池里取连接。改法很简单加一个DruidDataSource静态实例getConnection从它取close改成归还连接。这一步做完系统能扛的并发量翻好几倍。第二加预约提醒。现在的系统提交完就完了用户容易忘。可以在BookingService里加一个定时任务每天扫描第二天的预约给对应用户发站内信或邮件。用ScheduledExecutorService起一个后台线程就行不用上 Quartz 那么重。第三把审批流做成可配置的。现在只有「待审批→通过/拒绝」两态实际场景可能需要多级审批。改法是给t_booking加一个approve_level字段Service 里根据会议室配置决定需要几级审批每级审批通过后approve_level加一到顶了才把status置为通过。改的时候有个习惯我一直保持每次动核心逻辑前先把当前能跑通的版本打个 tag 或复制一份。有次我改冲突校验把compareTo的符号写反了结果所有预约都判成冲突整个系统提交不了。幸好有备份五分钟回滚。从那以后我每次改BookingService都强制走一遍「提交正常预约 → 提交冲突预约 → 提交边界时间预约」这三步验证确认没翻车再继续。这套 JSP 会议室预约系统代码量不大但五脏俱全登录、权限、CRUD、冲突校验、审批流都有拿来练手或者当小工具的基础都合适。希望帮到你。本文还有配套的精品资源点击获取