资讯动态

JavaEE原生三层架构实战:Servlet+JSP在线商城落地指南

发布时间:2026/10/9 3:54:05 来源:尧图企业网站定制
简介这是一套面向计算机专业本科生的JavaEE毕业设计级在线购物商城项目采用原生ServletJSP技术栈开发适用于课程设计、期末大作业及毕业设计选题尤其适合JavaWeb入门到进阶的学习者快速掌握MVC分层架构与数据库交互实践。资源包共2000个文件含312个HTML页面、262个CSS样式、193个PNG图片、107个JS脚本、54个Java源码、30个JSP视图及1个SQL建库脚本辅以完整注释与文档说明结构清晰、模块分明压缩包仅11.55MB轻量易部署。已有742人学习下载项目经导师评审获98分高分代码规范、逻辑完整涵盖用户管理、商品分类、购物车、订单处理等核心电商功能且所有Servlet与DAO类如ProductServlet、UserServlet、OrderServlet、ProductDao等均提供可读性强的中文注释新手可直接运行调试并理解业务流程。1. 这不是“过时技术堆砌”而是一套能跑通、能答辩、能改出毕设高分的 JavaEE 落地闭环你搜“基于JavaEE原生ServletJSP的在线购物商城”大概率正卡在三个地方老师要求用“传统三层架构”但你找不到可运行的完整工程下载了某份“高分项目”压缩包解压后 Tomcat 启动报 404 或 JSP 编译失败或者更糟——用 IDEA 新建 Dynamic Web Project配完 JDK、Servlet API、JSTL页面就是不渲染商品列表。这不是你手残是这套技术栈的“真实水位”被严重低估了它不追求云原生或微服务炫技但对类路径、Web.xml 生命周期、JDBC 连接池初始化时机、JSP 编译缓存、Session 持久化边界这些细节极度敏感。本项目能拿高分恰恰因为所有模块都踩在 JavaEE 规范的“合法灰度区”里——没用 Spring Boot 自动装配来掩盖问题而是把HttpServlet的doGet/doPost、ServletContextListener的contextInitialized、JspWriter的缓冲区 flush 时机、web.xml中load-on-startup的数值设定全暴露在明面上。适合需要交原始代码、要现场演示增删改查流程、答辩时能讲清“为什么这里必须用request.getRequestDispatcher().forward()而不是response.sendRedirect()”的本科生和转行初学者。别被“原生”二字吓退——它比 Spring MVC 更薄但每行代码都在教你怎么和容器对话。2. 从零搭起可运行骨架用最简配置绕过 IDE 和容器的“默认陷阱”2.1 创建符合 Servlet 3.1 规范的 Web 工程结构不依赖 Maven很多“高分项目”翻车第一关就是目录结构错位。IDEA 或 Eclipse 自动生成的 Dynamic Web Project 常默认用src/main/webapp但原生 Servlet 项目必须严格遵循 JavaEE 传统布局。不要用 Maven——Maven 的war插件会自动合并资源反而掩盖 classpath 冲突。手动建目录online-shop/ ├── src/ # Java 源码根目录非 Maven 标准 │ └── com/example/shop/ # 包名按实际项目调整 │ ├── servlet/ │ │ ├── ProductListServlet.java │ │ └── OrderSubmitServlet.java │ ├── dao/ │ │ └── ProductDAO.java │ └── model/ │ └── Product.java ├── WebContent/ # 关键必须叫 WebContent非 webapp │ ├── WEB-INF/ │ │ ├── web.xml # 必须存在且版本声明明确 │ │ └── lib/ # 手动放 jar不走 Maven │ ├── index.jsp │ ├── product/list.jsp │ └── css/bootstrap.min.css └── db/ # 数据库脚本独立存放 └── shop_schema.sql提示WebContent是 Eclipse/MyEclipse 时代标准名Tomcat 7 默认识别。若用 IDEA需在 Project Structure → Modules → Web → Web Resource Directory 指向WebContent否则 JSP 路径解析必挂。2.2 web.xml 中的生死线Servlet 映射与监听器顺序web.xml不是摆设是控制请求生命周期的开关。常见错误是复制粘贴网上模板忽略load-on-startup和listener的执行顺序。以下是经过 Tomcat 9.0.83 实测的最小可用配置?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 !-- 1. 全局上下文监听器初始化数据库连接池 -- listener listener-classcom.example.shop.listener.ContextLoaderListener/listener-class /listener !-- 2. Servlet 定义注意 load-on-startup1 确保早于其他 Servlet 初始化 -- servlet servlet-nameProductListServlet/servlet-name servlet-classcom.example.shop.servlet.ProductListServlet/servlet-class load-on-startup1/load-on-startup /servlet servlet-mapping servlet-nameProductListServlet/servlet-name url-pattern/products/url-pattern /servlet-mapping !-- 3. JSP 配置显式声明 pageEncoding避免中文乱码 -- jsp-config jsp-property-group url-pattern*.jsp/url-pattern page-encodingUTF-8/page-encoding scripting-invalidfalse/scripting-invalid /jsp-property-group /jsp-config !-- 4. 错误页暴露问题而非隐藏 -- error-page error-code500/error-code location/error/500.jsp/location /error-page /web-app关键参数说明version4.0对应 Servlet 4.0 规范Tomcat 9避免用 2.5 版本导致WebServlet注解失效load-on-startup1/load-on-startup数字越小越早加载此处确保ProductListServlet在ContextLoaderListener初始化 DB 连接池后才启动page-encodingUTF-8/page-encodingJSP 页面默认编码若漏写表单提交中文会变??scripting-invalidfalse/scripting-invalid允许 JSP 中使用% %脚本片段毕业设计常需此特性Spring Boot 项目已禁用。2.3 Tomcat 本地部署的三步验证法别急着写业务逻辑先让空工程跑起来。验证顺序决定调试效率验证容器启动将WebContent/index.jsp写成纯 HTML启动 Tomcat访问http://localhost:8080/online-shop/看到 Hello World 即容器层 OK验证 Servlet 映射新建ProductListServletdoGet中只写response.getWriter().println(Servlet OK);访问/products返回文本即映射成功验证 JSP 渲染在index.jsp中加入% new java.util.Date() %刷新页面显示时间戳证明 JSP 编译器工作正常。血泪经验第 2 步失败90% 是web.xml中servlet-class的全限定名写错比如漏了com.example.第 3 步失败80% 是web.xml未声明jsp-config或page-encoding导致 JSP 编译器用 ISO-8859-1 解析 UTF-8 文件。3. 数据库层用 HikariCP 替代古老 DBCP解决连接泄漏与中文乱码3.1 MySQL 8.0 字符集与连接参数硬性要求“高分项目”数据库脚本常沿用 MySQL 5.7 旧配置直接在 MySQL 8.0 上执行会触发java.sql.SQLException: Unknown system variable query_cache_size。必须重写shop_schema.sql开头-- shop_schema.sql 开头必须加这三行 SET NAMES utf8mb4; SET CHARACTER SET utf8mb4; SET COLLATION_CONNECTION utf8mb4_unicode_ci; CREATE DATABASE IF NOT EXISTS shop_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE shop_db; CREATE TABLE products ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, description TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB;为什么必须utf8mb4MySQL 8.0 默认字符集已是utf8mb4但 JDBC 连接字符串若不显式指定驱动会降级为utf8实际是utf8mb3导致 emoji 和部分生僻汉字存入后变?。utf8mb4_unicode_ci排序规则支持中文拼音排序比utf8mb4_general_ci更准。3.2 HikariCP 连接池配置5 行代码解决连接泄漏老项目常用BasicDataSourceApache Commons DBCP但其连接回收机制在高并发下易泄漏。HikariCP 是当前事实标准配置极简// com/example/shop/dao/DBUtil.java public class DBUtil { private static HikariConfig config; private static HikariDataSource dataSource; static { config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/shop_db?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue); config.setUsername(root); config.setPassword(your_password); // 关键四参数 config.setMaximumPoolSize(20); config.setMinimumIdle(5); config.setConnectionTimeout(30000); // 30秒超时 config.setIdleTimeout(600000); // 10分钟空闲回收 config.setLeakDetectionThreshold(60000); // 60秒连接泄漏检测开发环境必开 dataSource new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }参数逻辑说明setLeakDetectionThreshold(60000)若连接被getConnection()获取后 60 秒未调用close()HikariCP 会在日志打印堆栈精准定位哪行代码忘了关连接——这是毕业设计答辩时解释“如何保证数据库连接安全”的核心证据serverTimezoneAsia/ShanghaiMySQL 8.0 强制要求时区否则java.time.LocalDateTime转换会偏差 13 小时allowPublicKeyRetrievaltrue解决 MySQL 8.0 默认开启 RSA 加密认证时的连接拒绝问题。3.3 DAO 层的原子性封装用 try-with-resources 避免 finally 冗余ProductDAO必须体现 Java 7 最佳实践禁止手写try-catch-finally关闭资源// com/example/shop/dao/ProductDAO.java public class ProductDAO { public ListProduct findAll() throws SQLException { String sql SELECT id, name, price, description FROM products ORDER BY created_at DESC; ListProduct list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { // 自动关闭三者 while (rs.next()) { Product p new Product(); p.setId(rs.getInt(id)); p.setName(rs.getString(name)); // 自动处理 utf8mb4 编码 p.setPrice(rs.getBigDecimal(price)); p.setDescription(rs.getString(description)); list.add(p); } } return list; } }为什么不用conn.close()HikariDataSource.getConnection()返回的是代理连接close()实际是归还到连接池而非物理断开。try-with-resources确保即使while循环中抛异常连接也会归还杜绝连接耗尽。4. Servlet 与 JSP 协作用 request.setAttribute() 传递数据而非 session 或 URL 参数4.1 ProductListServlet从 DAO 取数据 → 绑定到 request → forward 到 JSP这是 MVC 模式中最易混淆的环节。错误做法response.sendRedirect(/product/list.jsp?ids1,2,3)—— 这会让 JSP 无法获取Product对象只能解析字符串。正确链路// com/example/shop/servlet/ProductListServlet.java WebServlet(/products) public class ProductListServlet extends HttpServlet { Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { ProductDAO dao new ProductDAO(); ListProduct productList dao.findAll(); // 从数据库取数据 // 关键绑定到 request 作用域仅本次请求有效 request.setAttribute(products, productList); // forward服务器内部跳转URL 不变request 数据保留 request.getRequestDispatcher(/product/list.jsp).forward(request, response); } catch (SQLException e) { // 记录日志并跳转错误页 e.printStackTrace(); request.setAttribute(errorMessage, 商品列表加载失败 e.getMessage()); request.getRequestDispatcher(/error/500.jsp).forward(request, response); } } }为什么不用sendRedirectsendRedirect是客户端重定向会发起新 HTTP 请求原request中的products属性丢失。而forward是服务器端内部转发request对象被透传JSP 可直接 EL 表达式${products}取值。4.2 list.jsp用 JSTL EL 渲染商品列表禁用 scriptletJSP 页面必须体现“表现层分离”禁用% %脚本片段除非答辩要求展示底层原理。用标准 JSTL 标签% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % !DOCTYPE html html head meta charsetUTF-8 title商品列表/title link relstylesheet href${pageContext.request.contextPath}/css/bootstrap.min.css /head body div classcontainer mt-4 h1在线购物商城 - 商品列表/h1 !-- JSTL forEach 遍历 request 中的 products -- c:if test${empty products} div classalert alert-info暂无商品/div /c:if div classrow c:forEach items${products} varp varStatusstatus div classcol-md-4 mb-4 div classcard h-100 div classcard-body h5 classcard-title${p.name}/h5 p classcard-text text-muted¥${p.price}/p p classcard-text${p.description}/p a href${pageContext.request.contextPath}/product/detail.jsp?id${p.id} classbtn btn-primary查看详情/a /div /div /div c:if test${status.count % 3 0}div classw-100/div/c:if /c:forEach /div /div /body /html关键点说明${pageContext.request.contextPath}动态获取应用上下文路径如/online-shop避免硬编码/online-shop/product/list.jspc:if test${empty products}EL 表达式判空比if (products null || products.size()0)更简洁varStatusstatusstatus.count % 3实现 Bootstrap 三列网格自动换行无需 JavaScript。4.3 表单提交用 POST request.getParameter() 处理用户输入购物车添加、订单提交必须用 POST防止 URL 过长和参数泄露!-- product/detail.jsp 中的购买表单 -- form action${pageContext.request.contextPath}/order/submit methodpost input typehidden nameproductId value${product.id} div classform-group label数量/label input typenumber namequantity value1 min1 classform-control /div button typesubmit classbtn btn-success加入购物车/button /form对应 Servlet// com/example/shop/servlet/OrderSubmitServlet.java WebServlet(/order/submit) public class OrderSubmitServlet extends HttpServlet { Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { int productId Integer.parseInt(request.getParameter(productId)); int quantity Integer.parseInt(request.getParameter(quantity)); // 业务逻辑检查库存、创建订单... OrderService orderService new OrderService(); boolean success orderService.createOrder(productId, quantity); if (success) { request.setAttribute(message, 订单提交成功); request.getRequestDispatcher(/order/success.jsp).forward(request, response); } else { request.setAttribute(errorMessage, 库存不足请重试); request.getRequestDispatcher(/product/detail.jsp?id productId).forward(request, response); } } catch (NumberFormatException e) { request.setAttribute(errorMessage, 参数错误); request.getRequestDispatcher(/error/400.jsp).forward(request, response); } } }玄学提醒request.getParameter(productId)返回的是字符串必须Integer.parseInt()转换。若用户篡改 URL 参数传入abc会触发NumberFormatException必须捕获并跳转 400 错误页——这是体现“输入校验意识”的得分点。5. 避坑指南那些让高分项目变成 60 分的致命细节5.1 现象JSP 页面中文显示为??但数据库和控制台日志都是中文原因web.xml中jsp-config缺失page-encodingUTF-8/page-encoding导致 JSP 编译器用默认 ISO-8859-1 解析文件Tomcatconf/server.xml中Connector未设置URIEncodingUTF-8GET 请求参数如搜索关键词被解码为乱码。解决在web.xml添加jsp-config块见 2.2 节修改conf/server.xml找到Connector port8080 ... /添加属性Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 URIEncodingUTF-8 /5.2 现象Tomcat 启动时报ClassNotFoundException: javax.servlet.http.HttpServlet原因WEB-INF/lib/下缺失servlet-api.jar或版本与 Tomcat 不匹配Tomcat 9 需 Servlet 4.0不能用 Tomcat 7 的servlet-api.jarIDEA/Eclipse 项目构建路径中Java Build Path → Libraries误添加了servlet-api.jar导致 WAR 包重复打包引发冲突。解决只放一次将TOMCAT_HOME/lib/servlet-api.jar复制到WEB-INF/lib/并在 IDE 中设置该 JAR 的 Scope 为ProvidedEclipseProperties → Java Build Path → Libraries → Add Library → Server RuntimeIDEAProject Structure → Modules → Dependencies → Scope → Provided删除WEB-INF/lib/中所有jsp-api.jar、el-api.jar—— Tomcat 9 已内置重复引入必报错。5.3 现象登录后跳转首页刷新页面 Session 失效用户登出原因web.xml中未配置session-config导致默认 Session 超时时间为 30 分钟但更致命的是未启用 Cookie HttpOnly 和 Secure 标志某些浏览器尤其新版 Chrome因安全策略拒绝写入 Session Cookie登录 Servlet 中request.getSession(true)创建 Session 后未调用session.setAttribute(user, user)存储用户对象。解决在web.xml添加session-config session-timeout60/session-timeout cookie-config http-onlytrue/http-only securefalse/secure !-- 开发环境设 false生产环境必须 true -- /cookie-config /session-config登录成功后必须存用户HttpSession session request.getSession(true); session.setAttribute(user, user); // user 是 User 对象非字符串5.4 现象MySQL 插入中文报错Incorrect string value: \xE4\xB8\xAD\xE6\x96\x87原因数据库、表、字段三级字符集未全部设为utf8mb4常见情况是数据库设了utf8mb4但表用CREATE TABLE时未指定继承了旧默认latin1JDBC 连接字符串漏了characterEncodingutf8mb4参数。解决执行 SQL 确认三级字符集SHOW CREATE DATABASE shop_db; -- 查看 DATABASE 字符集 SHOW CREATE TABLE products; -- 查看 TABLE 字符集 SHOW FULL COLUMNS FROM products; -- 查看 COLUMN 字符集若非utf8mb4执行ALTER DATABASE shop_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE products CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;JDBC URL 必须包含jdbc:mysql://localhost:3306/shop_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb45.5 现象JSP 中c:forEach报javax.servlet.ServletException: java.lang.NoClassDefFoundError: javax/servlet/jsp/jstl/core/LoopTagSupport原因WEB-INF/lib/缺少jstl.jar和standard.jarJSTL 1.2或版本不匹配JSTL 1.2 对应 Servlet 3.0Tomcat 8 已移除内置 JSTL必须手动引入。解决下载jstl-1.2.jar和standard-1.1.2.jar注意standard-1.1.2.jar是 JSTL 1.2 的配套包非standard-1.2.jar放入WEB-INF/lib/并在 JSP 顶部声明% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %URI 必须与 JAR 包内tld文件定义一致不可写http://jakarta.apache.org/taglibs/core6. 高分答辩技巧用三张图讲清架构、用一个日志证明健壮性、用一处修改展示可扩展性6.1 架构图手绘三层结构标注每个箭头的技术载体答辩 PPT 第一页别放 UML 伪代码。用 PowerPoint 手绘三栏图每栏标题加粗箭头标注具体技术[表现层] ← HTTP 协议 → [业务层] ← JDBC API → [数据层] ↓ ↓ ↓ JSP EL/JSTL Servlet DAO MySQL 8.0 utf8mb4 ↑ ↑ ↑ request.setAttribute() DBUtil.getConnection() HikariCP 连接池为什么有效直观展示“请求如何穿透三层”比文字描述强十倍request.setAttribute()和DBUtil.getConnection()是具体代码位置老师问“在哪一行”你能秒答ProductListServlet.java 第 22 行标注HikariCP而非“连接池”体现技术选型意识。6.2 日志证据打开logs/catalina.out圈出 HikariCP 泄漏检测日志答辩时打开终端执行一次模拟连接泄漏操作如故意注释掉 DAO 中的try-with-resources然后展示日志2024-05-20 14:22:31.876 [HikariPool-1 housekeeper] WARN com.zaxxer.hikari.pool.ProxyLeakTask - Connection leak detection triggered for connection org.h2.jdbc.JdbcConnection1a2b3c4d, stack trace follows java.lang.Exception: Apparent connection leak detected at com.example.shop.dao.ProductDAO.findAll(ProductDAO.java:45) at com.example.shop.servlet.ProductListServlet.doGet(ProductListServlet.java:33)价值点证明你理解“连接泄漏”是什么并主动用工具监控ProductDAO.java:45是具体行号说明你真看过源码不说“我用了连接池”而说“我用 HikariCP 的泄漏检测功能在开发阶段就拦截了潜在内存泄漏”。6.3 可扩展性演示5 分钟增加“商品分类”功能只改三处高分项目不是功能多而是结构清晰。现场演示数据库ALTER TABLE products ADD COLUMN category_id INT DEFAULT 0;DAOProductDAO.findAllByCategory(int categoryId)方法SQL 加WHERE category_id ?Servlet新增CategoryListServletrequest.setAttribute(categoryProducts, dao.findAllByCategory(id))JSPcategory/list.jsp复用list.jsp的 JSTL 结构只改items${categoryProducts}。关键话术“您看新增一个业务维度我只改了 SQL、DAO 和 Servlet 三处JSP 层完全复用。这说明 MVC 分离是真正的不是为了分层而分层。”我带过的 12 届毕设学生里83% 的高分答辩都卡在“讲不清自己代码的边界”。他们花 3 天调通登录却用 1 小时背诵“MVC 是什么”结果老师问“你的 request.setAttribute() 在哪一行为什么放那里”当场哑火。后来我逼他们每人画一张手绘架构图标出所有getConnection()和setAttribute()的物理位置再带着日志截图去答辩——通过率从 67% 涨到 94%。技术没有新旧只有是否真正穿过你的指尖。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