news 2026/9/16 0:40:09

JavaWeb分层实践骨架:Servlet+JSP+JDBC电商小项目解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaWeb分层实践骨架:Servlet+JSP+JDBC电商小项目解析

简介:这是一份面向JavaWeb初学者与高校实训学生的在线商城项目实战资源,基于JSP+Servlet+MySQL+JDBC技术栈实现,覆盖用户注册登录、商品浏览、购物车管理、订单提交等核心电商功能,适合课程设计、期末实训及Web开发入门实践。压缩包共189个文件,包含18个JSP页面(前端交互)、11个Java类(业务逻辑与控制器)、22个Class字节码(编译结果)、17个Jar包(数据库驱动与工具依赖)、16个CSS与14个JS文件(页面样式与交互),以及大量图片资源(42个JPG+18个PNG+2个WEBP),整体体积23.58MB,结构完整、模块清晰。已有2034人学习下载,资源附带可直接部署的WAR包、基础数据库脚本及README说明,预览可见RegisterController、DetailController、AddCarController等典型MVC分层代码,便于理解请求流转与数据处理逻辑,是掌握JavaWeb前后端协同开发流程的实用参考范例。

1. 这不是一个“仿小米”的UI套壳项目,而是一套可跑通的JavaWeb分层实践骨架

很多初学者下载“JavaWeb仿小米商城”压缩包后,第一眼看到Register.classDetailController.class这些编译文件就懵了:没有源码怎么学?其实这恰恰是本项目最值得拆解的地方——它用最朴素的Servlet+JSP+JDBC组合,把用户注册、商品详情、购物车添加等核心链路,压缩在6个可执行的Class文件里,不依赖Maven、不包装Spring Boot、不抽象DAO接口,所有逻辑直击HTTP请求生命周期。它解决的不是“如何做出高颜值电商首页”,而是“当浏览器发来一个/addCar?pid=1024&count=2请求时,Java Web容器内部到底发生了什么”。适合大三刚学完JDBC和Servlet生命周期、正卡在“写完代码却不知道请求怎么进来的”阶段的同学;也适合想快速验证自己对HttpServletRequest.getParameter()HttpSession.setAttribute()JDBCUtils.getConnection()三者协作关系理解是否准确的开发者。项目虽小,但每个.class文件背后都对应着一次完整的MVC流程切片。

2. 从字节码反推分层结构:解析RegisterController.class与Register.class的职责边界

2.1 RegisterController.class:Servlet层的请求路由中枢

RegisterController.class是典型的HttpServlet子类编译产物。通过javap -c RegisterController.class反编译其字节码,可确认其doPost()方法中必然包含以下关键指令序列:

// 伪代码还原(实际字节码指令对应此逻辑) String username = request.getParameter("username"); String password = request.getParameter("password"); String email = request.getParameter("email"); Register registerBean = new Register(); // 注意:此处调用的是Register.class的无参构造 registerBean.setUsername(username); registerBean.setPassword(password); registerBean.setEmail(email); int result = JDBCUtils.executeUpdate( "INSERT INTO users(username,password,email) VALUES(?,?,?)", username, password, email ); if (result > 0) { response.sendRedirect("login.jsp"); } else { request.setAttribute("msg", "注册失败"); request.getRequestDispatcher("register.jsp").forward(request, response); }

提示:RegisterController.class本身不处理数据库连接,它只做三件事——接收参数、组装数据对象、调用工具类执行SQL。这种剥离符合Servlet规范:Controller不持有Connection,Connection由JDBCUtils.class统一管理。

2.2 Register.class:纯粹的数据载体(POJO),无业务逻辑

反编译Register.class会发现其仅含私有字段、getter/setter及无参构造方法,典型JavaBean结构:

public class Register { private String username; private String password; private String email; public Register() {} // 必须存在,否则new Register()会抛NoSuchMethodError public void setUsername(String username) { this.username = username; } public String getUsername() { return username; } // ... 其他getter/setter }
2.2.1 为什么不用Map<String, Object>替代Register?

虽然request.getParameterMap()能直接获取键值对,但使用Register类有三个硬性优势:

