ARTICLE DETAIL

资讯详情

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

基于JavaEE+原生Servlet+JSP的在线购物商城项目实战:从架构到避坑

基于JavaEE+原生Servlet+JSP的在线购物商城项目实战:从架构到避坑 简介这是一套面向计算机相关专业学生与Java Web初学者的在线购物商城项目源码采用JavaEE搭配原生Servlet与JSP技术栈实现适合用作毕业设计、期末大作业或课程设计的参考方案也便于新手通过阅读源码理解MVC分层与请求处理流程。压缩包共约2000个文件整体约11.55MB涵盖Java源码、JSP页面、HTML、CSS、JavaScript、图片素材、数据库脚本及配置文件等其中Servlet与DAO类构成核心业务逻辑前端资源负责页面展示SQL文件用于初始化数据表。目前已有742人学习下载。项目代码附带注释包含商品、用户、订单、账户、分类等模块的实现并配有数据库与文档说明下载后简单部署即可运行便于读者对照梳理功能结构、理解各层调用关系并在此基础上进行二次开发或功能扩展。1. 从一份 JavaEE 课设说起在线购物商城到底要跑通哪些环节很多人搜「基于javaEE原生Servletjsp的在线购物商城项目代码数据库文档说明」真正想要的不是一份能交差的压缩包而是一条能自己复现的链路用 Servlet 接请求、用 JSP 渲染页面、用数据库存商品和订单最后能本地跑起来、能改、能讲清楚每一层在干什么。这个方向的价值在于它把 Java Web 的三大件——请求处理、会话管理、数据持久化——压在一个真实业务里比单独背 API 有用得多。它适合两类人一是刚学完 Servlet/JSP、想找个完整业务练手的同学二是需要快速搭一个可演示商城原型、又不想引入 Spring 全家桶的开发者。原生 Servlet 的好处是没有框架黑盒请求从doGet/doPost进来到 JDBC 落库每一步都能打断点看。代价是模板代码多、事务和连接池要自己管。这篇就按「先立住结构再动手复现最后讲坑」的顺序把这条链路拆开讲透。2. 先定架构再写代码原生 ServletJSP 商城的分层与选型2.1 为什么不用框架以及三层结构怎么切原生 Servlet 做商城最容易翻车的地方不是某个 API 不会用而是没有分层把 JDBC 代码直接写进 JSP 或者 Servlet 里改一个字段要动五个文件。常见做法是切三层Servlet 只负责收参数、调服务、转发页面Service 负责业务规则比如下单要扣库存、算总价DAO 负责和数据库打交道。JSP 只做展示不写业务逻辑。这样切的好处是后面你要把 JSP 换成别的模板、把 MySQL 换成别的库改动面是可控的。实体类Entity单独放一个包字段和数据库表列一一对应DAO 里用PreparedStatement拼 SQL。包结构我一般这样组织com.shop.entity 实体类和表对应 com.shop.dao DAO 接口 实现 com.shop.service 业务逻辑 com.shop.servlet 控制器 com.shop.util 数据库连接、工具类JSP 放在WEB-INF/views下不能直接被浏览器访问必须经过 Servlet 转发这样权限校验才有地方做。这是原生方案里一个容易被忽略但很关键的设计点。2.2 数据库表怎么设计五张核心表够用商城的最小可用模型五张表就能撑起来用户表、商品表、分类表、订单表、订单明细表。字段不用多但要留对。下面是我常用的建表脚本MySQL 8 可以直接跑-- 用户表 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, -- 存加密后的值别存明文 phone VARCHAR(20), address VARCHAR(200), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 商品表 CREATE TABLE t_product ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, category_id INT, image VARCHAR(200), status TINYINT DEFAULT 1 -- 1 上架 0 下架 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 订单表 CREATE TABLE t_order ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, total DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 0, -- 0 待付款 1 已付款 2 已发货 create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 订单明细 CREATE TABLE t_order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, product_id INT NOT NULL, price DECIMAL(10,2) NOT NULL, -- 下单时的价格快照 quantity INT NOT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有两个参数值得说。price用DECIMAL(10,2)而不是FLOAT因为金额用浮点会出现0.10.2那种精度问题对账时是血泪经验。订单明细里存price快照是因为商品价格会变订单必须记录下单那一刻的价格否则历史订单金额会跟着商品改价而变这是设计上的后悔药。2.3 数据库连接怎么管别每次请求都新建连接新手最常见的写法是在每个 DAO 方法里DriverManager.getConnection()请求一多数据库连接就被打满。正确做法是用连接池。原生项目里可以引 Druid 或 HikariCP也可以自己写一个简单的池。用 HikariCP 的配置大概是这样// DBUtil.java public class DBUtil { private static HikariDataSource ds; static { HikariConfig cfg new HikariConfig(); cfg.setJdbcUrl(jdbc:mysql://localhost:3306/shop?useSSLfalseserverTimezoneAsia/Shanghai); cfg.setUsername(root); cfg.setPassword(your_password); cfg.setMaximumPoolSize(10); // 按并发量调课设 10 够用 cfg.setMinimumIdle(2); cfg.setConnectionTimeout(30000); // 拿不到连接最多等 30s ds new HikariDataSource(cfg); } public static Connection getConnection() throws SQLException { return ds.getConnection(); } }maximumPoolSize不是越大越好数据库本身有最大连接数限制池开太大反而会把库拖垮。课设场景 10 到 20 足够。serverTimezone一定要设否则 MySQL 8 驱动会报时区错误这是很多人第一次连库就卡住的地方。DAO 里用完连接记得在finally里 close用 try-with-resources 更稳。3. 把请求跑通Servlet 控制器与 JSP 页面的最小闭环3.1 一个商品列表接口从请求到渲染的完整链路先跑通最简单的读链路浏览器访问商品列表Servlet 查库把数据塞进 request转发给 JSP 渲染。Servlet 写法WebServlet(/product/list) public class ProductListServlet extends HttpServlet { private ProductService productService new ProductServiceImpl(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String categoryId req.getParameter(categoryId); ListProduct list productService.listByCategory(categoryId); req.setAttribute(products, list); // 数据放进 request 域 req.getRequestDispatcher(/WEB-INF/views/product_list.jsp) .forward(req, resp); // 转发URL 不变 } }forward和redirect的区别要记牢forward 是服务器内部跳转request 域里的数据能带到 JSPredirect 是让浏览器重新发一次请求request 数据会丢。列表页用 forward下单成功后跳订单页用 redirect避免刷新重复提交。JSP 端用 JSTL 遍历别用脚本片段% %写 Java 代码% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table c:forEach items${products} varp tr td${p.name}/td td${p.price}/td td a href${pageContext.request.contextPath}/cart/add?id${p.id}加入购物车/a /td /tr /c:forEach /table${pageContext.request.contextPath}是拿项目上下文路径部署到 Tomcat 时如果 context 不是根路径写死/cart/add会 404这个坑几乎每个人都踩过。3.2 购物车和登录态Session 怎么用才不出错购物车放 Session 里最省事结构用MapInteger, CartItemkey 是商品 id。加购逻辑protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { int productId Integer.parseInt(req.getParameter(id)); HttpSession session req.getSession(); MapInteger, CartItem cart (MapInteger, CartItem) session.getAttribute(cart); if (cart null) { cart new HashMap(); session.setAttribute(cart, cart); } Product p productService.getById(productId); CartItem item cart.get(productId); if (item null) { cart.put(productId, new CartItem(p, 1)); } else { item.setQuantity(item.getQuantity() 1); // 已有则数量加一 } resp.sendRedirect(req.getContextPath() /cart/list); }登录态用session.getAttribute(user)判断没登录就跳登录页。这里要注意 Session 超时默认 30 分钟可以在web.xml里配session-configsession-timeout60/session-timeout/session-config。用户登录后要调session.invalidate()再新建防止会话固定攻击这是安全上的基本操作。3.3 下单事务库存扣减和订单写入必须一起成功下单是唯一必须用事务的地方。扣库存和写订单要么都成功要么都回滚。Service 层写法public void createOrder(int userId, MapInteger, CartItem cart) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关掉自动提交 int orderId orderDao.insert(conn, userId, total); for (CartItem item : cart.values()) { int rows productDao.reduceStock(conn, item.getProduct().getId(), item.getQuantity()); if (rows 0) { // 库存不足扣减影响行数为 0 throw new RuntimeException(库存不足); } orderItemDao.insert(conn, orderId, item); } conn.commit(); // 全部成功才提交 } catch (Exception e) { if (conn ! null) try { conn.rollback(); } catch (SQLException ex) {} throw new RuntimeException(e); } finally { if (conn ! null) try { conn.setAutoCommit(true); conn.close(); } catch (SQLException ex) {} } }关键在reduceStock的 SQL 要带条件UPDATE t_product SET stock stock - ? WHERE id ? AND stock ?。用影响行数判断是否扣成功比先查再扣更安全能防并发超卖。事务里所有 DAO 方法必须共用同一个 Connection不能各自去连接池拿否则事务根本不生效这是最隐蔽的坑。4. 避坑与排查原生 ServletJSP 商城最常见的 5 个翻车点4.1 中文乱码POST 和 GET 处理方式不一样现象表单提交的中文商品名进库变成问号或者页面显示乱码。原因Tomcat 默认用 ISO-8859-1 解码请求体JSP 页面没声明编码。解决POST 请求在 Servlet 第一行加req.setCharacterEncoding(UTF-8)GET 请求在 Tomcat 的server.xml里给 Connector 加URIEncodingUTF-8JSP 页面顶部写% page contentTypetext/html;charsetUTF-8 %数据库连接串加characterEncodingutf8。四处都对了才彻底不乱码。4.2 刷新页面重复下单现象下单成功后按 F5又生成一笔订单。原因下单用 forward浏览器地址还停在下单接口上刷新就重发。解决下单成功后用resp.sendRedirect()跳到订单列表页让浏览器地址变成 GET 页面刷新就不会重复提交。这是 PRG 模式Post-Redirect-Get下单、注册、支付都要这么处理。4.3 连接泄漏导致跑一会儿就卡死现象项目跑十几分钟后再点就没反应重启才好。原因DAO 里某个分支抛异常close()没执行连接被占住池里连接耗尽。解决所有 JDBC 资源用 try-with-resources或者把 close 放 finally。排查时可以在连接池配置里打开泄漏检测HikariCP 设setLeakDetectionThreshold(60000)超过 60 秒没归还就打印堆栈能直接定位到哪个方法漏了。4.4 JSP 里写业务代码导致改不动现象一个 JSP 里几百行% %查库、判断、循环全在里面改个字段要翻半天。原因图省事把逻辑写进页面。解决JSP 只留展示用 JSTL 和 EL 表达式数据由 Servlet 通过 request 传进来。判断登录态这种逻辑放过滤器Filter里统一处理别在每个 JSP 顶部复制粘贴。4.5 图片上传路径写死导致换机器就失效现象本地上传的商品图能显示部署到另一台机器全裂。原因上传目录写成了D:/upload这种绝对路径或者存进了项目编译输出目录重新部署就被清掉。解决上传目录配在项目外的固定位置通过web.xml的 context-param 或配置文件读取存库只存相对文件名展示时拼访问路径。图片坐标定位这类需求前端用 CSS 定位即可别把坐标存进数据库。5. 让项目从「能跑」到「能讲」验证与进阶技巧课设项目最怕的是「能跑但说不清」。答辩或面试时别人问一句「你的下单怎么防超卖」答不上来就露馅。所以最后这一步是把关键链路自己验证一遍并准备好能讲清楚的细节。先做并发验证。开两个浏览器同时买最后一件库存看会不会都下单成功。如果都成功说明扣库存的 SQL 没带stock ?条件或者事务没生效。正确结果是只有一个成功另一个提示库存不足。这个测试用 JMeter 或者简单写个多线程脚本都能做是检验事务和并发控制是否到位的最直接手段。再验证会话和权限。不登录直接访问/order/list应该被过滤器拦到登录页而不是报 500。把 Session 里的 user 手动清掉再操作看会不会空指针。这些边界情况自己先走一遍比等别人发现强。进阶方向有两个。一是把 JDBC 换成 MyBatis体会一下 DAO 层模板代码是怎么被消掉的但前提是你先用原生 JDBC 写过一遍否则不知道 MyBatis 帮你做了什么。二是给列表页加分页用LIMIT offset, size配合COUNT(*)查总数这是真实商城的必备功能也是很好的加分项。-- 分页查询第 page 页每页 size 条 SELECT * FROM t_product WHERE status 1 ORDER BY id DESC LIMIT #{offset}, #{size}; -- offset (page - 1) * size参数上注意offset从 0 开始页码从 1 开始转换时别算错。数据量大时深分页会慢可以用「上一页最大 id」的方式优化但课设数据量用 LIMIT 足够。我自己做这类项目最大的习惯是每写完一个功能先自己用错误输入怼一遍——空参数、超长字符串、负数 id、不存在的商品看系统是优雅提示还是直接抛异常堆栈。能扛住这些的才敢说这个商城是「完整」的。原生 Servlet 项目没有框架兜底所有边界都得自己守这既是它的麻烦也是它最能练人的地方。希望帮到你。本文还有配套的精品资源点击获取
返回列表