☰
基于JSP+Servlet+MySQL的会议室管理系统设计与实现
2026/9/28 11:34:19 网站建设 项目流程

接到一个用传统技术栈——javaweb体系下的jsp+servlet,后端搭配mysql数据库——做会议室管理系统的需求时,我第一反应是有点恍惚。毕竟现在Spring Boot、Vue一类的框架满天飞,JSP+Servlet似乎已经被很多教程归为“毕业设计专用技术栈”。但真正动手做完一遍,我反而觉得这套组合被低估了:它逼着你把HTTP请求、Session生命周期、JDBC、数据库设计这些最底层的东西彻底搞明白,而这些恰恰是很多直接上手Spring Boot的人最容易忽略的短板。

这篇文章就围绕这个基于javaweb和mysql的jsp+servlet会议室管理系统来写。我会从数据库设计讲到Servlet请求流转,再讲到JSP页面渲染,最后把部署和踩坑经验一并整理了。不管你是正在做课程设计、毕业设计,还是单纯想补一补传统JavaWeb的基础,这篇都能直接照着做。

1. 为什么都202X年了,我还要手写一套JSP+Servlet的会议室系统

1.1 这套技术栈没有过时,而是回归了本质

Spring Boot之所以受欢迎,是因为它把很多烦人的配置和底层逻辑都封装好了。但封装也意味着学习者在遇到问题时,往往只能在框架层打转,真正问他“一个HTTP请求从浏览器到服务器,再回到浏览器,中间到底发生了什么”,他说不清楚。

JSP+Servlet这套旧组合恰好能把这个过程掰开揉碎:

  • 浏览器发起请求,Tomcat收到后交给对应的Servlet。
  • Servlet调用DAO层访问MySQL,处理业务逻辑。
  • Servlet把结果转发或重定向到JSP页面。
  • JSP在服务器端被翻译成Servlet,动态拼出HTML,返回给浏览器。

整个链路没有任何隐藏魔法。会议室管理系统的业务量不大,用这套技术栈完全够跑,而且能让你亲手体会到每个环节的职责。

1.2 项目到底要做什么:功能边界和角色划分

做系统之前,先把边界划清楚,否则越做越散。一个标准的会议室管理系统,核心就是两块:会议室资源和预约流程。

我落地时定了两个角色:

  • 普通用户:登录、注册、查看会议室列表、发起预约申请、查看和取消自己的预约、查看审批结果。
  • 管理员:在普通用户功能之外,能维护会议室信息(增删改查)、审核预约(通过或驳回)、查看所有预约记录。

不需要做花哨的报表和统计,把预约状态流转做严谨,就已经超过很多课程设计作品了。功能多了反而容易暴露代码逻辑漏洞,比如权限绕过、时间冲突没校验。

1.3 整体架构:MVC在传统项目里怎么落地的

这个项目的架构可以在脑子里建立这样一个模型:

层次技术载体职责
视图层JSP、HTML、CSS、JavaScript展示数据、收集用户输入
控制层Servlet接收请求、调用业务对象、决定跳转
模型层JavaBean、DAO封装数据、访问MySQL数据库
数据库MySQL持久化存储用户、会议室、预约信息

我习惯把DAO单独抽一层,不直接在Servlet里写JDBC代码。这样Servlet只负责“对接前端请求和后台业务”,数据库操作集中在几个类里,后续好维护,答辩问到分层设计时也讲得清楚。

2. 数据库设计:四张核心表撑起整个会议预约流程

2.1 表结构设计:用户表、会议室表、预约表

数据库设计是整个系统的地基。我实际使用的四张表如下(第四张是会话记录表,也可以不用,用Session即可,但为了学生答辩能多讲点东西,我把操作日志表也建了,下面先给三张核心表)。