  • 类型安全setPassword(String)强制密码为字符串,避免getParameter("password")返回null时未判空导致NPE;
  • IDE支持:在RegisterController.doPost()中敲registerBean.即可自动补全字段名,减少拼写错误;
  • 扩展预留:未来若需对密码加密,只需在setPassword()中插入BCrypt.hashpw(...),无需修改Controller层。

2.3 JDBCUtils.class:连接池缺失下的务实方案

项目未使用Druid或HikariCP,JDBCUtils.class采用最简DriverManager.getConnection()实现:

public class JDBCUtils { private static final String URL = "jdbc:mysql://localhost:3306/shoppingmall?useSSL=false&serverTimezone=UTC"; private static final String USER = "root"; private static final String PASSWORD = "123456"; public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); // 每次新建连接,性能低但代码极简 } public static int executeUpdate(String sql, Object... params) { try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { for (int i = 0; i < params.length; i++) { ps.setObject(i + 1, params[i]); // 防止SQL注入的核心:预编译参数化 } return ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); return 0; } } }
2.3.1 关键参数说明与可修改项
参数当前值修改建议说明
useSSL=false强制关闭SSL生产环境必须改为true并配置证书MySQL 8.0+默认要求SSL,本地开发可关闭
serverTimezone=UTC时区设为UTC建议改为Asia/Shanghai避免NOW()函数返回时间与系统时区不一致
USER/PASSWORDroot/123456必须按实际数据库账号修改硬编码密码是严重安全隐患,应抽到properties文件

注意:executeUpdate()方法中try-with-resources自动关闭Connection/PreparedStatement,这是JDK 7+语法糖,确保资源不泄漏。若运行时报java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver,需将mysql-connector-java-8.0.33.jar放入WEB-INF/lib/目录。

3. 购物车功能闭环:AddCarController.class与Session状态管理实战

3.1 AddCarController.class的请求处理链路

该类处理形如/addCar?pid=1024&count=2的GET请求,核心逻辑在于将商品ID与数量存入HttpSession,而非直接操作数据库:

public class AddCarController extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String pidStr = request.getParameter("pid"); String countStr = request.getParameter("count"); // 1. 参数校验(生产环境必须加) if (pidStr == null || countStr == null) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "缺少pid或count参数"); return; } int pid = Integer.parseInt(pidStr); int count = Integer.parseInt(countStr); // 2. 从Session获取购物车Map,若不存在则创建新HashMap Map<Integer, Integer> cart = (Map<Integer, Integer>) request.getSession().getAttribute("cart"); if (cart == null) { cart = new HashMap<>(); request.getSession().setAttribute("cart", cart); } // 3. 更新购物车:累加数量(非覆盖) cart.put(pid, cart.getOrDefault(pid, 0) + count); // 4. 重定向到购物车页面,避免重复提交 response.sendRedirect("cart.jsp"); } }
3.1.1 为什么用request.getSession().getAttribute("cart")而不是request.getAttribute("cart")
  • request.getAttribute()作用域仅限本次请求,跳转到cart.jsp后数据丢失;
  • HttpSession在用户浏览器关闭前持续存在,cart作为Session属性可跨多个请求访问;
  • cart.jsp中通过<%= session.getAttribute("cart") %>即可获取该Map,再用JSTL<c:forEach>遍历渲染。

3.2 DetailController.class:商品详情页的动态数据加载

该类负责根据URL参数pid查询商品详情并转发至detail.jsp

public class DetailController extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String pidStr = request.getParameter("pid"); if (pidStr == null) { response.sendError(HttpServletResponse.SC_BAD_REQUEST); return; } int pid = Integer.parseInt(pidStr); // 查询SQL:SELECT * FROM products WHERE id = ? Product product = JDBCUtils.executeQuery( "SELECT * FROM products WHERE id = ?", rs -> { Product p = new Product(); p.setId(rs.getInt("id")); p.setName(rs.getString("name")); p.setPrice(rs.getBigDecimal("price")); p.setImgUrl(rs.getString("img_url")); return p; }, pid ); if (product != null) { request.setAttribute("product", product); // 绑定到request作用域 request.getRequestDispatcher("detail.jsp").forward(request, response); } else { response.sendError(HttpServletResponse.SC_NOT_FOUND, "商品不存在"); } } }
3.2.1 JDBCUtils.executeQuery()的泛型回调设计

此方法采用Function<ResultSet, T>回调,将结果集映射逻辑与数据库操作分离:

public static <T> T executeQuery(String sql, Function<ResultSet, T> mapper, Object... params) { try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { for (int i = 0; i < params.length; i++) { ps.setObject(i + 1, params[i]); } try (ResultSet rs = ps.executeQuery()) { return rs.next() ? mapper.apply(rs) : null; // 只取第一条记录 } } catch (SQLException e) { e.printStackTrace(); return null; } }

提示:Product类需与数据库products表字段严格对应(如img_url映射到setImgUrl()),否则rs.getString("img_url")返回null。若字段名不一致,应在SQL中用AS别名,例如SELECT id AS productId, name AS productName FROM products

4. 数据库建模与前端交互:MySQL表结构与jQuery异步提交验证

4.1 shoppingmall数据库核心表设计

项目依赖的MySQL数据库需包含以下三张表,建表语句必须与Java代码中的SQL完全匹配:

-- 用户表 CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, -- 存储BCrypt加密后的密文 email VARCHAR(100) NOT NULL ); -- 商品表 CREATE TABLE products ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, img_url VARCHAR(200), description TEXT ); -- 订单表(项目未实现下单,但预留结构) CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, total_price DECIMAL(10,2) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) );
4.1.1 字段类型选择依据
字段类型选择理由
priceDECIMAL(10,2)避免浮点数精度丢失(如19.99存储为19.990000000000002),金融计算必须用DECIMAL
passwordVARCHAR(100)BCrypt加密后密文长度约60字符,留足余量
create_timeDATETIMEMySQL原生时间类型,比BIGINT时间戳更易读、支持NOW()函数

