ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于JSP+Servlet的宿舍管理系统开发实战:从数据库设计到部署避坑

基于JSP+Servlet的宿舍管理系统开发实战:从数据库设计到部署避坑 简介这是一个基于JSPServlet的JavaWeb宿舍管理系统完整项目适用于毕业设计、课程设计及具有一定Java基础的学习者。项目围绕宿舍管理场景实现了用户管理、宿舍分配、资源预订等常见功能通过JSP页面展示界面、Servlet控制器处理请求、JavaBean封装业务数据清晰呈现了MVC开发模式。同时涉及HTTP GET/POST方法、request/session等内置对象、JDBC数据库CRUD、上传下载、基础身份验证与异常处理等知识点对理解JavaWeb应用整体架构很有帮助。压缩包共281个文件大小约4.68MB以Java源码、JSP、HTML、CSS、JavaScript、class文件为主并包含SQL数据库脚本、web.xml部署描述和jar依赖库目录分为src、web、lib、db等结构清晰便于逐模块阅读。目前已有64人学习适合用于课程设计参考、毕业设计演示或JavaWeb实战练习按文档配置即可运行和二次开发。1. JavaWeb大作业的经典答案jspservlet写宿舍管理系统值在哪期末大作业拿到手题目是“宿舍管理系统”要求javaweb技术栈。新手最容易卡在选型上用Spring Boot怕老师不认用纯JSP怕代码乱成一锅粥。其实jspservlet正是这个场景最稳的答案——它足够底层能讲清楚HTTP请求怎么进Tomcat、servlet生命周期怎么走、jsp怎么渲染数据回给浏览器答辩时有话可说代码量又不至于失控。这套组合跑通一个登录加增删改查大概三天时间适合单人完成也适合做毕设的过渡版本。它解决的核心问题是用最朴素的MVC分层把数据库、业务逻辑、页面展示拆开让阅卷老师一眼看到你的工程意识。2. 先把系统拆成表和页面宿舍管理系统的模块边界与库表设计2.1 数据库四张核心表用户、宿舍、学生、报修怎么建宿舍管理系统的业务边界比想象的清晰。管理员管理宿舍楼和房间学生信息要挂在宿舍上报修记录关联学生和宿舍公告可以暂时不做表用页面写死也说得过去。最简化的方案是四张表t_user登录账号、t_dorm宿舍、t_student学生、t_repair报修。SQL脚本直接跑在MySQL 5.7上字符集统一用utf8mb4。建表语句里主键自增、外键逻辑上关联但不物理建外键——大作业阶段物理外键会让插入数据时频繁报错逻辑关联足够应付。CREATE DATABASE dorm DEFAULT CHARACTER SET utf8mb4; USE dorm; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(20) NOT NULL UNIQUE, password VARCHAR(32) NOT NULL, role VARCHAR(10) DEFAULT admin ) ENGINEInnoDB; CREATE TABLE t_dorm ( id INT PRIMARY KEY AUTO_INCREMENT, building VARCHAR(10) NOT NULL COMMENT 楼号如A栋, room_no VARCHAR(10) NOT NULL COMMENT 房间号如301, capacity INT DEFAULT 4, UNIQUE KEY uk_building_room (building, room_no) ) ENGINEInnoDB; CREATE TABLE t_student ( id INT PRIMARY KEY AUTO_INCREMENT, stu_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(20) NOT NULL, gender VARCHAR(4), dorm_id INT, phone VARCHAR(11) ) ENGINEInnoDB; CREATE TABLE t_repair ( id INT PRIMARY KEY AUTO_INCREMENT, stu_no VARCHAR(20) NOT NULL, dorm_id INT NOT NULL, content VARCHAR(200), status VARCHAR(10) DEFAULT 待处理, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB;参数说明写三点。第一t_user的UNIQUE约束是为了登录时用username直接查重复账号在初始化时就挡住。第二t_dorm的联合唯一键uk_building_room保证“A栋301”不会重复录入这是宿舍分配不打架的前提。第三t_repair里status用默认值“待处理”查询页面可以直接按状态过滤省一次更新逻辑。说明一下意图大作业阶段不需要存储过程、不需要视图、不需要触发器表够用、字段够清楚就行老师问起来每张表都能说清楚“为什么存在”就够了。2.2 传统jsp项目目录结构src、webapp、lib各放什么jspservlet的项目目录结构是答辩时第一个被问的。用IDEA的Maven骨架创建war项目目录如下没有Maven就手动建同样的文件夹。src/main/java放Java类src/main/webapp放jsp和静态资源src/main/webapp/WEB-INF/lib放jar包。WEB-INF是容器保护目录外面的浏览器永远不能直接访问它jsp页面放里面就得走servlet转发进来。dormitory/ ├── pom.xml ├── src/main/java │ ├── com/dorm/controller/ # servlet类 │ ├── com/dorm/dao/ # JDBC数据访问 │ ├── com/dorm/entity/ # 实体类 │ ├── com/dorm/util/ # DBUtil、字符编码过滤器 │ └── com/dorm/filter/ # 登录拦截过滤器 ├── src/main/resources/ # 放db.properties等配置 └── src/main/webapp ├── login.jsp ├── index.jsp ├── WEB-INF/ │ ├── web.xml │ └── lib/ # mysql-connector-java.jar等 └── static/ # css、js、图片每层的职责一句话讲清controller只收参数、调service或dao、转发或重定向dao只写SQL和结果集封装entity是普通JavaBean字段和表字段一一对应jsp只负责渲染不写业务代码。这里ell一下分层思路很多同学把SQL直接写在servlet里跑起来没问题但答辩被问“为什么这么写”时会很难看分层的意义是每个文件改动的原因变得可解释。2.3 运行环境与版本搭配JDK、Tomcat、MySQL的兼容性底线大作业翻车最多的不是代码是环境。版本搭配直接照抄JDK 1.8、Tomcat 8.5、MySQL 5.7。这三个组合最稳。JDK 17虽然能跑但Tomcat要升到10且新版Tomcat把javax.servlet改成了jakarta.servlet网上大部分教程的包名都对不上卡住一小时起步。MySQL 8.0的驱动和5.7不一样连接URL也要加serverTimezone参数。这里强调一下为什么用Tomcat而不提其他容器常见做法是Tomcat因为idea运行javaweb项目配置的默认选项就是它教程最多出错时搜得到答案。jsp文件是由Tomcat内部的JSP引擎解析成servlet再编译执行的所以“nginx支持jsp吗”的答案是不支持nginx只是一层反向代理最终还是要转发给Tomcat处理。3. 从登录到CRUDservlet生命周期、JDBC封装与jsp页面渲染3.1 servlet生命周期与一次请求的完整路径servlet生命周期是大作业答辩的必问题。三个阶段init()在servlet第一次被请求时执行只执行一次适合初始化连接池之类的资源service()每次请求都执行它会按HTTP方法分发到doGet或doPostdestroy()在应用卸载或Tomcat关闭时执行一次释放资源。这个生命周期决定了你写代码的方式不要在doGet里初始化重量级对象要复用init里创建的东西。一次登录请求的完整路径是浏览器提交表单到/login路径Tomcat根据web.xml里的servlet-mapping找到LoginServlet容器调用它的service方法识别出POST请求后进doPostdoPost里调DAO层查数据库比对用户名密码成功就req.getSession().setAttribute(user, user)然后重定向到首页失败就req.setAttribute(error, 用户名或密码错误)再转发回login.jspjsp里用EL表达式把error消息显示出来。转发和重定向的区别也要能讲forward是服务器内部跳转浏览器地址栏不变request域里的属性能接着用sendRedirect是浏览器重新发一次请求地址栏变化request域数据失效只能用session。3.2 DBUtil一个够用、不花哨的JDBC连接封装大作业不建议上MyBatis裸JDBC反而更能体现基本功。DBUtil的核心是加载驱动、获取连接、提供关闭方法。新建一个DBUtil.javapublic class DBUtil { private static String url jdbc:mysql://localhost:3306/dorm?useSSLfalsecharacterEncodingutf8; private static String user root; private static String password 123456; static { try { Class.forName(com.mysql.jdbc.Driver); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(MySQL驱动未找到检查lib目录); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, user, password); } public static void close(ResultSet rs, Statement st, Connection conn) { // 三连关先关ResultSet再关Statement最后关Connection if (rs ! null) try { rs.close(); } catch (SQLException ignored) {} if (st ! null) try { st.close(); } catch (SQLException ignored) {} if (conn ! null) try { conn.close(); } catch (SQLException ignored) {} } }逻辑说明static块保证驱动只加载一次这是servlet生命周期里“初始化一次”思想的应用。getConnection每次调用都新建连接大作业的并发量根本不需要连接池不用引入c3p0或Druid徒增复杂度。注意characterEncodingutf8这个参数它和页面编码、Tomcat编码三层配合少一个都可能出现中文乱码。密码字段直接写死在类里虽然不规范但大作业场景下可接受真要做得体面可以抽到src/main/resources/db.properties里用Properties读取。3.3 登录功能的完整链路JSP表单、LoginServlet、会话管理登录功能是宿舍管理系统的门面也是整个项目最先要跑通的部分。先写登录页面login.jsp表单提交到LoginServlet% page contentTypetext/html;charsetUTF-8 languagejava % html body form action${pageContext.request.contextPath}/login methodpost 用户名input typetext nameusernamebr 密 码input typepassword namepasswordbr span stylecolor:red${requestScope.error}/spanbr button typesubmit登 录/button /form /body /html${pageContext.request.contextPath}是一个必备细节。它动态获取项目部署路径这样项目名从dorm改成dormitory后页面不需要改任何代码。如果写死/dorm/login换部署名就404。然后是LoginServlet的核心代码WebServlet(/login) public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password req.getParameter(password); UserDao dao new UserDao(); User user dao.findByUsernameAndPassword(username, password); if (user ! null) { req.getSession().setAttribute(loginUser, user); resp.sendRedirect(req.getContextPath() /index.jsp); } else { req.setAttribute(error, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } } }逻辑说明req.setCharacterEncoding(UTF-8)必须放在读取参数之前否则POST提交的中文用户名会乱码。findByUsernameAndPassword里用PreparedStatementSQL是SELECT * FROM t_user WHERE username? AND password?用预编译防止SQL注入——答辩时主动说出这一点很加分。登录成功后把user对象放进session这是后续所有页面判断登录状态的基础。失败时用forward回到登录页并携带error消息比sendRedirect更合适因为重定向会丢request域数据。对应的UserDao查询方法public User findByUsernameAndPassword(String username, String password) { String sql SELECT * FROM t_user WHERE username? AND password?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { User u new User(); u.setId(rs.getInt(id)); u.setUsername(rs.getString(username)); u.setPassword(rs.getString(password)); u.setRole(rs.getString(role)); return u; } } } catch (SQLException e) { e.printStackTrace(); } return null; }参数说明JDK 7的try-with-resources写法让连接自动关闭代码短了一半也避免忘关连接导致Tomcat连接耗尽。手动关连的旧写法容易漏特别是查询抛出异常时连接不归还几次之后Tomcat就拒绝服务了。3.4 列表页面渲染jsp里什么时候用默认代码什么时候该用EL宿舍管理系统的核心页面是学生列表和宿舍列表。学生列表需要展示学生的学号、姓名、性别、所在宿舍楼和房间号。这里最容易犯的错误是在jsp里写一堆% %Java代码去遍历List。能用EL和JSTL就绝不用脚本片段。正确写法是把查询结果放进request域然后转发到jsp。% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table border1 trth学号/thth姓名/thth性别/thth宿舍/thth操作/th/tr c:forEach items${studentList} varstu tr td${stu.stuNo}/td td${stu.name}/td td${stu.gender}/td td${stu.dorm.building}${stu.dorm.roomNo}/td tda href${pageContext.request.contextPath}/student/delete?id${stu.id}删除/a/td /tr /c:forEach /table${stu.dorm.building}依赖实体类的关联属性Student类里有一个Dorm dorm字段查询时用JOIN把宿舍信息也查出来填进去。如果Student里只有dormId这里就只能显示ID列表页就不直观。这是个小设计决策列表页需要展示关联表的信息时要么JOIN要么页面再做一次查询前者是标准答案。JSTL的taglib声明要放在jsp第一行附近如果没有JSTL依赖就去maven仓库下载jstl-1.2.jar放进webapp/WEB-INF/lib。4. 把项目跑起来idea运行javaweb项目配置与war包部署4.1 在IDEA里新建传统jsp项目并配置Tomcat很多同学卡在“代码写完了但不知道在哪启动”。IDEA运行javaweb项目的配置分两步。第一步点击Run菜单的Edit Configurations新增一个Tomcat Server的Local配置在Server标签页的Application server选择Tomcat 8.5的安装目录。第二步切到Deployment标签页点加号选择Artifact选中项目打好的war包Application context填/dorm。这两步做完后右上角就会多出一个Tomcat螃蟹图标点绿色三角运行。第一次运行时如果报Error during artifact deployment原因是IDEA没有把依赖jar包打进artifact。检查Project Structure的Artifacts菜单选中Output Layout标签确认WEB-INF/lib之下有mysql驱动。IDEA的“Build on frame deactivation”选项很管用勾上之后修改Java或jsp文件再切回浏览器就是最新内容不用手动重启Tomcat。4.2 数据库初始化把SQL脚本跑进MySQL数据库初始化是大作业里体验最差的一步因为每个人的MySQL账号密码不一样。把之前那段建表SQL保存为init.sql在命令行执行mysql -u root -p init.sql或者用Navicat直接复制进去执行。注意t_user表里要手动插入一条管理员账号INSERT INTO t_user (username, password, role) VALUES (admin, admin123, admin);然后回到DBUtil.java里把url、user、password改成你自己的。这个步骤是运行期最容易出问题的地方详见5.5。4.3 传统jsp项目打包war两种方式与部署路径传统jsp项目打包war有两种方式。第一种在IDEA里Build菜单的Build Artifacts选择dorm:war产物会生成到target/目录。第二种是Maven方式在项目根目录执行mvn clean package同理会得到dorm.war。拿到war包后把它直接复制到Tomcat安装目录的webapps/下启动Tomcat它会自动解压war包变成同名文件夹。这里值得展开一个概念war是web application archive的缩写它本质上是一个zip压缩包里面包含了WEB-INF/classes、WEB-INF/lib、jsp、静态资源等等。Tomcat的webapps目录就是发布目录放进去的war包会被自动展开部署。这比在IDE里启动更接近真实部署答辩时被问“项目怎么部署到服务器”就能答上来。敏感点在于部署路径如果Tomcat的server.xml没有改端口默认是8080访问地址就是http://localhost:8080/dorm/login.jsp。如果/dorm这个上下文路径被别的war包占了会冲突部署失败所以要检查webapps下有没有同名目录。另外补充nginx和jsp的关系如果机房验收要求用nginx转发则nginx配置里只需要把location /dorm的请求proxy_pass到http://127.0.0.1:8080jsp的解析完全交给Tomcat。nginx本身不处理jsp这一点要在答辩时讲清楚。5. 宿舍管理系统踩坑实录五个必看的运行期故障5.1 页面上全是乱码连SQL里中文都变问号现象登录页面输入中文jsp显示“???”或直接乱码数据库里倒是正常但页面显示不对或者反过来数据库里中文变问号。原因字符集问题贯穿三个环节。jsp文件本身的编码没保存成UTF-8Tomcat的连接器没有配置URIEncodingUTF-8JDBC连接URL里没有characterEncodingutf8。任何一个环节出错都会乱码。解决三处配置统一。第一处在server.xml的Connector标签里加URLEncodingUTF-8。第二处jsp文件第一行维持% page contentTypetext/html;charsetUTF-8 %同时IDEA右下角确认文件编码是UTF-8。第三处JDBC的url末尾加characterEncodingutf8useSSLfalse。这三处都改完之后新增一条中文数据测试大多数乱码都能解决。5.2 页面404访问路径和servlet映射对不上现象启动Tomcat后访问http://localhost:8080/dorm/login.jsp是正常的但表单提交到/login之后浏览器报404。原因两个常见误区是注解路径写错和web.xml配置冲突。WebServlet(/login)注解里的路径必须以/开头而表单里的action如果写成了login而没有前导斜杠浏览器会解析成相对路径「相对于当前URL目录」。解决表单action换成action${pageContext.request.contextPath}/login这样无论页面在哪个层级都能正确拼接。同时检查WEB-INF/web.xml里有没有重复配置了相同路径的servlet-mapping注解和web.xml只能选一种别同时配置同一个URL。5.3 端口被占用Tomcat启动即失败的背后现象IDEA控制台报Port 8080 was already in useTomcat直接启动失败。有时候报错更隐蔽Exception opening server socket之类的没有明确说端口占用但日志里有一个SEVERE日志。原因8080端口被其他程序占了常见的是之前有残留的Tomcat进程没退出、IIS、其他开发服务器的默认端口也用了8080。解决命令行执行netstat -ano | findstr 8080找到占用端口的PID然后在任务管理器里结束那个进程。如果不想杀进程就改Tomcat的端口在conf/server.xml里把Connector的port改成8081或9090。注意要同时改三处端口不一定HTTP的Connector一个端口就够了但改端口后访问URL也跟着变。5.4 ClassNotFoundExceptionjar包放进lib/tomcat/lib的差别现象启动时日志报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver明明IDEA的依赖列表里能看到这个jar包。原因这是IDEA的Artifact打包问题。IDEA运行项目时依赖jar包必须由Artifact的WEB-INF/lib包含而不仅仅是Gradle或Maven的依赖库里存在。很多同学在Project Structure里加了依赖但忘了把它加入Artifact的输出。解决File → Project Structure → Artifacts → 选中你的war exploded → Output Layout → 在WEB-INF/lib上点加号 → Library Files → 选mysql驱动。改完后重新部署。这里写一个替代方案直接把jar包复制到Tomcat的lib目录下也能生效但不推荐因为这个做法会让项目换一台机器就启动不了责任全在部署者自己。5.5 数据库连接失败驱动、URL参数、防火墙三层排查现象启动后页面能打开但是点登录或者查列表时报SQLException提示通信失败。测试数据库连接时也应证了连接不上。原因三层问题。第一层是驱动问题上述ClassNotFoundException。第二层是URL格式问题MySQL 5.7的url一般是jdbc:mysql://localhost:3306/dormMySQL 8.0需要jdbc:mysql://localhost:3306/dorm?serverTimezoneAsia/ShanghaiuseSSLfalse驱动也要换成com.mysql.cj.jdbc.Driver。第三层是服务没起来或防火墙拦截MySQL服务没启动或者3306端口被防火墙挡住。解决逐层排查。先telnet 127.0.0.1 3306检查端口通不通不通就看MySQL服务状态Windows的services.msc里找MySQL服务并启动。再确认JDBC驱动版本和MySQL版本对应。最后把DBUtil里的URL写成带时区参数的完整版避免8.0驱动报时区错误。这套流程能解决九成以上的连接问题。6. 验收前值得加的功能Filter登录拦截、分页查询与汇报话术6.1 用一个Filter包住所有页面不写注释的登录检查大作业普遍有一个问题不登录也能直接输入地址访问index.jsp。这会被老师扣分血泪经验告诉大家必须加Filter。实现一个LoginFilter拦截所有除login以外的URL。核心代码WebFilter(/*) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; String uri req.getRequestURI(); if (uri.endsWith(login.jsp) || uri.contains(/login) || uri.endsWith(.css) || uri.endsWith(.js) || uri.endsWith(.png) || uri.endsWith(.jpg)) { chain.doFilter(request, response); return; } if (req.getSession().getAttribute(loginUser) null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } chain.doFilter(request, response); } }逻辑说明白名单里的路径直接放行包括登录页、登录接口、静态资源。判断逻辑是“session里有没有user对象”没有就重定向进登录页。注意静态资源一定要放行否则jsp引用的css和图片全被拦截。Filter是servlet规范里的三大组件之一和servlet生命周期并列第一个被问“请求进来先经过谁”的答案就是Filter。能把白名单思路讲清楚比多写十个功能点都加分。注意Filter的urlPatterns用/*而不是/因为/只匹配到应用根目录拦不住子路径的页面。6.2 最小分页查询LIMIT 两个参数学生列表如果数据超过50条不分页会被老师追问。最小可用的分页是接收page和pageSize两个参数SQL用LIMIT offset, pageSize页面上显示上一页和下一页两个按钮。核心SQL和servlet代码int page 1, pageSize 10; String pageStr req.getParameter(page); if (pageStr ! null !pageStr.isEmpty()) { page Integer.parseInt(pageStr); } int offset (page - 1) * pageSize; String sql SELECT * FROM t_student LIMIT ? , ?;LIMIT 0, 10表示从第1条开始取10条LIMIT 10, 10表示从第11条开始取10条。分页查询的目的是减少数据传输量而不是为了炫技能在答辩时说清offset的计算方式就够用了。总页数可以单独写一条SELECT COUNT(*)来算也可以用一次性把总数查出来放request域。6.3 答辩时怎么讲这个项目从技术点到设计动机验收时的汇报顺序建议是先说用户角色和功能再用一个请求链路串起servlet生命周期和jsp渲染最后讲自己踩过哪个坑、怎么修的。三层架构里每层各抽出十分钟来讲例如Controller层的职责是接收参数和跳转Service层如果有做业务校验DAO层只写SQL。这比从登录页讲到删除按钮的流水账强十倍。整个方案投入产出比很高这个方向值得做透。升级路线也清晰把JDBC换成MyBatis把jsp换成Vue前后端分离把Tomcat部署换成Docker——每一次升级都能让项目在毕业设计或求职项目里多一个亮点。我在自己带过的项目里见过太多同学卡在环境上最后把整个方向否定了其实是亏的。做jspservlet就像学骑自行车先装辅助轮等你把请求流转、状态管理这些底子打扎实后面上框架都是水到渠成的事希望帮到你。本文还有配套的精品资源点击获取
返回列表