ARTICLE DETAIL

资讯详情

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

JavaEE网上商城实战:Servlet+JSP+JDBC跑通订单链路

JavaEE网上商城实战:Servlet+JSP+JDBC跑通订单链路 简介这份资源是面向计算机专业学生与Java初学者的一份毕业设计配套文档主题为基于JavaEE的网上商城管理系统适合作为课程设计、毕业设计选题参考或Java Web入门练手项目。系统采用Java语言与B/S架构以IDEA为开发工具数据库选用开源MySQL整体分为前台与后台两大模块前台涵盖登录注册、首页、我的订单、我的消息、我的地址、我的信息、售后维权与通知公告等后台管理员端则包含个人中心、首页轮播管理、通知公告管理、客户管理、商品管理、售后维权管理及统计中心等功能结构完整、模块划分清晰。资源包内共1个doc文档约2.31MB内容围绕选题背景、需求分析、系统架构与数据库设计、编码实现及测试验证展开可帮助读者理解商城类系统的整体设计思路与功能组织方式。目前已有1674人学习下载适合需要参考完整设计文档、梳理开发流程或进行功能扩展的读者。1. 从零搭一套 JavaEE 网上商城为什么我劝你先跑通一条订单链路很多人搜「基于javaEE网上商城管理系统」脑子里想的是把商品、订单、用户、后台全做齐结果一上手就卡在环境上——Tomcat 起不来、JDBC 连不上、JSP 页面 404。我带过几届实习生十个里有八个是这么翻车的。这套系统的本质是用 Servlet JSP JDBC 把「浏览商品 → 加购物车 → 下单 → 后台改库存」这条链路跑通它解决的是中小型电商后台从零到一的问题适合课程设计、毕设也适合想补 JavaWeb 全链路手感的后端新人。别贪多先把一条订单链路跑穿剩下的模块都是复制粘贴加改字段。这一章先讲清楚它由哪些层组成、为什么这么分层后面几章再落到建表、写 Servlet、配环境、排错。2. 分层架构与建表先想清楚数据怎么流再动手写代码2.1 为什么是 Servlet JSP JDBC 这套组合JavaEE 网上商城管理系统最常见的分层是四层表现层用 JSP 渲染页面控制层用 Servlet 接请求业务层用 Service 类做校验和事务持久层用 JDBC 或轻量封装访问数据库。有人会问为什么不上 Spring Boot答案很现实——课程设计和老项目里Servlet 是绕不开的基本功理解了 request/response 的生命周期后面学任何框架都是降维打击。数据流向是这样的浏览器发请求 → web.xml 或注解映射到某个 Servlet → Servlet 调 Service → Service 调 DAO → DAO 用 JDBC 查 MySQL → 结果逐层返回 → Servlet 转发到 JSP 渲染。这条链路里Servlet 只负责收参数和跳转业务判断放 ServiceSQL 只出现在 DAO。我见过太多人把 SQL 写在 JSP 里后期改一个字段要翻十几个文件血泪经验。选型上JDBC 用原生还是用 DBUtils如果只是课程设计原生 JDBC 加一个简单的 BaseDao 封装就够了依赖少、好调试。DBUtils 能省掉结果集映射的重复代码但多一个 jar 包。我的建议是先用原生 JDBC 把增删改查写一遍理解 Connection、PreparedStatement、ResultSet 的关闭顺序再考虑封装。2.2 商城核心表结构与建表 SQL一个能跑通订单链路的商城最少需要五张表用户表、商品表、分类表、订单表、订单明细表。购物车可以放 Session 里也可以单独建表课程设计阶段放 Session 更省事。-- 用户表存登录信息和角色 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, -- 登录名唯一 password VARCHAR(64) NOT NULL, -- 存 MD5 后的密码别存明文 role TINYINT DEFAULT 0, -- 0 普通用户1 管理员 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, -- 用 DECIMAL 不用 FLOAT避免精度丢失 stock INT NOT NULL DEFAULT 0, category_id INT, img_path VARCHAR(200), -- 图片相对路径 status TINYINT DEFAULT 1 -- 1 上架0 下架 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 订单表订单号用时间戳随机数生成 CREATE TABLE t_order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, user_id INT NOT NULL, total_price 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, quantity INT NOT NULL, price DECIMAL(10,2) NOT NULL -- 下单时的价格快照别关联查现价 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;建表有三个参数必须说清楚。第一价格字段一律用 DECIMAL(10,2)FLOAT 在累加时会飘订单金额对不上就是玄学 bug。第二订单明细里的 price 是下单那一刻的价格快照不能靠 product_id 去关联查现价否则商品调价后历史订单金额全变。第三字符集统一 utf8mb4不然商品名里的特殊符号会变问号。外键约束在课程设计里可以不加靠 Service 层保证一致性加了反而在删除时容易报错。2.3 用 Maven 组织项目目录目录结构决定了你后期找文件的速度。标准做法是按层分包而不是按功能分包。src/main/java/com/shop/ ├── dao/ # 数据访问每个表一个 Dao 类 │ ├── UserDao.java │ └── ProductDao.java ├── service/ # 业务逻辑事务控制在这一层 │ └── OrderService.java ├── servlet/ # 控制层按模块分 │ ├── ProductServlet.java │ └── OrderServlet.java ├── entity/ # 实体类和表字段一一对应 │ └── Product.java └── util/ # 工具类 └── DBUtil.java src/main/webapp/ ├── WEB-INF/web.xml ├── css/ js/ images/ └── index.jsppom.xml 里只需要三个核心依赖servlet-apiscope 设 provided因为 Tomcat 自带、mysql-connector-java、jstl。别一股脑把 Spring 全家桶塞进去课程设计用不上还会拖慢启动。3. 从商品列表到下单Servlet 与 JDBC 的最小可跑实现3.1 数据库连接工具类 DBUtil连接管理是 JDBC 最容易翻车的地方。每次 new Connection 不关跑几十次请求数据库连接池就满了。正确做法是写一个工具类统一获取和关闭。public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/shop?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PWD your_password; static { try { Class.forName(com.mysql.cj.jdbc.Driver); // 新版驱动类名带 cj } catch (ClassNotFoundException e) { throw new RuntimeException(驱动加载失败, e); } } public static Connection getConn() throws SQLException { return DriverManager.getConnection(URL, USER, PWD); } // 关闭顺序ResultSet - Statement - Connection反了会报错 public static void close(Connection c, Statement s, ResultSet r) { try { if (r ! null) r.close(); } catch (SQLException ignored) {} try { if (s ! null) s.close(); } catch (SQLException ignored) {} try { if (c ! null) c.close(); } catch (SQLException ignored) {} } }URL 里三个参数必须带useSSLfalse 关掉 SSL 警告serverTimezone 设成 Asia/Shanghai 否则时间差 8 小时characterEncodingutf8 防止中文乱码。驱动类名 com.mysql.cj.jdbc.Driver 是 MySQL 8.x 的写法5.x 是 com.mysql.jdbc.Driver写错直接 ClassNotFoundException。关闭顺序不能反先关 ResultSet 再关 Statement 最后关 Connection这是 JDBC 的硬规矩。3.2 商品列表查询与分页商品列表是用户进商城看到的第一屏必须支持分页否则商品一多页面直接卡死。public ListProduct findByPage(int page, int size) { ListProduct list new ArrayList(); String sql SELECT id,name,price,stock,img_path FROM t_product WHERE status1 ORDER BY id DESC LIMIT ?,?; try (Connection c DBUtil.getConn(); PreparedStatement ps c.prepareStatement(sql)) { ps.setInt(1, (page - 1) * size); // 起始偏移量 ps.setInt(2, size); // 每页条数 try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Product p new Product(); p.setId(rs.getInt(id)); p.setName(rs.getString(name)); p.setPrice(rs.getBigDecimal(price)); // 金额用 BigDecimal p.setStock(rs.getInt(stock)); p.setImgPath(rs.getString(img_path)); list.add(p); } } } catch (SQLException e) { e.printStackTrace(); } return list; }分页公式是 (page-1)*sizepage 从 1 开始。用 LIMIT 而不是在 Java 里 subList数据量大时性能差几十倍。金额字段用 getBigDecimal 而不是 getDouble和建表时的 DECIMAL 对应。try-with-resources 能自动关资源比手动 close 更省心但要注意它只关括号里声明的对象。3.3 下单 Servlet事务与库存扣减下单是整个系统最容易出问题的地方核心是「扣库存」和「写订单」必须在一个事务里要么都成功要么都回滚。protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session req.getSession(); User user (User) session.getAttribute(loginUser); if (user null) { resp.sendRedirect(login.jsp); return; } MapInteger,Integer cart (MapInteger,Integer) session.getAttribute(cart); Connection conn null; try { conn DBUtil.getConn(); conn.setAutoCommit(false); // 开启事务关掉自动提交 BigDecimal total BigDecimal.ZERO; for (Map.EntryInteger,Integer e : cart.entrySet()) { int pid e.getKey(), qty e.getValue(); // 扣库存带 stock? 条件防止超卖 String sql UPDATE t_product SET stockstock-? WHERE id? AND stock?; try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, qty); ps.setInt(2, pid); ps.setInt(3, qty); if (ps.executeUpdate() 0) { throw new RuntimeException(库存不足商品ID pid); } } // 查价格累加总额略 } // 写订单主表和明细表略 conn.commit(); // 全部成功才提交 session.removeAttribute(cart); resp.sendRedirect(orderList.jsp); } catch (Exception ex) { try { if (conn ! null) conn.rollback(); } catch (SQLException ignored) {} req.setAttribute(msg, 下单失败 ex.getMessage()); req.getRequestDispatcher(cart.jsp).forward(req, resp); } finally { try { if (conn ! null) conn.close(); } catch (SQLException ignored) {} } }关键点有三个。第一conn.setAutoCommit(false) 开启事务所有操作共用同一个 Connection不能每个 DAO 各拿各的连接那样事务根本不生效。第二扣库存的 SQL 带 stock? 条件靠数据库的行锁保证不超卖比先查再改安全得多。第三任何一步抛异常都要 rollback否则会出现库存扣了订单没写的情况。这套写法在并发不高时够用高并发下要换成乐观锁或消息队列但课程设计阶段没必要。4. 环境配置与联调VSCode 跑 JavaEE 的踩坑清单4.1 VSCode 配置 JavaEE 语言环境现在很多人不想装笨重的 Eclipse用 VSCode 配 JavaEE 环境。核心是装三个扩展Extension Pack for Java、Community Server Connectors、Maven for Java。装完后按 CtrlShiftP 输入 Java: Configure Java Runtime确认 JDK 路径指向 1.8 或 11。Tomcat 的配置在 Community Server Connectors 里点 SERVERS 面板的加号选 Tomcat 安装目录再把项目 add 进去。这里有个坑VSCode 默认不会把 src/main/webapp 识别为 web 根目录需要在项目根目录建 .vscode/settings.json{ java.configuration.updateBuildConfiguration: automatic, java.compile.nullAnalysis.mode: automatic, maven.executable.path: mvn, files.exclude: { **/target: true } }如果启动后访问 404先看 Tomcat 的 context path 是不是项目名再看 web.xml 里 servlet 的 url-pattern 有没有写错。VSCode 的 Java 语言服务器偶尔会抽风改完代码不生效就 CtrlShiftP 执行 Java: Clean Java Language Server Workspace 重启一次。4.2 中文乱码的三处源头乱码是 JavaWeb 的经典问题源头有三个逐个排查。第一处是 JSP 页面顶部没写% page contentTypetext/html;charsetUTF-8 %。第二处是 Servlet 里 req.setCharacterEncoding(UTF-8) 没在 getParameter 之前调用POST 请求会乱码。第三处是数据库连接 URL 没带 characterEncodingutf8存进去就是问号。三处都对了还乱码检查 MySQL 的库和表字符集是不是 utf8mb4。GET 请求的中文乱码更麻烦因为参数在 URL 里setCharacterEncoding 管不到。Tomcat 8 以后默认 URIEncoding 就是 UTF-8如果还是乱码去 server.xml 的 Connector 标签加 URIEncodingUTF-8。4.3 用 Postman 单独测 Servlet 接口前后端联调时JSP 页面报错很难定位是 Servlet 的问题还是页面的问题。我的习惯是先用 Postman 直接打 Servlet 的 URL看返回的 JSON 或状态码。比如测商品列表接口POST 到 http://localhost:8080/shop/product?actionlist带上参数看返回内容。这样能把控制层和表现层的问题分开省一半调试时间。5. 避坑与排查五个让我加班到凌晨的坑5.1 现象Tomcat 启动报 ClassNotFoundException: com.mysql.cj.jdbc.Driver原因mysql-connector 的 jar 没放进 WEB-INF/lib或者 Maven 依赖 scope 写成了 provided。Tomcat 只认 WEB-INF/lib 下的 jarMaven 的 provided 依赖不会打进去。解决确认 pom.xml 里 mysql-connector-java 的 scope 是默认的 compile然后 mvn clean package 重新打包或者手动把 jar 复制到 WEB-INF/lib。5.2 现象下单后库存扣了订单表却没数据原因Service 层每个 DAO 方法各自 getConn()事务没生效。JDBC 事务要求所有操作共用同一个 Connection。解决把 Connection 作为参数在 DAO 方法间传递或者用 ThreadLocal 绑定当前线程的连接。下单逻辑里统一用外层传入的 conn。5.3 现象商品价格显示 19.900000000000002原因数据库字段用了 FLOAT 或 DOUBLEJava 里用了 double 接收。浮点数在二进制里无法精确表示小数。解决建表用 DECIMAL(10,2)Java 用 BigDecimal运算用 add、multiply 方法别用 和 *。5.4 现象JSP 页面报「无法在 web.xml 或标注中找到 servlet 映射」原因Servlet 类上的 WebServlet(/product) 注解没生效或者 web.xml 的 metadata-completetrue 导致注解被忽略。解决检查 web.xml 的 version 是不是 3.0 以上metadata-complete 是不是 false。用注解就别在 web.xml 里重复配 servlet两者选一个。5.5 现象VSCode 里改完代码浏览器刷新还是旧页面原因Tomcat 没重新部署或者浏览器缓存了 JSP 编译结果。解决在 SERVERS 面板右键 Tomcat 选 Restart浏览器按 CtrlF5 强制刷新。JSP 改动一般会自动重编译Java 类改动必须重启。6. 进阶技巧用过滤器统一登录校验和字符编码系统做到后期你会发现每个 Servlet 开头都在写「判断登录」和「设置编码」重复代码一大堆。这时候该上 Filter 了。Filter 能在请求到达 Servlet 之前统一处理登录校验和字符编码是最典型的两个场景。WebFilter(/*) // 拦截所有请求 public class AuthFilter implements Filter { // 白名单登录页、登录接口、静态资源不拦截 private static final String[] WHITE {/login.jsp, /login, /css/, /js/, /images/}; public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; request.setCharacterEncoding(UTF-8); // 统一编码省掉每个 Servlet 写一遍 response.setContentType(text/html;charsetUTF-8); String uri request.getRequestURI(); for (String w : WHITE) { if (uri.contains(w)) { chain.doFilter(req, resp); return; } } // 非白名单请求校验登录 if (request.getSession().getAttribute(loginUser) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); // 放行 } }这个 Filter 有两个作用。第一setCharacterEncoding 只写一次所有 POST 请求的中文都不乱码了比在每个 Servlet 里重复写靠谱。第二登录校验集中在一处新增页面不用再复制判断逻辑。白名单用 contains 匹配注意 /login 会匹配到 /login.jsp顺序上把更具体的放前面或者用 equals 精确匹配。Filter 的执行顺序由 web.xml 里的 filter-mapping 顺序决定用注解的话顺序不确定。如果同时有编码过滤器和登录过滤器建议编码过滤器用注解登录过滤器在 web.xml 里配保证编码先执行。最后说个验证方法写完 Filter 后故意在未登录状态访问 /orderList看是否跳转到登录页再用中文商品名搜索看是否乱码。两个都通过说明 Filter 生效了。我一般会在 Filter 里加一行日志打印拦截的 URI排查时一眼就能看出哪些请求被放行、哪些被拦了。这套东西不难但能省下大量重复代码值得花半小时加上。希望帮到你。本文还有配套的精品资源点击获取
返回列表