4.2 register.jsp中的jQuery表单验证

前端register.jsp使用jQuery对注册表单做客户端校验,关键代码如下:

<script src="js/jquery-3.6.0.min.js"></script> <script> $(function() { $("#registerForm").submit(function(e) { e.preventDefault(); // 阻止默认提交 var username = $("#username").val().trim(); var password = $("#password").val(); var email = $("#email").val().trim(); // 1. 用户名非空且长度2-20 if (!username || username.length < 2 || username.length > 20) { alert("用户名长度必须为2-20个字符"); return false; } // 2. 密码非空且至少6位 if (!password || password.length < 6) { alert("密码长度不能少于6位"); return false; } // 3. 邮箱格式验证(简单正则) var emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { alert("请输入有效的邮箱地址"); return false; } // 4. 提交表单(触发RegisterController) this.submit(); }); }); </script>
4.2.1 为什么客户端验证不能替代服务端验证?
  • 客户端JS可被禁用或绕过(如直接curl发送请求);
  • RegisterController.class中仍需二次校验username != null && !username.isEmpty()
  • 安全原则:客户端验证只为提升用户体验,服务端验证才是安全底线

5. 部署调试与常见故障排查:Tomcat日志定位与JSP编译错误修复

5.1 Tomcat启动失败的三大高频原因

当将项目部署到Tomcat后访问http://localhost:8080/ShoppingMall/register.jsp报404或500错误,按以下顺序排查:

故障现象日志关键词解决方案
ClassNotFoundException: com.mysql.cj.jdbc.DriverSEVERE: Servlet.service() for servlet [RegisterController] in context with path [/ShoppingMall] threw exceptionmysql-connector-java-8.0.33.jar复制到$CATALINA_HOME/lib/ShoppingMall/WEB-INF/lib/
SQLException: Access denied for user 'root'@'localhost'Caused by: java.sql.SQLException: Access denied for user修改JDBCUtils.class中的USER/PASSWORD为真实数据库账号,或在MySQL中执行CREATE USER 'shopuser'@'localhost' IDENTIFIED BY 'pwd123'; GRANT ALL ON shoppingmall.* TO 'shopuser'@'localhost';
org.apache.jasper.JasperException: /register.jsp (line: 12, column: 1) The method setAttribute(String, Object) is undefined for the type HttpServletRequestorg.apache.jasper.compiler.DefaultErrorHandler.javacError检查register.jsp顶部是否有<%@ page import="javax.servlet.http.HttpServletRequest" %>,JSP中request对象已内置,无需import,此错误通常因JSP语法错误(如多写了<%=)导致编译器误判

5.2 JSP页面中文乱码终极解决方案

register.jsp中显示“用户名”变成“ç”乱码,需同步修改三处:

  1. JSP页面声明register.jsp首行):

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
  2. Tomcat配置$CATALINA_HOME/conf/server.xml中Connector节点):

    <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" /> <!-- 必须添加此属性 -->
  3. 数据库连接URLJDBCUtils.class中):

    private static final String URL = "jdbc:mysql://localhost:3306/shoppingmall?useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8";

提示:URIEncoding="UTF-8"解决GET请求参数乱码(如?username=张三),characterEncoding=UTF-8解决数据库存取乱码,二者缺一不可。

5.3 使用Chrome DevTools验证Session购物车状态

AddCarController添加商品后,可通过浏览器开发者工具验证Session是否生效:

  1. 打开Chrome DevTools(F12)→ Application → Storage → Cookies;
  2. 查看http://localhost:8080域名下的Cookie,确认存在JSESSIONID(Tomcat生成的Session ID);
  3. 在Console中执行:
    // 模拟向cart.jsp发起请求,查看Session中cart内容 fetch('cart.jsp').then(r => r.text()).then(console.log);
    若返回HTML中包含<c:forEach items="${sessionScope.cart}"且渲染出商品,则证明Session写入成功。

最终,这个看似简单的ShoppingMall.zip包,本质是一份可执行的JavaWeb教学切片——它用最原始的Servlet代替Spring MVC的@RequestMapping,用JDBCUtils代替MyBatis的SqlSession,用HttpSession代替Redis缓存。当你能手动反编译出AddCarController.class的字节码,并在Tomcat日志里精准定位到NullPointerException发生在哪一行request.getParameter()时,你就真正穿过了JavaWeb的第一道门。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 0:35:53

少走弯路:AI论文工具2026最新测评与推荐

2026年真正好用的AI论文工具&#xff0c;核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测&#xff0c;千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队&#xff0c;覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

作者头像 李华
网站建设 2026/9/16 0:27:57

AI工具助力24小时高效人生修复方案

1. 项目概述&#xff1a;AI时代的高效人生修复方案"在AI时代如何在一天内修复你的人生"这个标题乍看有些夸张&#xff0c;但作为从业十余年的效率优化专家&#xff0c;我可以负责任地说&#xff1a;通过合理运用现代AI工具和科学方法论&#xff0c;24小时内实现人生关…

作者头像 李华
网站建设 2026/9/16 0:25:44

Redis 7集群搭建实战:从节点规划到故障转移全解析

说到 Redis 7 集群搭建&#xff0c;我估计不少朋友已经踩过一轮坑了。网上教程确实多&#xff0c;但要么停留在单个实例的伪集群&#xff0c;要么只贴命令不讲为什么&#xff0c;真到自己动手把多机环境拉起来&#xff0c;还是会卡在节点握手、槽位分配、故障转移这些细节点上。…

作者头像 李华
网站建设 2026/9/16 0:21:13

OpenClaw Windows安装与配置完整指南

1. OpenClaw Windows 安装完整指南最近在技术社区看到不少关于OpenClaw的讨论&#xff0c;这个工具在数据处理和自动化任务方面确实很强大。作为一款跨平台的开源工具&#xff0c;OpenClaw在Linux环境下部署相对简单&#xff0c;但在Windows系统上安装可能会遇到一些特有的问题…

作者头像 李华
网站建设 2026/9/16 0:20:14

Python 下载 CFSv2 数据:目录规则、断点续传与并发优化

简介&#xff1a;这是一份用于自动化下载CFSv2气象数据的Python脚本资源&#xff0c;面向气象科研人员、气候模型开发者以及需要批量获取NCEP再分析产品的学习者。脚本通过调用UCAR数据接口完成身份认证与数据下载&#xff0c;使用者只需在代码中替换账号密码占位符即可运行&am…

作者头像 李华