资讯详情

Servlet+JSP+MySQL宿舍管理系统从零搭建实战

📅 2026/10/8 14:34:27 | 华诺云谱 👁 阅读
Servlet+JSP+MySQL宿舍管理系统从零搭建实战
简介这是一份面向Java Web初学者的宿舍管理实战项目资源聚焦ServletJSPMySQL技术栈的原生开发实践帮助学习者掌握Web应用基础架构与前后端协同逻辑。资源包含63个文件涵盖18个Java源码实现Servlet控制器与业务逻辑、18个Class编译文件、5个JSP页面登录、列表、增删改表单等核心视图、1个SQL建库脚本nafeng.sql、1个配置文档详细配置.docx及1个实操演示视频mp4总大小10.01MB。已有1160人学习下载资源结构清晰配套功能描述.txt与图片素材便于理解模块划分与界面设计视频直观展示系统运行流程SQL脚本与配置文档降低环境搭建门槛特别适合零基础学员通过完整可运行项目深入理解HTTP请求处理、JDBC数据库交互、Session会话管理及MVC分层思想。1. 为什么一个“简单宿舍管理系统”值得用 ServletJSPMySQL 原生组合从零搭起这不是一个过时的怀旧项目而是一条被反复验证的 Java Web 入门硬通路当你需要在两周内交付一个可运行、可演示、可调试、能对接真实数据库的管理类系统比如宿舍分配、学生入住、房间状态、管理员登录又不想被 Spring Boot 的自动配置黑匣子绕晕、不打算引入 MyBatis/MyBatis-Plus 的 XML 或注解抽象层、更不希望前端框架Vue/React抢走你对 HTTP 请求生命周期的掌控权——Servlet JSP MySQL 就是那个最透明、最可控、最利于 debug 的技术栈锚点。它不追求高并发或微服务架构但能让你亲手把HttpServletRequest里取参数、HttpServletResponse写响应、HttpSession存用户态、JDBC连 MySQL、JSP页面嵌入 Java 表达式、EL和JSTL控制循环与判断、web.xml配置映射路径……这些链条全部串起来。尤其对校招面试官来说“手写一个带登录列表增删改的宿舍系统”比“调通 Spring Boot Starter”更能暴露你对 Web 容器本质的理解深度。本文就带你用原生方式不依赖任何脚手架从 JDK 环境开始一砖一瓦垒出一个真正能跑在 Tomcat 上、数据落进本地 MySQL、界面用纯 JSP 渲染的宿舍管理系统——所有代码可复制、所有配置可验证、所有报错有归因。2. 环境筑基JDK 8 Tomcat 9 MySQL 5.7 的最小可行组合2.1 JDK 8 是原生 Servlet/JSP 生态的“黄金锚点”Servlet 3.1 规范JSP 2.3与 JDK 8 完全兼容且 Tomcat 9 默认支持该组合。高于 JDK 11 会触发javax.*包被移除的警告如javax.servlet.*低于 JDK 7 则无法使用 Lambda 表达式简化 DAO 层JDK 8 是当前教育场景和老系统维护中最稳定的交集版本。安装后务必验证java -version # 输出应为java version 1.8.0_3XX javac -version提示不要用JAVA_HOME指向 JRE 目录必须指向 JDK 根目录含bin/javac.exe。Windows 下若提示javac 不是内部或外部命令请检查PATH是否包含%JAVA_HOME%\bin。2.2 Tomcat 9.0.x轻量、无侵入、直连 Servlet APITomcat 9 对应 Servlet 4.0但向下完全兼容 Servlet 3.1本项目所用。它不打包任何 ORM 或 MVC 框架纯粹作为 Web 容器存在是你理解web.xml生命周期、ServletContextListener初始化、Filter拦截逻辑的天然沙盒。下载地址为官方 archive非最新版https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.85/bin/apache-tomcat-9.0.85.zip2023 年末稳定版避免 10.x 引入 Jakarta EE 命名空间变更带来的jakarta.servlet.*兼容问题。解压后进入bin目录双击startup.batWindows或执行./startup.shLinux/macOS访问http://localhost:8080出现 Tomcat 欢迎页即成功。关键配置在conf/server.xml中确认端口未被占用Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 /2.3 MySQL 5.7兼容性最强、JDBC 驱动最稳的关系型底座MySQL 5.7 是最后一个默认使用mysql_native_password认证插件的大版本与mysql-connector-java:5.1.47驱动零摩擦。8.0 默认caching_sha2_password会导致Access denied for user报错即使密码正确。安装时务必勾选 “Legacy Authentication Method”传统认证方式并记下 root 密码。启动服务后用命令行验证连接mysql -u root -p # 输入密码后进入 MySQL CLI创建专用数据库避免污染test或mysql系统库CREATE DATABASE dorm_management CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE dorm_management;注意utf8mb4是真正支持 emoji 和四字节 UTF-8 字符的编码COLLATE utf8mb4_unicode_ci保证中文排序与比较正确。若建库时漏掉后续INSERT中文会变???。3. 工程结构与核心文件从 web.xml 开始的请求生命线3.1 标准 Web 应用目录结构非 Maven纯原生我们不使用 IDE 自动生成的 Maven 结构而是手动构建符合 Servlet 规范的物理目录这是理解部署本质的关键dorm-system/ ├── WEB-INF/ │ ├── web.xml ← 全局配置中枢Servlet 映射、Filter、Listener │ ├── lib/ │ │ └── mysql-connector-java-5.1.47.jar ← JDBC 驱动必须放这里 │ └── classes/ │ ├── dao/ ← 数据访问对象StudentDao.java 等 │ ├── service/ ← 业务逻辑StudentService.java │ ├── servlet/ ← 控制器LoginServlet.java, RoomListServlet.java │ └── model/ ← 实体类Student.java, Room.java ├── index.jsp ← 首页入口欢迎页 or 登录页 ├── login.jsp ← 登录表单 ├── room-list.jsp ← 宿舍列表页 ├── student-add.jsp ← 新增学生页 └── css/ js/ images/ ← 静态资源不走 Servlet直接由 Tomcat 服务提示WEB-INF是安全边界其下所有内容包括web.xml、classes、lib不能被浏览器直接访问只能通过 Servlet 转发或重定向到达。这是原生 Web 安全的第一道防线。3.2 web.xml定义请求如何找到对应的 Servlet这是整个应用的路由总控。必须放在WEB-INF/web.xml且根元素为web-app声明版本为3.1对应 Servlet 3.1?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd version3.1 !-- 全局初始化参数 -- context-param param-namedbUrl/param-name param-valuejdbc:mysql://localhost:3306/dorm_management?useSSLfalseamp;serverTimezoneAsia/Shanghaiamp;characterEncodingutf8mb4/param-value /context-param context-param param-namedbUser/param-name param-valueroot/param-value /context-param context-param param-namedbPassword/param-name param-valueyour_root_password/param-value /context-param !-- 登录 Servlet 映射 -- servlet servlet-nameLoginServlet/servlet-name servlet-classservlet.LoginServlet/servlet-class /servlet servlet-mapping servlet-nameLoginServlet/servlet-name url-pattern/login/url-pattern /servlet-mapping !-- 宿舍列表 Servlet 映射 -- servlet servlet-nameRoomListServlet/servlet-name servlet-classservlet.RoomListServlet/servlet-class /servlet servlet-mapping servlet-nameRoomListServlet/servlet-name url-pattern/room/list/url-pattern /servlet-mapping !-- 默认欢迎文件 -- welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list /web-app关键点说明amp;是 XML 中的转义写法不可写成否则解析失败serverTimezoneAsia/Shanghai解决 MySQL 时区与 Java 时区不一致导致的datetime字段读写错乱url-pattern/login/url-pattern表示所有以/login结尾的请求如POST /login,GET /login?nextroom都会交给LoginServlet处理context-param在ServletContext中全局可用Servlet 中通过getServletContext().getInitParameter(dbUrl)获取避免硬编码。3.3 LoginServlet一次请求的完整闭环示范这是第一个真正处理业务的 Servlet。它接收表单 POST、校验账号密码、查询数据库、设置 Session、转发到主页或返回错误。// src/servlet/LoginServlet.java package servlet; import model.User; import service.UserService; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; WebServlet(/login) public class LoginServlet extends HttpServlet { private UserService userService new UserService(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 获取表单参数注意GET 请求用 getParameterPOST 同样适用 String username req.getParameter(username); String password req.getParameter(password); // 2. 校验非空前端 JS 校验是锦上添花后端校验是底线 if (username null || username.trim().isEmpty() || password null || password.trim().isEmpty()) { req.setAttribute(errorMsg, 用户名和密码不能为空); req.getRequestDispatcher(/login.jsp).forward(req, resp); return; } // 3. 调用 Service 层查询用户实际查数据库 User user userService.login(username, password); // 4. 业务判断登录成功 or 失败 if (user ! null) { // 成功存入 Session跳转到宿舍列表页 HttpSession session req.getSession(); session.setAttribute(currentUser, user); // 关键绑定用户态 resp.sendRedirect(req.getContextPath() /room/list); // 重定向避免刷新重复提交 } else { // 失败回传错误信息留在登录页 req.setAttribute(errorMsg, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } } // GET 请求也支持例如直接访问 /login 显示登录页 Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.getRequestDispatcher(/login.jsp).forward(req, resp); } }逻辑拆解WebServlet(/login)是注解式映射与web.xml中的servlet-mapping功能等价二者选一即可推荐注解更直观req.getSession()创建或获取当前会话session.setAttribute(currentUser, user)是实现“登录态”的核心后续所有页面可通过session.getAttribute(currentUser)获取resp.sendRedirect(...)是重定向302浏览器 URL 变为/room/list而req.getRequestDispatcher(...).forward(...)是请求转发服务器内部跳转URL 不变且 request scope 中的属性如errorMsg可传递req.getContextPath()返回应用上下文路径如/dorm-system确保重定向路径可移植不写死/room/list。4. 数据层落地JDBC 连接池 DAO 模式 MySQL 表结构设计4.1 手写简易连接池不依赖 C3P0/Druid理解本质为避免每次请求都新建/关闭连接性能灾难我们实现一个极简的ConnectionPool类。它不是生产级方案但能让你看清连接复用原理// src/dao/ConnectionPool.java package dao; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; public class ConnectionPool { private static DataSource dataSource; static { try { // 从 JNDI 查找数据源需在 Tomcat conf/context.xml 中配置见下文 Context initContext new InitialContext(); Context envContext (Context) initContext.lookup(java:/comp/env); dataSource (DataSource) envContext.lookup(jdbc/DormDB); } catch (NamingException e) { // 若 JNDI 失败降级为直连开发阶段够用 System.err.println(JNDI lookup failed, using direct connection...); dataSource new DirectDataSource(); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } // 直连实现仅用于快速验证勿用于生产 private static class DirectDataSource implements DataSource { private static final String URL jdbc:mysql://localhost:3306/dorm_management?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4; private static final String USER root; private static final String PASSWORD your_root_password; Override public Connection getConnection() throws SQLException { try { Class.forName(com.mysql.jdbc.Driver); // MySQL 5.x 驱动类名 } catch (ClassNotFoundException e) { throw new SQLException(MySQL Driver not found, e); } return java.sql.DriverManager.getConnection(URL, USER, PASSWORD); } // 其余方法返回 UnsupportedOperationException连接池功能暂不实现 Override public Connection getConnection(String username, String password) throws SQLException { return getConnection(); } Override public PrintWriter getLogWriter() throws SQLException { return null; } Override public void setLogWriter(PrintWriter out) throws SQLException {} Override public void setLoginTimeout(int seconds) throws SQLException {} Override public int getLoginTimeout() throws SQLException { return 0; } Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; } Override public T T unwrap(ClassT iface) throws SQLException { return null; } Override public boolean isWrapperFor(Class? iface) throws SQLException { return false; } } }注意DirectDataSource是临时方案。真正的连接池应在Tomcat/conf/context.xml中配置 JNDI 数据源见 4.2ConnectionPool优先使用 JNDI失败才降级。这体现了“约定优于配置”的思想。4.2 Tomcat JNDI 数据源配置生产推荐在Tomcat/conf/context.xml的Context标签内添加Resource namejdbc/DormDB authContainer typejavax.sql.DataSource factoryorg.apache.tomcat.jdbc.pool.DataSourceFactory driverClassNamecom.mysql.jdbc.Driver urljdbc:mysql://localhost:3306/dorm_management?useSSLfalseamp;serverTimezoneAsia/Shanghaiamp;characterEncodingutf8mb4 usernameroot passwordyour_root_password maxActive20 minIdle5 maxWait10000 validationQuerySELECT 1 testOnBorrowtrue/然后在web.xml中声明资源引用让应用知道要查找这个 JNDI 名resource-ref descriptionDorm Management Database/description res-ref-namejdbc/DormDB/res-ref-name res-typejavax.sql.DataSource/res-type res-authContainer/res-auth /resource-ref这样ConnectionPool中的 JNDI 查找就能命中获得 Tomcat 管理的连接池实例。4.3 MySQL 表结构宿舍管理的核心实体与关系执行以下 SQL 创建三张核心表student,room,dorm_assignment覆盖典型业务-- 学生表 CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, student_id VARCHAR(12) NOT NULL UNIQUE COMMENT 学号, name VARCHAR(20) NOT NULL COMMENT 姓名, gender ENUM(男,女) NOT NULL COMMENT 性别, phone VARCHAR(15) COMMENT 联系电话, created_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 宿舍表 CREATE TABLE room ( id INT PRIMARY KEY AUTO_INCREMENT, room_number VARCHAR(10) NOT NULL UNIQUE COMMENT 房间号如 301, floor INT NOT NULL COMMENT 楼层, capacity INT NOT NULL DEFAULT 4 COMMENT 床位数, status ENUM(空闲,已满,维修中) DEFAULT 空闲 COMMENT 状态, remark TEXT COMMENT 备注 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 分配关系表多对多一个学生住一个房间一个房间住多个学生 CREATE TABLE dorm_assignment ( id INT PRIMARY KEY AUTO_INCREMENT, student_id VARCHAR(12) NOT NULL, room_id INT NOT NULL, assign_date DATE NOT NULL DEFAULT (CURRENT_DATE) COMMENT 入住日期, FOREIGN KEY (student_id) REFERENCES student(student_id) ON DELETE CASCADE, FOREIGN KEY (room_id) REFERENCES room(id) ON DELETE CASCADE, UNIQUE KEY uk_student_room (student_id, room_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;设计要点student_id作为student表主键同时是dorm_assignment的外键避免使用INT自增 ID 作为业务标识学号才是唯一业务键dorm_assignment表用联合唯一索引uk_student_room防止同一学生重复分配到同一房间ON DELETE CASCADE保证删除学生或房间时关联分配记录自动清理status ENUM比VARCHAR更节省空间且约束值域空闲,已满,维修中覆盖主要状态。4.4 StudentDao封装 JDBC 操作的原子单元DAO 层只做数据存取不涉业务逻辑。以StudentDao.findByName()为例// src/dao/StudentDao.java package dao; import model.Student; import java.sql.*; import java.util.ArrayList; import java.util.List; public class StudentDao { // 查询所有学生带房间号 public ListStudent findAllWithRoom() throws SQLException { String sql SELECT s.id, s.student_id, s.name, s.gender, s.phone, r.room_number, r.floor, r.capacity, r.status FROM student s LEFT JOIN dorm_assignment da ON s.student_id da.student_id LEFT JOIN room r ON da.room_id r.id ORDER BY s.id DESC ; ListStudent list new ArrayList(); try (Connection conn ConnectionPool.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Student stu new Student(); stu.setId(rs.getInt(id)); stu.setStudentId(rs.getString(student_id)); stu.setName(rs.getString(name)); stu.setGender(rs.getString(gender)); stu.setPhone(rs.getString(phone)); // 关联房间信息可能为 null if (rs.getString(room_number) ! null) { stu.setRoomNumber(rs.getString(room_number)); stu.setFloor(rs.getInt(floor)); stu.setCapacity(rs.getInt(capacity)); stu.setRoomStatus(rs.getString(status)); } list.add(stu); } } return list; } // 根据学号精确查询 public Student findByStudentId(String studentId) throws SQLException { String sql SELECT * FROM student WHERE student_id ?; try (Connection conn ConnectionPool.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, studentId); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { Student stu new Student(); stu.setId(rs.getInt(id)); stu.setStudentId(rs.getString(student_id)); stu.setName(rs.getString(name)); stu.setGender(rs.getString(gender)); stu.setPhone(rs.getString(phone)); return stu; } } } return null; } // 新增学生事务控制 public boolean insert(Student student) throws SQLException { String sql INSERT INTO student (student_id, name, gender, phone) VALUES (?, ?, ?, ?); try (Connection conn ConnectionPool.getConnection(); PreparedStatement ps conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, student.getStudentId()); ps.setString(2, student.getName()); ps.setString(3, student.getGender()); ps.setString(4, student.getPhone()); int rows ps.executeUpdate(); if (rows 0) { try (ResultSet rs ps.getGeneratedKeys()) { if (rs.next()) { student.setId(rs.getInt(1)); // 获取自增主键 } } } return rows 0; } } }关键细节使用PreparedStatement防止 SQL 注入?占位符 setXxx()是标准做法ps.executeUpdate()返回影响行数ps.getGeneratedKeys()获取自增主键如id赋值给student.setId()便于后续操作LEFT JOIN在findAllWithRoom()中确保即使学生未分配房间也能查出基本信息try-with-resources自动关闭Connection/PreparedStatement/ResultSet避免连接泄漏这是血泪经验漏关连接池会迅速耗尽。5. JSP 原生界面EL 表达式 JSTL 标签 CSS 布局实战5.1 login.jsp表单提交与错误反馈的最小闭环% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % !DOCTYPE html html head meta charsetUTF-8 title宿舍管理系统 - 登录/title style body { font-family: Microsoft YaHei, sans-serif; background: #f5f5f5; } .login-box { width: 400px; margin: 100px auto; padding: 30px; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } .form-group { margin-bottom: 15px; } label { display: block; margin-bottom: 5px; font-weight: bold; } input[typetext], input[typepassword] { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; } button { width: 100%; padding: 12px; background: #007bff; color: white; border: none; border-radius: 4px; font-size: 16px; cursor: pointer; } .error { color: #dc3545; font-size: 14px; margin-top: 5px; } /style /head body div classlogin-box h2 styletext-align: center; margin-bottom: 30px;宿舍管理系统/h2 form action${pageContext.request.contextPath}/login methodpost div classform-group label forusername用户名/label input typetext idusername nameusername required /div div classform-group label forpassword密码/label input typepassword idpassword namepassword required /div button typesubmit登录/button /form c:if test${not empty errorMsg} div classerror${errorMsg}/div /c:if /div /body /htmlJSP 核心要素解析% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %引入 JSTL 核心标签库c:if是条件渲染的基础${pageContext.request.contextPath}动态获取应用上下文路径如/dorm-system确保action地址可移植${errorMsg}是 EL 表达式从 request scope 中取值由LoginServlet的req.setAttribute(errorMsg, ...)设置CSS 内联样式足够满足简单布局避免引入外部 CSS 文件增加复杂度原生开发原则能少则少required是 HTML5 原生表单校验作为第一道防线。5.2 room-list.jsp表格展示 分页雏形 操作按钮% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % % // 从 request scope 获取数据由 RoomListServlet 设置 request.setAttribute(studentList, request.getAttribute(studentList)); % !DOCTYPE html html head meta charsetUTF-8 title宿舍列表/title style table { width: 100%; border-collapse: collapse; margin-top: 20px; } th, td { padding: 12px; text-align: left; border: 1px solid #ddd; } th { background-color: #007bff; color: white; } tr:nth-child(even) { background-color: #f9f9f9; } .btn { display: inline-block; padding: 6px 12px; margin: 0 5px; text-decoration: none; border-radius: 4px; font-size: 14px; } .btn-primary { background-color: #007bff; color: white; } .btn-danger { background-color: #dc3545; color: white; } .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } /style /head body div classheader h2学生宿舍分配列表/h2 a href${pageContext.request.contextPath}/student-add.jsp classbtn btn-primary新增学生/a /div c:choose c:when test${empty studentList || studentList.size() 0} p暂无学生数据请先添加。/p /c:when c:otherwise table thead tr th序号/th th学号/th th姓名/th th性别/th th电话/th th所在房间/th th楼层/th th床位数/th th房间状态/th th操作/th /tr /thead tbody c:forEach items${studentList} varstu varStatusstatus tr td${status.count}/td td${stu.studentId}/td td${stu.name}/td td${stu.gender}/td td${stu.phone}/td td${stu.roomNumber ! null ? stu.roomNumber : 未分配}/td td${stu.roomNumber ! null ? stu.floor : -}/td td${stu.roomNumber ! null ? stu.capacity : -}/td td${stu.roomStatus ! null ? stu.roomStatus : -}/td td a href${pageContext.request.contextPath}/servlet/StudentDeleteServlet?id${stu.id} classbtn btn-danger onclickreturn confirm(确定删除学生 ${stu.name})删除/a /td /tr /c:forEach /tbody /table /c:otherwise /c:choose /body /htmlJSP 高级技巧c:choosec:whenc:otherwise实现三元逻辑比嵌套c:if更清晰c:forEach遍历studentListvarStatusstatus提供循环计数${status.count}从 1 开始${stu.roomNumber ! null ? stu.roomNumber : 未分配}是 EL 中的三元运算符处理关联字段为空的情况删除链接加onclickreturn confirm(...)是最简前端确认防止误点fmt标签库未使用但已引入为后续日期格式化预留如fmt:formatDate value${stu.createdTime} patternyyyy-MM-dd。5.3 student-add.jsp表单提交与后端校验联动% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % !DOCTYPE html html head meta charsetUTF-8 title新增学生/title style .form-container { max-width: 600px; margin: 40px auto; padding: 20px; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } .form-group { margin-bottom: 15px; } label { display: block; margin-bottom: 5px; font-weight: bold; } input[typetext], select { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; } button { padding: 10px 20px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer; } .error { color: #dc3545; font-size: 14px; margin-top: 5px; } /style /head body div classform-container h2新增学生信息/h2 form action${pageContext.request.contextPath}/servlet/StudentAddServlet methodpost div classform-group label forstudentId学号 */label input typetext idstudentId namestudentId required c:if test${not empty errorMsg errorMsg 学号已存在} div classerror学号已存在请更换/div /c:if /div div classform-group label forname姓名 */label input typetext idname namename required /div div classform-group label forgender性别 */label select idgender namegender required option value请选择/option option value男男/option option value女女/option /select /div div classform-group label forphone联系电话/label input typetext idphone namephone /div div classform-group label forroomId分配房间/label select idroomId nameroomId option value暂不分配/option c:forEach items${roomList} varroom option value${room.id}${room.roomNumber} (楼层:${room.floor}, 床位:${room.capacity})/option /c:forEach /select /div button typesubmit保存/button a href${pageContext.request.contextPath}/room-list.jsp stylemargin-left: 10px;取消/a /form /div /body /html关键交互设计select下拉框动态渲染roomList由StudentAddServlet查询所有房间后设置到 request scoperequired属性强制必填c:if根据特定错误码显示定制提示如学号重复房间选项格式化为301 (楼层:3, 床位:4)提升可读性“暂不分配” 选项允许新增学生时不立即分配符合业务弹性。6. 避坑指南那些让新手卡住 3 小时的 5 个真实问题6.1 现象JSP 页面中文显示为???数据库存入也是乱码原因编码链断裂——JSP 文件本身编码、HTTP 响应头、MySQL 连接参数、数据库/表编码四者不统一。解决确保.jsp文件用 UTF-8 无 BOM 编码保存IDEA/VS Code 右下角确认JSP 顶部声明% page contentTypetext/html;charsetUTF-8 %web.xml中添加过滤器强制请求编码在web.xmlweb-app内filter filter-nameCharacterEncodingFilter/filter-name本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