news 2026/9/10 15:25:50

Java Web实验室管理系统:原生Servlet+JDBC分层实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java Web实验室管理系统:原生Servlet+JDBC分层实战

简介:这是一套面向Java Web初学者与课程设计者的实验室管理系统实战项目,聚焦高校实验教学场景中的资源调度、用户权限与成绩管理等核心需求。资源包含完整可运行的Java Web源码及配套MySQL数据库,涵盖用户管理(管理员/教师/学生三角色)、实验设备预约、成绩录入与通知公告等模块,代码结构清晰、功能完整,适合用于Java EE开发入门实践与毕业设计参考。压缩包共445个文件,以68个Java业务类、22个JSP页面、100个JS前端脚本、66个Jar依赖库及35个CSS样式文件为主,辅以SQL建表语句与配置文件,总大小37.79MB,目录组织规范,便于模块化学习与二次开发。目前已有434人下载学习,开发者可直接部署于本地Tomcat(http://127.0.0.1:8989),通过预置账号快速验证全流程,并基于BaseDaoImpl、Action层分层结构深入理解MVC开发模式与JDBC数据库交互实践。

1. 这不是又一个学生课设——它是一套可直接跑通、带角色权限链路、含完整 JDBC 封装的 Java Web 实验室管理骨架

你打开这个.zip包,看到BaseDaoImpl.class和一串*Action.class,第一反应可能是“又一个没注释的 JavaEE 课设”。但真正解压后你会发现:它没有用 Spring Boot 自动装配,却用BaseDaoImpl+UserDaoImpl构建出清晰的 DAO 层继承链;它没上 MyBatis,但DateUtils.class里封装了yyyy-MM-dd HH:mm:ss格式化与java.sql.Timestamp的双向转换;它甚至在login_indexs.do路由里硬编码了三角色登录逻辑(admin/js/xs),而密码明文写死在UserAction.classlogin()方法里——这不是缺陷,是教学级系统刻意暴露的权限控制起点。这套系统不追求高并发或微服务,但它把 Java Web 开发中最易被跳过的环节全摊开:从web.xmlActionServlet的手动配置,到Equipment.class里每个字段对应 MySQL 表字段的命名映射,再到PruchaseAction.class(注意拼写)中采购单状态流转的 if-else 分支。适合刚学完 Servlet 生命周期、正卡在“怎么把数据库查出来的 List 塞进 JSP 表格里”的人,也适合需要快速搭个内部实验室预约入口、又不想啃 Spring Security 文档的运维同事。

2. 从 web.xml 到 BaseDaoImpl:理解这套系统如何用原生 Java Web 构建分层结构

2.1 web.xml 是整个请求路由的总开关,必须先看懂它的四层拦截逻辑

系统运行在http://127.0.0.1:8989,说明它依赖传统 Java Web 容器(如 Tomcat 7/8)。其web.xml文件是所有请求的入口控制器,核心配置如下:

<servlet> <servlet-name>ActionServlet</servlet-name> <servlet-class>com.action.ActionServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>ActionServlet</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping>

提示:.do后缀不是约定俗成,而是ActionServlet类主动截获所有匹配该模式的请求。比如访问login_indexs.do,容器会将请求交给ActionServletdoGet()方法处理,再由它根据 URL 中的login_indexs字符串反射调用LoginAction类(虽然源码中未提供该类,但UserAction.class承担了实际登录逻辑)。

更关键的是过滤器链配置:

<filter> <filter-name>EncodingFilter</filter-name> <filter-class>com.filter.EncodingFilter</filter-class> </filter> <filter-mapping> <filter-name>EncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

这个EncodingFilter类负责统一设置request.setCharacterEncoding("UTF-8")response.setContentType("text/html;charset=UTF-8")。若跳过此步,在表单提交中文用户名时,request.getParameter("username")会返回乱码。这是 Java Web 开发中第一个必须亲手验证的环节——删掉该 filter,输入“张三”,后台打印出来就是å¼ ä¸‰

2.2 BaseDaoImpl 是 JDBC 操作的中枢,它用泛型+反射解决了 CRUD 的重复代码问题

系统未使用 Hibernate 或 MyBatis,所有数据库操作都基于 JDBC 封装。BaseDaoImpl.class是核心抽象类,其关键方法executeUpdate()executeQuery()采用模板方法模式:

public abstract class BaseDaoImpl<T> { protected Connection conn; protected PreparedStatement pstmt; // 通用更新操作:INSERT/UPDATE/DELETE public int executeUpdate(String sql, Object... params) { try { conn = JDBCUtil.getConnection(); // 从 JDBCUtil 获取连接 pstmt = conn.prepareStatement(sql); for (int i = 0; i < params.length; i++) { pstmt.setObject(i + 1, params[i]); // 参数占位符赋值 } return pstmt.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); return -1; } finally { closeResource(); // 关闭 pstmt 和 conn } } }

UserDaoImpl.class继承BaseDaoImpl<User>,并实现具体业务 SQL:

public class UserDaoImpl extends BaseDaoImpl<User> implements UserDao { @Override public User findUserByUsernameAndPassword(String username, String password) { String sql = "SELECT * FROM t_user WHERE username = ? AND password = ?"; // 注意:此处密码未加密,明文比对,仅用于教学演示 List<User> list = executeQuery(sql, username, password); return list.isEmpty() ? null : list.get(0); } }

注意:executeQuery()方法在BaseDaoImpl中返回List<T>,它通过ResultSetMetaData获取列名,再用反射调用T类的 setter 方法完成对象封装。这意味着User.class必须有与数据库字段一一对应的 setter(如setUsername(String)),且字段名需严格匹配(usernamevsuser_name会导致空值)。

2.3 DateUtils.class 解决时间类型转换的硬伤,避免 Timestamp 与 Date 的隐式转换异常

Java 中java.util.Datejava.sql.Timestamp的混用是常见崩溃点。DateUtils.class提供了两个关键静态方法:

public class DateUtils { private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 将字符串转为 Timestamp(用于 INSERT INTO) public static Timestamp stringToTimestamp(String timeStr) { try { Date date = sdf.parse(timeStr); return new Timestamp(date.getTime()); } catch (ParseException e) { e.printStackTrace(); return null; } } // 将 Timestamp 转为字符串(用于 JSP 显示) public static String timestampToString(Timestamp ts) { if (ts == null) return ""; return sdf.format(ts); } }

EquipmentAction.class处理设备入库时,前端传入"2024-05-20 14:30:00"字符串,后端必须调用DateUtils.stringToTimestamp()转为Timestamp才能安全塞入PreparedStatement。若直接pstmt.setDate(1, new java.sql.Date(...)),会丢失时分秒;若用pstmt.setTimestamp(1, ...)但传入java.util.Date,则抛出IllegalArgumentException。这个工具类的存在,正是系统能稳定运行的关键细节。

2.4 角色权限控制不是靠框架,而是靠 Action 层的硬编码分支判断

系统预设三角色:admin(管理员)、js(教师)、xs(学生),其权限差异体现在UserAction.classlogin()方法中:

public String login() { String username = request.getParameter("username"); String password = request.getParameter("password"); User user = userDao.findUserByUsernameAndPassword(username, password); if (user != null) { // 硬编码角色判断 if ("admin".equals(username)) { session.setAttribute("user", user); session.setAttribute("role", "admin"); return "redirect:/admin/index.jsp"; // 跳转管理员首页 } else if ("js".equals(username)) { session.setAttribute("user", user); session.setAttribute("role", "teacher"); return "redirect:/teacher/index.jsp"; } else if ("xs".equals(username)) { session.setAttribute("user", user); session.setAttribute("role", "student"); return "redirect:/student/index.jsp"; } } request.setAttribute("msg", "用户名或密码错误"); return "forward:/login.jsp"; }

提示:这种写法虽不满足生产环境的 RBAC(基于角色的访问控制)规范,但它是理解权限控制本质的最短路径。你可以在此基础上扩展:将角色存入数据库t_user.role字段,再用switch(user.getRole())替代硬编码,就完成了向真实业务逻辑的演进。

3. 数据库初始化与关键表结构解析:从 t_user 到 t_equipment 的字段设计逻辑

3.1 MySQL 数据库脚本必须手动执行,字段命名直白但暗含业务约束

系统提供的是.sql文件(通常命名为lab_system.sql),需在 MySQL 中手动执行。核心表结构如下:

表名主要字段说明
t_userid,username,password,realname,role,statusrole存储 'admin'/'js'/'xs';status为 0/1 表示禁用/启用
t_equipmentid,name,type,status,purchase_date,location,remarkstatus为 '可用'/'维修中'/'已报废';purchase_date类型为DATETIME
t_noticeid,title,content,publish_time,publisherpublish_timeNOW()默认值,确保发布时间准确

执行建表语句前,必须确认字符集:

CREATE DATABASE lab_system CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE lab_system;

注意:若跳过utf8mb4设置,插入带 emoji 的公告内容(如【⚠️重要通知】)会导致Incorrect string value错误。utf8mb4是 MySQL 5.5.3+ 支持完整 UTF-8 的唯一选项。

3.2 t_user 表的 role 字段是权限路由的唯一依据,必须与 Action 层硬编码严格一致

t_user表中role字段值必须与UserAction.class中的字符串完全匹配。例如,插入管理员用户:

INSERT INTO t_user (username, password, realname, role, status) VALUES ('admin', '123456', '系统管理员', 'admin', 1);

若误写为'administrator',则登录后session.getAttribute("role")返回'administrator',但UserAction中无对应分支,最终跳转到login.jsp并提示失败。这是新手调试时最常见的“明明账号密码对却登不进去”问题根源。

3.3 t_equipment 表的 status 字段驱动前端显示逻辑,JSP 中用 JSTL 判断

EquipmentAction.class查询设备列表后,将List<Equipment>存入 request,JSP 页面用 JSTL 渲染状态标签:

<c:forEach items="${equipmentList}" var="eq"> <tr> <td>${eq.name}</td> <td> <c:choose> <c:when test="${eq.status == '可用'}"> <span style="color:green">✅ 可用</span> </c:when> <c:when test="${eq.status == '维修中'}"> <span style="color:orange">🔧 维修中</span> </c:when> <c:otherwise> <span style="color:red">❌ 已报废</span> </c:otherwise> </c:choose> </td> </tr> </c:forEach>

这种写法将业务状态与 UI 展示强绑定,虽不如 Vue/React 的响应式灵活,但胜在逻辑透明——修改t_equipment.status值,页面立即变色,无需查 JS 逻辑。

3.4 JDBC 连接配置藏在 JDBCUtil.class,URL 参数决定是否自动重连

JDBCUtil.class是获取数据库连接的工具类,其getConnection()方法核心代码如下:

public static Connection getConnection() { try { Class.forName("com.mysql.jdbc.Driver"); // MySQL 5.x 驱动 String url = "jdbc:mysql://localhost:3306/lab_system?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&autoReconnect=true"; return DriverManager.getConnection(url, "root", "123456"); } catch (Exception e) { e.printStackTrace(); return null; } }

关键 URL 参数说明:

  • useUnicode=true&characterEncoding=utf8:强制使用 UTF-8 编码,解决中文乱码;
  • serverTimezone=GMT%2B8:适配东八区时间,避免java.sql.SQLException: The server timezone value 'CST' is unrecognized
  • autoReconnect=true:当 MySQL 连接超时断开时,自动重连(开发阶段可开启,生产环境需配合连接池)。

若 MySQL 版本为 8.x,需将驱动类改为com.mysql.cj.jdbc.Driver,并添加allowPublicKeyRetrieval=true&useSSL=false参数,否则连接失败。

4. 部署到 Tomcat 的实操步骤与三类典型报错排查指南

4.1 部署前必须完成的四项检查清单

检查项操作命令/位置不通过后果
JDK 版本java -version,需 1.7+(推荐 1.8)UnsupportedClassVersionError报错
Tomcat 版本CATALINA_HOME/conf/server.xml,确认<Connector port="8989"访问http://127.0.0.1:8989显示 404
MySQL 服务net start mysql(Windows)或sudo systemctl status mysql(Linux)Communications link failure连接异常
项目目录结构webapps/lab_system/WEB-INF/web.xml必须存在Tomcat 启动时报No web application found

4.2 启动 Tomcat 后访问http://127.0.0.1:8989/login_indexs.do的完整请求链路

  1. 浏览器发送 GET 请求 → Tomcat 接收/login_indexs.do
  2. web.xml匹配<url-pattern>*.do</url-pattern>→ 转发给ActionServlet
  3. ActionServlet解析 URL 中的login_indexs→ 反射加载LoginAction类(若不存在,则 fallback 到UserActionexecute()方法);
  4. UserAction.login()执行 → 调用UserDaoImpl.findUserByUsernameAndPassword()
  5. BaseDaoImpl.executeQuery()执行 SQL →JDBCUtil.getConnection()获取连接;
  6. 查询成功 →request.setAttribute("msg", "")RequestDispatcher.forward("/login.jsp")
  7. login.jsp渲染 → 加载jquery.min.jsbootstrap.css(静态资源需放在webapps/lab_system/js/下)。

4.3 三类高频报错的精准定位与修复方案

报错一:HTTP Status 404 – /lab_system/login_indexs.do
  • 原因:项目未正确部署到webapps目录,或web.xmlservlet-mappingurl-pattern与请求路径不匹配。
  • 验证:访问http://127.0.0.1:8989/,若显示 Tomcat 默认页,说明容器正常;再检查webapps/lab_system/下是否存在WEB-INF/web.xml
  • 修复:将项目根目录(含WEB-INF文件夹)整体复制到webapps/下,并重命名为lab_system(不能是lab_system.zip)。
报错二:java.lang.ClassNotFoundException: com.mysql.jdbc.Driver
  • 原因:MySQL 驱动 JAR 未放入WEB-INF/lib/目录。
  • 验证:进入webapps/lab_system/WEB-INF/lib/,确认存在mysql-connector-java-5.1.47.jar(或对应版本)。
  • 修复:下载 MySQL Connector/J 5.1 ,解压后取mysql-connector-java-5.1.47-bin.jar,复制到lib/目录,重启 Tomcat。
报错三:org.apache.jasper.JasperException: /login.jsp (line: 12, column: 1) Unable to compile class for JSP
  • 原因:JSP 中引用了未声明的 Java 类(如DateUtils),或web.xml中缺少jsp-config配置。
  • 验证:打开login.jsp,检查第 12 行是否有<%@ page import="com.util.DateUtils" %>,确认com/util/DateUtils.class是否在WEB-INF/classes/下。
  • 修复:将DateUtils.class所在的包路径(如com.util.*)完整复制到WEB-INF/classes/com/util/,确保类路径与 import 语句一致。

4.4 修改默认密码的两种安全方式:数据库直改与代码层加固

系统默认密码123456存在严重安全隐患,必须修改:

方式一(快速):直接更新数据库

UPDATE t_user SET password = 'e10adc3949ba59abbe56e057f20f883e' WHERE username = 'admin';

其中'e10adc3949ba59abbe56e057f20f883e'123456的 MD5 值(系统未加盐,仅作基础哈希)。此方式无需重启服务,但密码强度仍不足。

方式二(推荐):在 UserAction.login() 中加入密码加密校验

// 在 login() 方法内,替换原明文比对逻辑 String inputPassword = request.getParameter("password"); String encryptedInput = DigestUtils.md5Hex(inputPassword); // 使用 commons-codec User user = userDao.findUserByUsernameAndPassword(username, encryptedInput);

同时需引入commons-codec-1.15.jarlib/目录。此举将密码校验升级为不可逆哈希,即使数据库泄露,攻击者也无法直接还原原始密码。

5. 从实验室管理到通用后台系统的迁移技巧:抽离 BaseDaoImpl 与重构 Action 层

5.1 将 BaseDaoImpl 升级为支持连接池的通用 DAO 工厂

BaseDaoImpl每次操作都新建连接,高并发下会耗尽 MySQL 连接数。可引入HikariCP连接池替代JDBCUtil

public class HikariDataSourceFactory { private static HikariDataSource dataSource; static { HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:mysql://localhost:3306/lab_system?serverTimezone=GMT%2B8"); config.setUsername("root"); config.setPassword("123456"); config.setMaximumPoolSize(20); config.setMinimumIdle(5); dataSource = new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }

修改BaseDaoImpl.executeUpdate()中的conn = JDBCUtil.getConnection()conn = HikariDataSourceFactory.getConnection()。此举将连接创建开销降至毫秒级,且连接复用率提升 90% 以上。

5.2 用策略模式重构 UserAction,消除硬编码角色分支

UserAction.login()中的 if-else 替换为策略接口:

public interface RoleHandler { String handle(User user, HttpServletRequest request, HttpServletResponse response); } @Component("adminHandler") public class AdminHandler implements RoleHandler { @Override public String handle(User user, HttpServletRequest request, HttpServletResponse response) { request.getSession().setAttribute("role", "admin"); return "redirect:/admin/index.jsp"; } } // 在 UserAction 中注入所有 Handler @Autowired private Map<String, RoleHandler> roleHandlers; public String login() { // ... 用户查询逻辑 if (user != null) { String role = user.getRole(); // 从数据库读取 return roleHandlers.get(role).handle(user, request, response); } }

此时t_user.role字段可自由扩展(如新增'assistant'助教角色),只需添加AssistantHandler类并注册到 Spring 容器,无需修改login()方法。这是从教学代码迈向企业级架构的关键跃迁。

5.3 用 Log4j2 替代 System.out.println,实现日志分级与文件输出

系统中大量e.printStackTrace()会淹没 Tomcat 控制台。引入log4j2.xml配置:

<Configuration status="WARN"> <Appenders> <File name="FileAppender" fileName="logs/lab_system.log"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </File> </Appenders> <Loggers> <Root level="info"> <AppenderRef ref="FileAppender"/> </Root> </Loggers> </Configuration>

UserAction中替换日志:

private static final Logger logger = LogManager.getLogger(UserAction.class); public String login() { try { // ... 业务逻辑 logger.info("用户 {} 登录成功,角色 {}", username, user.getRole()); } catch (Exception e) { logger.error("用户 {} 登录失败", username, e); // 自动记录堆栈 } }

日志文件将生成在logs/lab_system.log,按时间滚动,便于线上问题追踪。这比System.out多出 3 个核心价值:可配置级别、可分离输出、可追溯上下文。

提示:log4j2.xml需放在src/main/resources/目录(Maven 项目)或WEB-INF/classes/(传统项目),确保类路径可加载。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 15:25:40

基于SpringBoot与Vue的智能校园综合服务平台设计与实现源码+文档

温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台…

作者头像 李华
网站建设 2026/9/10 15:19:51

我想投资开一家酒店,有哪些成熟的加盟品牌推荐?

我想投资开一家酒店&#xff0c;有哪些成熟的加盟品牌推荐&#xff1f;成熟不成熟&#xff0c;可以落到三件能查的事上&#xff1a;品牌做了多少年、门店开到多大规模、有没有第三方榜单背书。希尔顿欢朋在这三件事上都有公开数据可以核对。品牌做了多少年1984年&#xff0c;全…

作者头像 李华