ARTICLE DETAIL

资讯详情

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

Java Web入门实战:JSP+Servlet+MySQL教学系统搭建与避坑指南

Java Web入门实战:JSP+Servlet+MySQL教学系统搭建与避坑指南 简介本资源是一套面向高校计算机专业本科生的毕业设计完整交付包聚焦教学设备报修场景解决教师报修流程繁琐、学生报修入口不统一等实际管理痛点。系统基于B/S架构采用JSP前端Java后端MySQL数据库技术栈实现具备用户注册、报修提交、状态查询、公告展示等核心功能界面友好、结构清晰适合作为Java Web课程设计或毕业设计参考范例。压缩包共662个文件含87个JSP页面实现业务逻辑与交互、39个CSS样式文件保障前端呈现、29个JS脚本增强动态效果、419个GIF动图用于界面提示与加载以及SQL建库脚本、论文文档、系统演示视频等关键交付物整体大小15.94MB。目前已有208人学习下载读者可直接部署运行、对照源码理解MVC分层设计复现完整开发流程并参考配套论文掌握需求分析、数据库设计与系统测试等毕业设计必备环节。1. 为什么一个“教学设备报修系统”毕业设计至今还在被上百所高校用作Java Web入门实战靶场这不是一个过时的怀旧项目而是一套被反复验证过的、能闭环跑通“用户提报→管理员分派→维修员处理→状态回传→数据统计”全链路的最小可行教学系统。它用JSPServletMySQL构建不依赖Spring Boot自动装配所有请求流转、页面跳转、数据库交互都裸写在代码里——正因如此学生第一次看到request.setAttribute(list, devices)和c:forEach items${list} vard时才真正理解MVC里“Model怎么到View”的物理路径第一次手动配置web.xml中的servlet-mapping才明白URL路径和Java类之间那层薄薄的映射关系究竟由谁维护。它不是为生产环境设计的但却是目前高校Java Web课程中唯一能把“HTTP协议、会话管理、表单校验、SQL注入防护、文件上传、分页查询”这七项核心能力在2000行以内代码里全部串起来的毕设模板。如果你正卡在“学了Servlet却不会写登录页”或“IDEA里新建JSP项目总404”这个源码包就是你缺的那块拼图。2. 从零启动用IDEA搭建可运行的JSP环境避开Tomcat乱码、JDK版本错配、JSTL失效三大玄学2.1 环境组合必须锁定JDK 8 Tomcat 9.0.x IDEA 2021.3及以上很多同学用JDK 17新建Web项目结果% page contentTypetext/html;charsetUTF-8 %死活不生效页面中文全变方框——根本原因是Tomcat 9.0.x官方明确声明仅完全兼容JDK 8–11而JDK 17的模块化机制会破坏JSP编译器jasper对javax.servlet.*包的反射调用。实测结论JDK 8u291推荐或JDK 11.0.15可用Tomcat 9.0.83是当前最稳版本2023年12月发布修复了JSP EL表达式在嵌套循环中的空指针IDEA必须关闭“Use Tomcat internal JRE”选项Settings → Build → Compiler → Java Compiler → Target bytecode version设为1.8提示不要下载Tomcat官网的.tar.gz源码包必须用.zip二进制发行版否则bin/startup.bat缺失set JAVA_HOME导致启动失败。2.2 IDEA新建JSP项目三步绕过“无法识别JSP语法”陷阱# 第一步创建标准Maven Web项目非Spring Initializr File → New → Project → Maven → 勾选Create from archetype → 选择maven-archetype-webapp!-- 第二步在pom.xml中强制指定Servlet和JSP API关键 -- dependencies dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope !-- 注意此scope不可改为compile -- /dependency dependency groupIdjavax.servlet.jsp/groupId artifactIdjsp-api/artifactId version2.3.3/version scopeprovided/scope /dependency dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency /dependencies# 第三步配置Artifact为WAR exploded不是WAR File → Project Structure → Artifacts → → Web Application: Exploded → From modules → 选中你的module → Output directory设为 target/your-project-name/ → 点击Fix按钮自动补全WEB-INF/lib下的jar包逻辑说明provided作用域告诉Maven——这些API由Tomcat容器提供编译时需要、打包时剔除若设为compile会导致java.lang.ClassCastException: org.apache.jasper.runtime.JspFactoryImpl cannot be cast to javax.servlet.jsp.JspFactory。而WAR exploded模式让IDEA把src/main/webapp目录直接映射为Tomcat的webapps/ROOT避免WAR包解压延迟导致JSP修改后需重启。2.3 验证JSP是否真跑通写一个带EL表达式的测试页在src/main/webapp/index.jsp中写% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html head titleJSP环境验证/title /head body h2服务器时间% new java.util.Date() %/h2 h3EL表达式测试c:out value${param.name ! null ? param.name : 未传参}//h3 form actionindex.jsp input typetext namename placeholder输入名字/ button typesubmit提交/button /form /body /html参数说明% ... %是脚本片段直接输出Java表达式结果用于验证JVM执行能力c:out是JSTL核心标签验证EL解析器和Tag Library是否加载成功param.name是隐式对象无需后台代码即可获取GET参数这是JSP区别于纯HTML的关键启动Tomcat后访问http://localhost:8080/输入名字提交若页面显示“EL表达式测试xxx”且无500错误则环境已通。3. 核心业务拆解报修流程四阶段如何用JSPServlet落地含数据库建表与DAO手写逻辑3.1 数据库设计三张表撑起完整业务流附建表SQL与字段注释-- 设备表记录教室/实验室所有可报修设备 CREATE TABLE device ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT 设备名称如投影仪、电脑, location VARCHAR(100) NOT NULL COMMENT 安装位置如301多媒体教室, status ENUM(正常,故障,维修中,报废) DEFAULT 正常, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 报修单表用户提交的工单 CREATE TABLE repair_order ( id INT PRIMARY KEY AUTO_INCREMENT, device_id INT NOT NULL, user_name VARCHAR(20) NOT NULL COMMENT 报修人姓名, phone VARCHAR(11) NOT NULL COMMENT 联系电话, description TEXT COMMENT 故障描述, status ENUM(待处理,已分派,处理中,已完成,已关闭) DEFAULT 待处理, assign_time DATETIME NULL COMMENT 分派时间, finish_time DATETIME NULL COMMENT 完成时间, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (device_id) REFERENCES device(id) ); -- 维修员表分配任务的执行者 CREATE TABLE technician ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20) NOT NULL, phone VARCHAR(11) NOT NULL, status ENUM(空闲,忙碌) DEFAULT 空闲 );关键设计点说明repair_order.status用ENUM而非INT避免前端传入非法值如status99数据库层兜底校验device.location用VARCHAR(100)而非TEXT因搜索频率高需建索引ALTER TABLE device ADD INDEX idx_location(location)assign_time和finish_time设为NULL允许因“待处理”状态无需时间戳避免插入时硬塞0000-00-00 00:00:00引发MySQL严格模式报错3.2 Servlet分层结构每个请求对应一个Servlet类拒绝万能DoPost按MVC规范本系统划分6个核心Servlet请求路径Servlet类名职责/device/listDeviceListServlet查询所有设备返回ListDevice到device/list.jsp/order/addOrderAddServlet接收表单插入repair_order重定向到/order/success.jsp/order/listOrderListServlet分页查报修单支持按status筛选/order/assignOrderAssignServlet更新repair_order.status为“已分派”设置assign_time更新technician.status为“忙碌”/order/finishOrderFinishServlet将status设为“已完成”填finish_time/loginLoginServlet校验用户名密码此处用明文演示实际应加盐哈希注意所有Servlet必须继承HttpServlet且doGet()和doPost()方法中禁止直接写HTML输出必须通过request.getRequestDispatcher(xxx.jsp).forward(request, response)跳转否则破坏MVC分离原则。3.3 DAO手写技巧用PreparedStatement防SQL注入附OrderDAO关键方法public class OrderDAO { // 插入报修单重点看参数绑定 public void addOrder(RepairOrder order) throws SQLException { String sql INSERT INTO repair_order (device_id, user_name, phone, description, status, create_time) VALUES (?, ?, ?, ?, ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setInt(1, order.getDeviceId()); ps.setString(2, order.getUserName()); ps.setString(3, order.getPhone()); ps.setString(4, order.getDescription()); ps.setString(5, order.getStatus()); // status默认待处理 ps.setTimestamp(6, new Timestamp(System.currentTimeMillis())); ps.executeUpdate(); // 获取自增主键ID用于后续日志记录 try (ResultSet rs ps.getGeneratedKeys()) { if (rs.next()) { order.setId(rs.getInt(1)); } } } } // 分页查询避免内存溢出 public ListRepairOrder listOrders(int page, int pageSize, String status) throws SQLException { StringBuilder sql new StringBuilder(SELECT o.*, d.name as device_name FROM repair_order o JOIN device d ON o.device_id d.id); ListObject params new ArrayList(); if (status ! null !status.isEmpty()) { sql.append( WHERE o.status ?); params.add(status); } sql.append( ORDER BY o.create_time DESC LIMIT ?, ?); params.add((page - 1) * pageSize); // offset params.add(pageSize); // limit try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql.toString())) { for (int i 0; i params.size(); i) { ps.setObject(i 1, params.get(i)); } ListRepairOrder list new ArrayList(); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { RepairOrder order new RepairOrder(); order.setId(rs.getInt(id)); order.setDeviceName(rs.getString(device_name)); order.setUserName(rs.getString(user_name)); order.setPhone(rs.getString(phone)); order.setDescription(rs.getString(description)); order.setStatus(rs.getString(status)); order.setCreateTime(rs.getTimestamp(create_time)); list.add(order); } } return list; } } }参数说明ps.setObject(i 1, params.get(i))比ps.setString()更安全自动适配INT/VARCHAR/TIMESTAMP类型分页用LIMIT ?, ?而非LIMIT 0,10因offset需动态计算硬编码会导致第2页查不到数据DBUtil.getConnection()是封装好的工具类内部用DriverManager.getConnection(url, user, pwd)连接池暂未引入毕设简化版4. 避坑指南JSP项目上线前必查的5个致命问题血泪经验总结4.1 现象页面中文显示为“???”控制台无报错原因IDEA的File Encoding设为GBK而Tomcat默认用UTF-8读取JSP文件导致字节流解码错位。解决File → Settings → Editor → File Encodings → Global Encoding / Project Encoding / Default encoding for properties files 全部设为UTF-8在web.xml中添加过滤器强制请求编码filter filter-nameCharacterEncodingFilter/filter-name filter-classorg.apache.catalina.filters.SetCharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameCharacterEncodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping4.2 现象点击“提交报修”后页面空白Network面板显示302重定向到/error.jsp原因OrderAddServlet中response.sendRedirect(/order/list)路径错误。若项目部署在http://localhost:8080/repair/下/order/list会跳转到根路径http://localhost:8080/order/list404正确写法是response.sendRedirect(request.getContextPath() /order/list)。解决所有重定向路径前加request.getContextPath()这是获取当前应用上下文路径的安全方式。4.3 现象JSTL标签c:forEach不解析原样输出HTML源码原因webapp/WEB-INF/lib下缺少jstl.jar和standard.jar或web.xml的web-app根节点未声明JSP 2.0以上版本。解决检查pom.xml中JSTL依赖是否生效右键项目 → Maven → Reloadweb.xml开头必须为?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_4_0.xsd version4.0注意version4.0和web-app_4_0.xsd低于4.0版本不支持EL表达式自动启用。4.4 现象MySQL插入时间字段报错Incorrect datetime value: 0000-00-00 00:00:00原因MySQL 5.7默认开启STRICT_TRANS_TABLES模式禁止零日期。而new Date()转java.sql.Timestamp时若未设值可能生成零时间。解决修改MySQL配置文件my.cnf在[mysqld]下添加sql_modeNO_ENGINE_SUBSTITUTION或在JDBC URL后加参数jdbc:mysql://localhost:3306/repair?serverTimezoneAsia/ShanghaizeroDateTimeBehaviorconvertToNull更优方案在实体类中用LocalDateTime.now()替代new Date()并确保数据库字段为DATETIME类型非TIMESTAMP。4.5 现象Tomcat启动后访问http://localhost:8080/显示404但http://localhost:8080/manager/html能进原因IDEA的Artifact输出路径未指向webapps/ROOT或web.xml中未配置欢迎文件列表。解决web.xml中添加welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list检查Artifact配置Output directory必须是target/your-project-name/且Available Elements中WEB-INF/web.xml已勾选。5. 进阶实战给原始毕设加三个真实可用功能免改架构只增代码5.1 功能一报修单导出Excel用Apache POI 4.1.2兼容JDK 8不引入Spring Boot直接在OrderListServlet中增加导出逻辑// OrderListServlet.java 中新增 doExport 方法 protected void doExport(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ListRepairOrder orders orderDAO.listOrders(1, 1000, null); // 导出全部 // 创建Excel工作簿 XSSFWorkbook workbook new XSSFWorkbook(); XSSFSheet sheet workbook.createSheet(报修单汇总); // 表头 String[] headers {序号, 设备名称, 报修人, 电话, 故障描述, 状态, 提交时间}; XSSFRow headerRow sheet.createRow(0); for (int i 0; i headers.length; i) { headerRow.createCell(i).setCellValue(headers[i]); } // 数据行 int rowNum 1; for (RepairOrder order : orders) { XSSFRow row sheet.createRow(rowNum); row.createCell(0).setCellValue(order.getId()); row.createCell(1).setCellValue(order.getDeviceName()); row.createCell(2).setCellValue(order.getUserName()); row.createCell(3).setCellValue(order.getPhone()); row.createCell(4).setCellValue(order.getDescription().substring(0, Math.min(50, order.getDescription().length()))); row.createCell(5).setCellValue(order.getStatus()); row.createCell(6).setCellValue(new SimpleDateFormat(yyyy-MM-dd HH:mm).format(order.getCreateTime())); } // 设置响应头触发下载 response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setHeader(Content-Disposition, attachment; filenamerepair_export_ System.currentTimeMillis() .xlsx); try (ServletOutputStream out response.getOutputStream()) { workbook.write(out); } finally { workbook.close(); } }关键点说明substring(0, Math.min(50, ...))防止单元格内容过长导致Excel打开卡死SimpleDateFormat格式化时间避免Timestamp直接toString出现纳秒级精度response.getOutputStream()必须用try-with-resources否则Tomcat线程池会因流未关闭而耗尽5.2 功能二设备状态实时统计用JSP内置对象AJAX轮询在device/list.jsp底部加统计卡片div classstat-card h3设备状态统计/h3 div idstat-content p正常span idnormal-count0/span台/p p故障span idfault-count0/span台/p p维修中span idrepairing-count0/span台/p /div button onclickrefreshStats()刷新/button /div script function refreshStats() { fetch(% request.getContextPath() %/device/stats) .then(res res.json()) .then(data { document.getElementById(normal-count).textContent data.normal; document.getElementById(fault-count).textContent data.fault; document.getElementById(repairing-count).textContent data.repairing; }); } // 页面加载后每30秒自动刷新 setInterval(refreshStats, 30000); refreshStats(); // 首次加载 /script对应ServletDeviceStatsServletprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { MapString, Integer stats deviceDAO.getStats(); // SELECT status, COUNT(*) FROM device GROUP BY status response.setContentType(application/json;charsetUTF-8); response.getWriter().write(new ObjectMapper().writeValueAsString(stats)); }提示ObjectMapper来自com.fasterxml.jackson.core:jackson-databind添加到pom.xml即可无需Spring MVC。5.3 功能三手机端适配用Bootstrap 4.6.0零CSS框架冲突在webapp/WEB-INF/web.xml中添加servlet servlet-nameBootstrapServlet/servlet-name servlet-classcom.example.BootstrapServlet/servlet-class /servlet servlet-mapping servlet-nameBootstrapServlet/servlet-name url-pattern/bootstrap/*/url-pattern /servlet-mappingBootstrapServlet只做静态资源代理public class BootstrapServlet extends HttpServlet { Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String path request.getRequestURI().substring(request.getContextPath().length() /bootstrap.length()); String realPath getServletContext().getRealPath(/bootstrap path); File file new File(realPath); if (file.exists() file.isFile()) { String mimeType getServletContext().getMimeType(file.getName()); response.setContentType(mimeType ! null ? mimeType : application/octet-stream); Files.copy(file.toPath(), response.getOutputStream()); } else { response.sendError(HttpServletResponse.SC_NOT_FOUND); } } }然后在JSP中引用link relstylesheet href${pageContext.request.contextPath}/bootstrap/css/bootstrap.min.css script src${pageContext.request.contextPath}/bootstrap/js/bootstrap.bundle.min.js/script这样既避免了CDN不稳定问题又保证了Bootstrap与JSP原生标签共存如c:forEach可嵌套在div classrow内。我带过三届毕设每年都有学生因为“想用Spring Boot重写JSP系统”卡在事务管理上浪费两周——其实这个JSP系统真正的价值不是它多先进而是它把Web开发里那些看不见的线头HTTP请求怎么变成Java对象、Session怎么跨页面传递、SQL怎么从字符串变成安全的PreparedStatement、浏览器缓存怎么干扰表单重复提交……全都赤裸裸地摊开给你看。现在你手里这份源码不是古董而是一份可调试、可打断点、可逐行跟踪的Web开发黑匣子说明书。希望帮到你。本文还有配套的精品资源点击获取
返回列表