CREATE TABLE `user` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `username` VARCHAR(50) NOT NULL COMMENT '登录名', `password` VARCHAR(64) NOT NULL COMMENT '密码,建议加密存储', `real_name` VARCHAR(50) DEFAULT NULL COMMENT '真实姓名', `role` TINYINT(4) NOT NULL DEFAULT 1 COMMENT '1-普通用户 2-管理员', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `meeting_room` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `room_name` VARCHAR(100) NOT NULL, `location` VARCHAR(200) DEFAULT NULL, `capacity` INT(11) DEFAULT 0 COMMENT '容纳人数', `equipment` VARCHAR(500) DEFAULT NULL COMMENT '设备说明,投影仪/白板等', `status` TINYINT(4) NOT NULL DEFAULT 1 COMMENT '1-可用 0-停用', PRIMARY KEY (`id`) ) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `reservation` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `room_id` INT(11) NOT NULL, `user_id` INT(11) NOT NULL, `title` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '会议主题', `reserve_date` DATE NOT NULL COMMENT '预约日期', `start_time` TIME NOT NULL, `end_time` TIME NOT NULL, `status` TINYINT(4) NOT NULL DEFAULT 0 COMMENT '状态:0-待审核 1-已通过 2-已驳回 3-已取消', `reason` VARCHAR(255) DEFAULT NULL COMMENT '驳回或取消原因', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `audit_time` DATETIME DEFAULT NULL, `audit_user_id` INT(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_room_date` (`room_id`, `reserve_date`) ) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;

设计细节上,我特别强调了几点:

  • 表名尽量避免使用保留字,比如user在某些环境要加反引号,如果你嫌麻烦可以改成sys_user。
  • reservation表必须建联合索引(room_id, reserve_date),因为查询“某会议室某天有哪些预约”是最频繁的操作,没索引的表,数据一多就开始卡。

2.2 时间冲突校验:为什么不能只靠程序判断

会议室预约最核心的校验是时间冲突。同一间会议室、同一天,两条预约的时间段不能重叠。

我见过很多人只在Java代码里写判断,把预约列表中已通过的记录查出来,再跟新提交的时间段比较大小。这在单机下勉强能用,但存在一个很脏的隐患:并发请求同时提交时,两次查询都没有查到冲突,结果都插入成功,数据就错了。

更可靠的做法是双保险:

  • 第一层:数据库层面,用SQL查重叠区间。
  • 第二层:程序里面再提示用户“该时段已被预约”。

重叠判断SQL可以这么写:

SELECT COUNT(*) FROM reservation WHERE room_id = ? AND status = 1 AND reserve_date = ? AND ((start_time < ? AND end_time > ?) OR (start_time < ? AND end_time > ?));

注意这里的时间比较逻辑:新预约是[a, b),已存在预约是[c, d),只要c < b and a < d就说明有重叠。上面的SQL把两种传入方向都覆盖了。

2.3 初始化数据与默认值陷阱

建表之后要插入初始化数据。管理员账号、默认会议室都是刚需:

INSERT INTO sys_user (username, password, real_name, role) VALUES ('admin', '202cb962ac59075b964b07152d234b70', '系统管理员', 2);

密码字段我给出的是纯MD5的示例,202cb962ac59075b964b07152d234b70对应明文123。实际项目中建议加盐或者用BCrypt,课程设计阶段至少别把明文存进去。

这里提一个MySQL 8.0的坑:大多数MySQL版本里,TIME和DATETIME类型字段设置默认值很麻烦,比如start_time TIME DEFAULT '09:00'在低版本直接报错。所以我的表设计里,时间字段都没有用默认值,而是靠Java代码在插入时填好。

2.4 MySQL连接层面的常见坑:驱动类名、时区、socket错误

搜索mysql安装教程的人特别多,但我发现真正让新人卡住的往往不是安装,而是连接。MySQL 5.x和8.x的连接参数差异很大,下面这个表你保存好:

配置项MySQL 5.xMySQL 8.x
JDBC驱动类com.mysql.jdbc.Drivercom.mysql.cj.jdbc.Driver
连接URLjdbc:mysql://localhost:3306/meetingjdbc:mysql://localhost:3306/meeting?serverTimezone=Asia/Shanghai&useSSL=false&characterEncoding=utf8
驱动JAR包mysql-connector-java-5.1.x.jarmysql-connector-java-8.0.x.jar

MySQL8如果不写serverTimezone=Asia/Shanghai,运行时会抛The server time zone value '�й���ʱ��' is unrecognized的异常。连接不上还有一种典型报错:

ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock'

这个报错我在Linux服务器上遇到好几次。它根本原因不是你的Java代码有问题,而是MySQL服务没有启动,或者客户端指定的socket文件路径和MySQL实际生成的socket路径不一致。排查路径很简单:

  • 服务端:执行service mysql status,确认mysql进程是否存活。
  • socket路径:执行mysql -uroot -p测试能否本地登录;如果本地都登录不了,说明服务确实没起来。
  • 如果你确定服务是启动的,那就在连接时改host为127.0.0.1而不是localhost,让JDBC走TCP而不是走socket文件。

3. Servlet为核心的请求流转:登录、预约、审批怎么组织代码

3.1 登录与Session管理:不要只做页面存个值

登录是所有业务系统的入口。在用JSP+Servlet做登录时,最容易被忽略的是:登录态存哪里、怎么保证后续每个请求都需要验证。

我的做法如下:

  • LoginServlet接收username和password。
  • 调用LoginDAO查询数据库,比对密码(用MD5后与库中比较)。
  • 用户存在且密码正确,就把user对象放入Session:
HttpSession session = request.getSession(); session.setAttribute("loginUser", user);
  • 返回重定向到首页,而不是转发。

对应地,写一个LoginFilter实现JavaWeb里的Filter接口,在doFilter方法中检查Session:

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpSession session = request.getSession(false); HttpServletResponse response = (HttpServletResponse) resp; if (session == null || session.getAttribute("loginUser") == null) { response.sendRedirect(request.getContextPath() + "/login.jsp"); return; } chain.doFilter(request, response); }

有了这个Filter,就不会再出现“直接在浏览器地址栏输入list.jsp就能绕过登录”的低级漏洞了。忘了聊Filter在web.xml里配置时,要把login相关的Servlet路径排除掉,否则登录页面本身也被拦截,导致死循环。

3.2 Servlet路径设计和请求处理组织

传统的Servlet项目,路径规范的两种方式我都在项目里用了。一种是改web.xml,一种是直接用注解@WebServlet("/reserve/add"),我建议新项目直接用注解,代码即文档,少一个配置文件少一个坑。

这个项目的Servlet可以这样划分:

Servlet路径职责
LoginServlet/login登录
LogoutServlet/logout退出登录
RegisterServlet/register用户注册
RoomListServlet/room/list会议室列表
RoomEditServlet/room/edit管理员新增/编辑会议室
ReserveAddServlet/reserve/add提交预约申请
MyReserveServlet/reserve/my我的预约列表
ApproveServlet/reserve/approve管理员处理预约申请
CancelServlet/reserve/cancel用户取消预约

核心思想是一个Servlet只处理一类资源操作,不要写成一个万能Servlet,传个参数区分事务。那样子代码膨胀到一个类五六百行,排查问题时谁看谁崩溃。

每个Servlet内部的标准处理模板:

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); boolean success = service.doReserve(request); if (success) { response.sendRedirect(request.getContextPath() + "/reserve/my"); } else { request.setAttribute("errorMsg", "预约失败,时间冲突或会议室已停用"); request.getRequestDispatcher("/reserve/add.jsp").forward(request, response); } }

通过这种模式,你能清楚看到JSP+Servlet的MVC流转:表单POST到Servlet,Servlet调用Service/DAO层执行数据库操作,失败则带着错误消息转发回JSP,成功则重定向避免重复提交。

3.3 审批状态机的设计:状态字段驱动业务

预约审批的难点不是表结构,而是状态流转。我在代码里定义了一个ReservationStatus常量类:

public class ReservationStatus { public static final int PENDING = 0; public static final int APPROVED = 1; public static final int REJECTED = 2; public static final int CANCELED = 3; }

流转规则如下:

  • 用户提交预约,状态为PENDING。
  • 管理员看到待审核列表,可把状态置为APPROVED或REJECTED。
  • 用户只能对PENDING状态的预约执行取消(置为CANCELED)。
  • 状态一旦变成APPROVED,用户不能直接取消,必须联系管理员处理。这个设定可以防止“我已经批准了,会议室也锁定了,用户又取消导致会议室闲置”的尴尬场景。

在SQL更新时,一定要带上前置状态条件。比如管理员审核通过:

UPDATE reservation SET status = 1, audit_time = NOW(), audit_user_id = ? WHERE id = ? AND status = 0;

这里执行后,如果返回影响行数为0,说明这条预约已经被别人处理过了(比如用户已取消),不应该再操作成功。

3.4 表单重复提交、数据回显和重定向

传统Servlet项目有个经典问题:用户点了一次提交,结果页面刷新时又弹出“重新提交表单”,然后重复插入了一条预约数据。

解决方案就是上面提到的“POST/REDIRECT/GET”模式:

  • 表单POST提交到Servlet处理。
  • 处理成功后,Servlet不转发回JSP,而是生成一个302重定向响应,让浏览器重新发起GET请求到列表页。
  • 这时刷新页面,浏览器只会刷新GET响应,不会再次提交POST。

如果处理失败,需要带错误提示回显时,才用forward转发到表单页,这样用户填写的部分内容还在表单里。实际开发中,我发现还有一种要用到的小技巧:给表单加一个隐藏的随机token,提交时校验,也能防重复提交,但对于课程项目,POST/REDIRECT/GET这个习惯已经能防住90%的问题了。

4. JSP页面的动态渲染与HTML页面的分工

4.1 JSP本质就是一个Servlet,但千万别把业务逻辑写进去

很多新人对JSP有个误解:既然JSP里能写Java代码,是不是业务逻辑放JSP里也无所谓?答案是否定的。

JSP在运行时会被Tomcat翻译成一个Servlet,再编译成class执行。所以你在JSP里写<% if(user.getRole()==2) { %>这类逻辑,最终执行的还是一个Servlet Java类。但把大量业务写进JSP,会导致:

  • 页面源代码混杂Java控制流,前端同学完全没法维护。
  • 出错时没有编译期检查一堆运行时异常。
  • 违背MVC职责划分,答辩老师一看就给差评。

我的底线是:JSP里只允许出现EL表达式和JSTL自定义标签,不写原生Java代码。页面顶部可以做权限判断、遍历列表,但所有的数据,都应该由Servlet提前放到request域中。

比如会议室未读列表循环:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> ... <ul> <c:forEach items="${roomList}" var="room"> <li>${room.roomName} - 容纳${room.capacity}人 - ${room.location}</li> </c:forEach> </ul>

${room.roomName}这种写法就是EL表达式,它会自动调用Room.roomName的getter方法,比用<%=room.getRoomName()%>清爽得多。

4.2 预约表单页的开发细节

预约表单是这个系统最重要的用户输入入口。JSP页面里必须把“可选会议室”和“日期”“开始时间”“结束时间”做一个组合联动。

  • 可选会议室:从RoomListServlet查询所有状态为1的会议室,通过JSTL循环生成下拉框。
  • 日期输入:我直接用了input type="date",这个HTML原生控件比自己拼字符串日历强多了。
  • 时间范围:用两个input type="time",再写一个JS校验startTime < endTime,不合格直接拦截提交,减少后端无谓的请求。

当然,前端校验只是提升体验,真正的合法性判断和冲突校验仍要在Servlet后端做。

页面回显时有一个细节:如果表单校验失败被转发回来,request.getParameter("startTime")是可以直接取到刚提交的值的。在JSP里回填:

<input type="time" name="startTime" value="${param.startTime}" required />

${param.startTime}是EL表达式读取请求参数的快捷方式,不用手动在Servlet里再次setAttribute。

4.3 纯HTML页面在传统项目里的重要地位

这个项目的技术栈里也有HTML的标签。很多人以为JSP项目的界面全用jsp文件,其实静态HTML页面也扮演关键角色,尤其是在混合开发模式下:

  • 登录页可以用静态login.html做高颜值页面,用户输入后通过fetch或jQuery的ajax把JSON数据POST到/login,Servlet返回JSON字符串,页面拿到结果再跳转。
  • 会议室管理后台的表格,也可以用静态HTML页面拉取RoomListServlet返回的最新JSON数据来渲染,减少整页刷新。

在这个项目里,我建议你至少把登录页写成静态HTML + AJAX,这种“前端动态渲染、后端返回JSON”的合作方式,实际上是很多公司里旧项目升级换代的第一步。让项目展示的时候,可以很自然地说一句:“这里我做了一个前后端分离的过渡体验。”

Servlet返回JSON也很简单,不需要额外的json库,手动拼也够用:

response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{\"success\": true, \"msg\": \"登录成功\"}");

当然,如果是更复杂的列表数据,建议引入fastjson或者Jackson,别手拼JSON字符串,容易被引号转义搞到怀疑人生。

5. 开发与调试:从IDEA配置到WAR包部署的完整链路

5.1 环境配置:JDK、Tomcat、IDEA三者缺一不可

开发JSP+Servlet项目,环境配置环节就能劝退一批人。我的推荐组合:

组件版本建议备注
JDK8 或 11不要轻易上JDK17,老Tomcat版本不兼容
Tomcat8.5 或 9.0对应Java EE规范,支持Servlet 4.0
IDEAUltimate版社区版对Web项目支持稍弱
构建工具Maven或直接用IDEA的Artifacts打WAR

如果你第一次在IDEA里跑JavaWeb项目,记住一个核心操作:需要在Run Configuration里添加一个Tomcat Server - Local,然后在Deployment标签页添加war exploded工件,Application context建议填/meeting_system。

很多人卡在“代码能编译但启动后访问404”,八成是IDEA里的Tomcat部署配置没有做对,没把Web模块加到部署列表里。

5.2 MySQL连接池:从DriverManager到Druid的升级

传统的DriverManager.getConnection()在会议室管理系统这种教学项目里常见,但如果你熟悉真实工程实践,应该认识一下连接池。实现方案我推荐阿里巴巴的Druid,配置简单,还有监控页面可以用。

先引入依赖:

<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.20</version> </dependency>

然后在工具类里初始化连接池:

public class DBUtil { private static DruidDataSource dataSource; static { dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/meeting_system?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false"); dataSource.setUsername("root"); dataSource.setPassword("123456"); dataSource.setInitialSize(5); dataSource.setMaxActive(20); dataSource.setMinIdle(5); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }

用连接池的好处是:每个请求来的时候不需要重新创建物理连接,用完的连接直接还回池里,性能提升非常明显。测试阶段你可以肉眼看到系统在并发请求下不卡顿了。

5.3 传统JSP项目打包WAR并部署

搜索引擎里“传统jsp项目打包war”这个热搜,说明这个问题困扰了很多人。打包WAR部署是课程设计展示和答辩前的最后一道关卡。

有两种打包方式:

  • 方式一:IDEA自带Artifacts → 选Web Application: Archive → For '项目名:war exploded' → Build Artifacts,生成war包。
  • 方式二:Maven install,前提是项目结构按Maven规范建好。

打包之后把war文件放到$CATALINA_HOME/webapps目录下(Tomcat安装目录),启动Tomcat,它会自动解压war包,访问路径就是war包的名字。例如包名叫meeting.war,访问地址是http://localhost:8080/meeting/。

部署时遇到过最恶心的问题就是乱码。排查顺序很固定:

  1. 页面响应乱码:检查JSP顶部<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>。
  2. 数据库存取乱码:检查数据库连接URL里是否带characterEncoding=utf8。
  3. 控制台乱码:检查IDEA的Help -> Edit Custom VM Options,加一行-Dfile.encoding=UTF-8。
  4. 请求参数乱码:写一个CharacterEncodingFilter把所有请求和响应编码统一为UTF-8。
@WebFilter("/*") public class EncodingFilter implements Filter { private FilterConfig config; public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); chain.doFilter(request, response); } }

这个Filter应该是所有JavaWeb项目的标配,但很多教程居然都没讲。

5.4 Servlet生命周期和JSP执行过程:答辩常考考点

面试和答辩必问的“Servlet生命周期”,其实在这个项目里有机会真实体会到。

Servlet生命周期分三步:

  • init():Servlet首次被请求时创建,只执行一次。可以在初始化阶段加载全局配置。
  • service():每次请求都会执行,Tomcat根据请求方法分发到doGet或doPost。
  • destroy():Web应用卸载或Tomcat关闭时执行,释放资源。

如果项目用了数据库连接池,可以把连接池初始化动作放到一个专门的InitListener里,用ServletContextListener在应用启动时初始化,关闭时释放池。这也是评分卡上的加分项。

JSP同样有生命周期。当浏览器第一次请求某个.jsp页面时,Tomcat会把它翻译成一个Java文件,再编译成class,之后每次请求都直接执行这个Servlet实例。这也是为什么JSP第一次访问慢、后面快的原因。

6. 安全与质量:这些细节决定了项目能得多少分

6.1 预编译SQL与SQL注入防护

会议室管理系统的搜索和预约条件很多,最容易写出拼接SQL的代码。比如新手为了省事,可能这么写:

String sql = "SELECT * FROM meeting_room WHERE room_name LIKE '%" + keyword + "%'";

这条SQL在单机测试时没问题,但用户在搜索框输入一个' OR '1'='1,整张表数据就被带出来了,这就是最典型的SQL注入。

正确做法是使用PreparedStatement:

String sql = "SELECT * FROM meeting_room WHERE room_name LIKE ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, "%" + keyword + "%");

预编译的好处很直白:SQL结构在编译时已固定,用户输入永远只是参数,不会参与SQL语法解析。这个习惯从JSP+Servlet阶段就要养好,等你以后接触MyBatis时,对#{}和${}的区别理解会通透得多。

6.2 密码安全与敏感操作权限校验

密码安全前面提过要加密存储,这里再补一个实操方案:

用户注册时,后端拿到明文密码,做一次加盐MD5。存储格式简化为盐值 + MD5(盐值 + 明文密码),登录时从库里取出盐值重新计算比对。

另一个容易漏的是权限校验。很多课程项目只在页面上判断管理员身份,比如显示“审核”按钮或者不显示,但是坏人可以直接构造一个POST请求发给/reserve/approve接口。所以,必须在Servlet内部再校验一次登录用户角色:

User loginUser = (User) request.getSession().getAttribute("loginUser"); if (loginUser == null || loginUser.getRole() != 2) { response.sendError(403); return; }

记住这条黄金法则:前端控制一切只是用户体验,后端校验才是安全边界。

6.3 连接池泄露与资源释放的细节

使用连接池后,有一个非常隐蔽的坑:连接泄漏。getConnection()之后,如果代码某处抛了异常且没有在finally里close(),这个连接就永远不会回到池子里,当池子里的连接耗尽时,整个系统会卡死或报Connection is not available, request timed out。

我的经验是,所有获取连接的代码都用try-with-resources结构:

try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { while (rs.next()) { ... } } catch (SQLException e) { e.printStackTrace(); }

这样不管正常还是异常路径,连接都能归还到连接池。就这一条,能帮你避免掉生产环境里80%的数据库连接问题。

6.4 关于接口返回结果再包装一下:状态码与消息统一

如果你打算在项目里做基于HTML+AJAX的交互,那么接口返回统一格式这个习惯越早建立越好。我给自己约定的统一字段:

{ "code": 200, "msg": "操作成功", "data": {...} }

code表示业务状态码,200成功,500失败,权限不足用403。这样前端用Ajax拿到响应后统一判断code === 200决定是否跳转或刷新,逻辑会简洁很多,另外也为答辩时讲“代码可扩展性”准备了谈资。

我在这个项目里写了一个简单的Result工具类,用静态方法生成标准响应。代码本身不长,但对整个工程代码风格的统一非常有帮助。

这就是我从数据库、Servlet、JSP到部署调试全流程做这个会议室管理系统的核心经验。最后再分享一个我自己的实操心得:如果你打算拿这个项目做毕业设计或课程作业,千万不要临到答辩才把项目跑起来。把运行环境的配置步骤、数据库初始化脚本、访问地址写成一个README文档,答辩现场照着步骤几分钟内就能启动演示,印象分至少能提一档。没人喜欢看一个满头大汗在命令行里敲错命令的演示者,稳才是硬道理。

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

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

立即咨询