ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

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

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人下载学习开发者可直接部署于本地Tomcathttp://127.0.0.1:8989通过预置账号快速验证全流程并基于BaseDaoImpl、Action层分层结构深入理解MVC开发模式与JDBC数据库交互实践。1. 这不是又一个学生课设——它是一套可直接跑通、带角色权限链路、含完整 JDBC 封装的 Java Web 实验室管理骨架你打开这个.zip包看到BaseDaoImpl.class和一串*Action.class第一反应可能是“又一个没注释的 JavaEE 课设”。但真正解压后你会发现它没有用 Spring Boot 自动装配却用BaseDaoImplUserDaoImpl构建出清晰的 DAO 层继承链它没上 MyBatis但DateUtils.class里封装了yyyy-MM-dd HH:mm:ss格式化与java.sql.Timestamp的双向转换它甚至在login_indexs.do路由里硬编码了三角色登录逻辑admin/js/xs而密码明文写死在UserAction.class的login()方法里——这不是缺陷是教学级系统刻意暴露的权限控制起点。这套系统不追求高并发或微服务但它把 Java Web 开发中最易被跳过的环节全摊开从web.xml中ActionServlet的手动配置到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-nameActionServlet/servlet-name servlet-classcom.action.ActionServlet/servlet-class /servlet servlet-mapping servlet-nameActionServlet/servlet-name url-pattern*.do/url-pattern /servlet-mapping提示.do后缀不是约定俗成而是ActionServlet类主动截获所有匹配该模式的请求。比如访问login_indexs.do容器会将请求交给ActionServlet的doGet()方法处理再由它根据 URL 中的login_indexs字符串反射调用LoginAction类虽然源码中未提供该类但UserAction.class承担了实际登录逻辑。更关键的是过滤器链配置filter filter-nameEncodingFilter/filter-name filter-classcom.filter.EncodingFilter/filter-class /filter filter-mapping filter-nameEncodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping这个EncodingFilter类负责统一设置request.setCharacterEncoding(UTF-8)和response.setContentType(text/html;charsetUTF-8)。若跳过此步在表单提交中文用户名时request.getParameter(username)会返回乱码。这是 Java Web 开发中第一个必须亲手验证的环节——删掉该 filter输入“张三”后台打印出来就是å¼ ä¸‰。2.2 BaseDaoImpl 是 JDBC 操作的中枢它用泛型反射解决了 CRUD 的重复代码问题系统未使用 Hibernate 或 MyBatis所有数据库操作都基于 JDBC 封装。BaseDaoImpl.class是核心抽象类其关键方法executeUpdate()和executeQuery()采用模板方法模式public abstract class BaseDaoImplT { 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继承BaseDaoImplUser并实现具体业务 SQLpublic class UserDaoImpl extends BaseDaoImplUser implements UserDao { Override public User findUserByUsernameAndPassword(String username, String password) { String sql SELECT * FROM t_user WHERE username ? AND password ?; // 注意此处密码未加密明文比对仅用于教学演示 ListUser list executeQuery(sql, username, password); return list.isEmpty() ? null : list.get(0); } }注意executeQuery()方法在BaseDaoImpl中返回ListT它通过ResultSetMetaData获取列名再用反射调用T类的 setter 方法完成对象封装。这意味着User.class必须有与数据库字段一一对应的 setter如setUsername(String)且字段名需严格匹配usernamevsuser_name会导致空值。2.3 DateUtils.class 解决时间类型转换的硬伤避免 Timestamp 与 Date 的隐式转换异常Java 中java.util.Date与java.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.class的login()方法中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/xsstatus为 0/1 表示禁用/启用t_equipmentid,name,type,status,purchase_date,location,remarkstatus为 可用/维修中/已报废purchase_date类型为DATETIMEt_noticeid,title,content,publish_time,publisherpublish_time用NOW()默认值确保发布时间准确执行建表语句前必须确认字符集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查询设备列表后将ListEquipment存入 requestJSP 页面用 JSTL 渲染状态标签c:forEach items${equipmentList} vareq tr td${eq.name}/td td c:choose c:when test${eq.status 可用} span stylecolor:green✅ 可用/span /c:when c:when test${eq.status 维修中} span stylecolor:orange 维修中/span /c:when c:otherwise span stylecolor:red❌ 已报废/span /c:otherwise /c:choose /td /tr /c:forEach这种写法将业务状态与 UI 展示强绑定虽不如 Vue/React 的响应式灵活但胜在逻辑透明——修改t_equipment.status值页面立即变色无需查 JS 逻辑。3.4 JDBC 连接配置藏在 JDBCUtil.classURL 参数决定是否自动重连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?useUnicodetruecharacterEncodingutf8serverTimezoneGMT%2B8autoReconnecttrue; return DriverManager.getConnection(url, root, 123456); } catch (Exception e) { e.printStackTrace(); return null; } }关键 URL 参数说明useUnicodetruecharacterEncodingutf8强制使用 UTF-8 编码解决中文乱码serverTimezoneGMT%2B8适配东八区时间避免java.sql.SQLException: The server timezone value CST is unrecognizedautoReconnecttrue当 MySQL 连接超时断开时自动重连开发阶段可开启生产环境需配合连接池。若 MySQL 版本为 8.x需将驱动类改为com.mysql.cj.jdbc.Driver并添加allowPublicKeyRetrievaltrueuseSSLfalse参数否则连接失败。4. 部署到 Tomcat 的实操步骤与三类典型报错排查指南4.1 部署前必须完成的四项检查清单检查项操作命令/位置不通过后果JDK 版本java -version需 1.7推荐 1.8UnsupportedClassVersionError报错Tomcat 版本CATALINA_HOME/conf/server.xml确认Connector port8989访问http://127.0.0.1:8989显示 404MySQL 服务net start mysqlWindows或sudo systemctl status mysqlLinuxCommunications link failure连接异常项目目录结构webapps/lab_system/WEB-INF/web.xml必须存在Tomcat 启动时报No web application found4.2 启动 Tomcat 后访问http://127.0.0.1:8989/login_indexs.do的完整请求链路浏览器发送 GET 请求 → Tomcat 接收/login_indexs.doweb.xml匹配url-pattern*.do/url-pattern→ 转发给ActionServletActionServlet解析 URL 中的login_indexs→ 反射加载LoginAction类若不存在则 fallback 到UserAction的execute()方法UserAction.login()执行 → 调用UserDaoImpl.findUserByUsernameAndPassword()BaseDaoImpl.executeQuery()执行 SQL →JDBCUtil.getConnection()获取连接查询成功 →request.setAttribute(msg, )→RequestDispatcher.forward(/login.jsp)login.jsp渲染 → 加载jquery.min.js和bootstrap.css静态资源需放在webapps/lab_system/js/下。4.3 三类高频报错的精准定位与修复方案报错一HTTP Status 404 – /lab_system/login_indexs.do原因项目未正确部署到webapps目录或web.xml中servlet-mapping的url-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 importcom.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.jar到lib/目录。此举将密码校验升级为不可逆哈希即使数据库泄露攻击者也无法直接还原原始密码。5. 从实验室管理到通用后台系统的迁移技巧抽离 BaseDaoImpl 与重构 Action 层5.1 将 BaseDaoImpl 升级为支持连接池的通用 DAO 工厂原BaseDaoImpl每次操作都新建连接高并发下会耗尽 MySQL 连接数。可引入HikariCP连接池替代JDBCUtilpublic class HikariDataSourceFactory { private static HikariDataSource dataSource; static { HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/lab_system?serverTimezoneGMT%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 MapString, 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 statusWARN Appenders File nameFileAppender fileNamelogs/lab_system.log PatternLayout pattern%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n/ /File /Appenders Loggers Root levelinfo AppenderRef refFileAppender/ /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/传统项目确保类路径可加载。本文还有配套的精品资源点击获取
返回列表