ARTICLE DETAIL

资讯详情

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

JavaWeb购物车实战:Servlet+Session+JDBC完整实现

JavaWeb购物车实战:Servlet+Session+JDBC完整实现 简介使用Java与Servlet技术实现的简易购物车系统项目包面向Java Web初学者或需要课程设计的读者。项目完整演示了从商品展示页面、点击“添加到购物车”超链接到Servlet接收请求并记录商品信息和选购数量再跳转到另一个Servlet用表格显示购物车内容的完整流程有助于理解请求跳转、数据传递和页面响应的基本思路。压缩包大小仅为491KB共25个文件。其中3个Java源文件对应两个Servlet的核心逻辑8张商品图片提供展示素材1个HTML页面承担商品展示其余为IDE工程配置文件如project、classpath、XML等导入开发环境后即可运行。目前已有638人学习/下载适合用来快速上手Servlet的典型用法。项目中的第一个Servlet专门处理“添加到购物车”动作第二个Servlet负责生成购物车表格视图代码分工清晰同时保留了标准的Web目录结构便于观察Tomcat项目部署方式。对正在练习Java Web、希望掌握Servlet基本流程或寻找简单课程设计范例的读者这是一份可以直接参考的小型工程。1. 从「加购按钮没反应」到能跑的购物车Servlet 方案到底解决什么问题产品经理把一个加购按钮丢给你时你以为是写条 insert 语句。真正用 javaWebservlet 实现一次购物车系统你会发现最难的从来不是 SQL而是状态用户加进购物车的商品放哪、刷新之后还在不在、两个用户同时登录怎么不串数据。Servlet 方案没有 Spring 那些花哨装备它把 HTTP 请求、Session、JDBC 这几样基本功全部摊在你面前——这也是为什么网上搜 javaweb项目完整案例mysql一半教程还是用它讲基础。自己从 IDEA 里建目录、配 Tomcat 到跑通加购你会把请求生命周期彻底搞明白。这套代码适合三类人刚学完 JavaWeb 想动手的新手维护老项目的同学以及需要快速出原型的小团队。2. 项目骨架与请求流转把 Servlet 当作购物车的“前台接待”2.1 为什么这个场景适合 ServletSpring 之外的务实选择购物车系统的本质是一系列 HTTP 请求对同一份数据做修改加购、改数量、删除、清空。用 Servlet 实现时每一次操作就是一个 Servlet 方法请求参数就是你要传的商品编号和数量而购物车本身放在 Session 里请求结束后数据仍然活着。这个模型直观到不太可能有第二种理解方式。为什么不直接上 Spring MVC核心原因有三点第一Spring 的 DispatcherServlet、HandlerMapping、视图解析器本身是一层抽象新手很难分清到底是哪个组件处理了你的 /cart?actionadd第二Servlet 项目部署就是丢一个 war 包进 Tomcat连类加载器冲突都要少很多第三如果目标是搞懂 JavaWeb 基础Servlet 里手写一遍请求分发回头看 Spring MVC 的 RequestMapping 就只是一层语法糖。不是说 Spring 不好而是第一版购物车没必要为一个 Controller 注解引入全家桶。课堂笔记里反复强调的一句话做项目时特别有用Servlet 负责“接住请求、理解参数、调用业务、决定下一步跳转”它不该负责拼 SQL 或者算总价。把这句话当纪律代码自然分层。2.2 目录结构与分层视图、控制、数据三层各管各的事一个标准的 Maven webapp 项目结构长这样我建议你第一版就照这个路子建cart-demo/ ├── pom.xml └── src/main/ ├── java/com/example/cart/ │ ├── controller/CartServlet.java │ ├── dao/ProductDao.java │ ├── model/Cart.java │ ├── model/CartItem.java │ ├── model/Product.java │ └── util/JdbcUtils.java ├── resources/db.properties └── webapp/ ├── WEB-INF/web.xml ├── product_list.jsp └── cart.jspcontroller 下放 Servletmodel 下放购物车和商品实体dao 下放数据库访问util 放连接池工具。JSP 只做展示从 request 或 session 里取数据不写 Java 业务代码。很多老项目翻车就翻在 JSP 里写了一大坨 % %页面一多根本维护不了。注意 WEB-INF 目录下的 JSP 不能通过浏览器直接输入地址访问必须由 Servlet forward 过去这正好把访问入口收敛到控制器。如果你不想写 web.xmlServlet 3.0 之后的 WebServlet 注解也能用但 web.xml 里配 session 超时和欢迎页更方便我两个都会保留。2.3 从 URL 到 Servlet 的映射一种面向请求的接口设计购物车有四个核心动作我用一个 CartServlet 加 action 参数来分发比建四个 Servlet 更省事也更符合小项目的直觉WebServlet(/cart) public class CartServlet extends HttpServlet { Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action req.getParameter(action); // 根据 action 分发add 加购、update 改数量、remove 移除、clear 清空 switch (action null ? : action) { case add: this.add(req, resp); break; case update: this.update(req, resp); break; case remove: this.remove(req, resp); break; case clear: this.clear(req, resp); break; default: this.list(req, resp); } } Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 加购和改数量用 POST 更语义化这里统一转给 doGet 处理 doGet(req, resp); } }逻辑说明action 参数相当于一个轻量级路由四种操作共享同一个 Servlet 入口权限控制、日志记录都只写一处。doPost 转 doGet 是我自己的习惯——购物车的操作都不涉及文件上传GET 方便浏览器直接输 URL 调试POST 满足生产环境语义两边都通。参数说明action 取值 add、update、remove、clear对应页面里的链接或表单提交productId 作为第二个参数跟在后面例如 /cart?actionremoveproductId3。用 switch 而不是一堆 if后续加 checkout 动作时只需要补一个 case 即可。2.4 在 IDEA 里把项目跑起来Tomcat 配置与依赖引入这一步是新手最容易卡住的地方也是搜索“idea运行javaweb项目配置”时问得最多的场景。新建项目时直接选 Maven 骨架pom.xml 里放这几组依赖dependencies !-- Servlet APITomcat 运行时才提供打包时用 provided 避免冲突 -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency !-- JSTL页面里渲染购物车循环和格式化金额 -- dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency !-- MySQL 驱动用 8.x 对应 com.mysql.cj.jdbc.Driver -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency /dependencies参数说明javax.servlet-api 的 version 必须跟 Tomcat 匹配——Tomcat 9 对应 4.0.xTomcat 10 之后包名改成 jakarta.servlet你要是用 Tomcat 10 还引 javax 的依赖启动直接报 ClassNotFound。scope 写 provided 是为了防止打 war 包时把 servlet-api 塞进去跟 Tomcat 自带的冲突。提示Tomcat 9 是目前 JavaWeb 老项目最稳妥的选择如果你的环境只能用 Tomcat 10记得把代码里的 import javax.servlet 全部换成 jakarta.servlet。IDEA 配置步骤右上角 Add Configuration选 Tomcat Server - Local在 Deployment 页签点加号选 Artifact把项目的 war exploded 部署上去Application context 建议填 /这样访问路径是 localhost:8080/product_list.jsp不用带项目名。改完 JSP 不生效时把 On Update Action 设为 Update classes and resources同时勾选 Hot Swap。这一步不做的话你会发现改个前端页面要重启 Tomcat排查一个问题来回浪费五分钟。3. 购物车数据模型与核心操作Session 才是购物车的真正载体3.1 购物车放哪里Session、Cookie、数据库三选一把购物车放进 Session 是我第一优先推荐的做法背后的取舍看这张表存储位置生命周期容量实现难度典型问题HttpSession用户会话结束或超时几 KB 到几十 KB低setAttribute 即可服务器重启丢失Cookie浏览器端可设长过期4KB 上限只能存文本中要序列化用户清浏览器就没了数据库表持久化跨会话无限制高要建表加 DAO每次操作都要读写磁盘购物车的特点是数据量小、变更频繁、只归属当前用户Session 天生就是干这个的。Cookie 存不下几十个商品数据库存购物车适合跨设备同步这种需求——第一版别碰。等以后用户体系做得完整了再把 Session 里的购物车在结算时落库这是后话。3.2 CartItem 与 Cart 两个类把“买了什么”变成代码购物车不是一张表它是个内存对象。先写购物车条目public class CartItem { private String productId; // 商品编号 private String productName; // 名称冗余存下来避免每次渲染购物车都查库 private double price; // 单价 private int quantity; // 数量 public double getSubtotal() { // 小计由价格和数量实时算出来 return price * quantity; } // getter / setter 省略IDE 自动生成即可 }逻辑说明productName 和 price 在加购那一刻从商品表查出并存入 Session。好处是渲染购物车页面时只读 Session不用再查数据库代价是管理员改了商品名称后已在购物车里的条目还显示旧名称。对这种矛盾我的取舍是第一版保性能名称不一致的窗口期很短能接受。public class Cart { private MapString, CartItem items new HashMap(); public void add(CartItem item) { CartItem exist items.get(item.getProductId()); if (exist ! null) { // 已加过同款商品累加数量 exist.setQuantity(exist.getQuantity() item.getQuantity()); } else { items.put(item.getProductId(), item); } } public void update(String productId, int quantity) { CartItem item items.get(productId); if (item ! null) { item.setQuantity(quantity); } } public void remove(String productId) { items.remove(productId); } public void clear() { items.clear(); } public ListCartItem getItems() { return new ArrayList(items.values()); } public double getTotal() { double total 0; for (CartItem item : items.values()) { total item.getSubtotal(); } return total; } }逻辑说明用 Map 而不是 List是因为购物车核心操作是按商品 ID 查条目、改数量Map 的查找复杂度是 O(1)。add 方法内部已经处理了“同款商品重复加购就累加”的逻辑Servlet 层不需要再 if 判断。total 每次实时计算避免维护一个容易过期的手工累加字段。getTtems 返回副本防止外部直接改动内部 Map。3.3 加入购物车的核心 Servletadd、update、remove、clear 四条分支有了 Cart 类Servlet 层就很薄了。看 add 方法private void add(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String productId req.getParameter(productId); if (productId null || productId.isEmpty()) { resp.sendError(400, 缺少商品ID); return; } // 商品信息从数据库查出来封装成 CartItem ProductDao productDao new ProductDao(); Product product productDao.findById(Integer.parseInt(productId)); if (product null) { resp.sendError(404, 商品不存在); return; } // 从 Session 取购物车首次访问时新建 HttpSession session req.getSession(); Cart cart (Cart) session.getAttribute(cart); if (cart null) { cart new Cart(); } // 数量上限不能超过库存暂时先做一道校验 CartItem item new CartItem( String.valueOf(product.getId()), product.getName(), product.getPrice(), 1); cart.add(item); session.setAttribute(cart, cart); // 重定向到购物车列表页避免刷新时重复提交 resp.sendRedirect(req.getContextPath() /cart?actionlist); }逻辑说明整个方法的核心就三步——查商品、改购物车、写回 Session。req.getSession() 默认会创建新会话所以第一次访问就拿到了可用的 Session。最后必须用 sendRedirect 而不是 forward这关系到第五章要讲的防重复提交问题这里先记住“加购之后重定向”这个固定动作。参数说明productId 从请求参数来理论上前端应该传商品编号如果你用自增主键这里 parseInt 失败时要 catch NumberFormatException否则 500。item 的 quantity 先写死 1实际项目里可以支持在商品列表页选数量。update、remove、clear 三个方法结构一致区别只在调用 Cart 的对应方法这里不重复贴。3.4 JSP 页面用 JSTL 渲染购物车避免 scriptlet 写一脸购物车页面的核心就是一个表格用 JSTL 循环渲染。记得在 JSP 顶部引入标签库% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % 和 % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt %。c:forEach varitem items${sessionScope.cart.items} tr td${item.productName}/td tdfmt:formatNumber value${item.price} typecurrency//td td input typenumber value${item.quantity} onchangelocation.href${pageContext.request.contextPath}/cart?actionupdateproductId${item.productId}quantity this.value/ /td tdfmt:formatNumber value${item.subtotal} typecurrency//td td a href${pageContext.request.contextPath}/cart?actionremoveproductId${item.productId}删除/a /td /tr /c:forEach逻辑说明sessionScope.cart 是 EL 内置作用域查找到的 Session 里的 cart 对象注意别写成 requestScope——购物车在 Session 里用 requestScope 永远是 null。onchange 触发时直接拼 URL 跳转属于最简洁的做法但生产环境建议改成提交表单走 POST避免商品 ID 和数量暴露在地址栏。参数说明pageContext.request.contextPath 是必备的上下文前缀不然在 context 不为 / 时链接会 404。fmt:formatNumber 的 typecurrency 会按服务器 Locale 输出货币格式只是展示层处理不会改变 item.price 的值。购物车是空的情况要在循环外判断c:if test${empty sessionScope.cart.items} 提示“购物车还是空的”。4. 商品数据从 MySQL 来建表、连接池与列表页落地4.1 商品表与用户表的建表 SQL字段与索引的一次到位购物车系统再简单也得有商品来源。这里给一份够用的建表脚本重点是字段类型和两个约束CREATE TABLE product ( id INT NOT NULL AUTO_INCREMENT COMMENT 商品ID, name VARCHAR(100) NOT NULL COMMENT 商品名称, price DECIMAL(10,2) NOT NULL COMMENT 售价, stock INT NOT NULL DEFAULT 0 COMMENT 库存, image VARCHAR(255) DEFAULT NULL COMMENT 图片URL, description VARCHAR(500) DEFAULT NULL COMMENT 商品描述, PRIMARY KEY (id), KEY idx_stock (stock) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品表; INSERT INTO product(name, price, stock, description) VALUES (螺丝刀套装, 49.90, 100, 家用维修必备), (无线鼠标, 89.00, 50, 办公静音款), (USB-C 数据线, 19.90, 200, 支持快充);建表说明price 必须用 DECIMAL(10,2) 而不是 double金额用二进制浮点计算会产生 0.10.2 不等于 0.3 的经典问题购物车里的总价在 checkout 时要传给订单系统精度必须心里有数。stock 加普通索引是因为商品列表页可能有“只看有货”的筛选实际并发量上来后这个索引可能被弃用但第一版无伤大雅。用户表第一版可以不建等做登录后再补核心是 product 这站表的字段能撑起商品列表和加购。注意表的默认字符集用 utf8mb4如果用了 utf8商品名里出现 emoji 就会入库失败——有过这个坑的人现在都学乖了。4.2 数据库连接池配置db.properties 与 Druid 初装每次请求都新建 Connection 太浪费了连接池是标准做法。第一步先建 db.propertiesjdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/cart_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 jdbc.usernameroot jdbc.password123456 # 连接池初始连接数小项目给 5 就够 druid.initialSize5 # 最大活跃连接数20 对应单机小站点 druid.maxActive20参数说明url 里的 serverTimezoneAsia/Shanghai 是 MySQL 8.x 必须带的不然 JDBC 驱动拿不到默认时区会直接报错characterEncodingutf8 保证读出商品名不乱码。jdbc.password 里的 123456 只是本地开发值提交到 git 时要把这个文件加进 ignore不然就是裸奔。然后写一个 JdbcUtils 工具类public class JdbcUtils { private static DruidDataSource dataSource; static { try { Properties props new Properties(); props.load(JdbcUtils.class.getClassLoader() .getResourceAsStream(db.properties)); dataSource (DruidDataSource) DruidDataSourceFactory .createDataSource(props); } catch (Exception e) { throw new RuntimeException(数据库连接池初始化失败, e); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }逻辑说明static 代码块在类加载时执行一次读配置、建连接池整个应用只有一个数据源实例。DruidDataSourceFactory 会自动识别以 druid. 开头的属性并映射到连接池参数。getConnection 从池里借连接用完在 DAO 里通过 try-with-resources 归还连接池会复用物理连接。提示如果不想引入 Druid 依赖初期用 DriverManager.getConnection 也能跑通但正式环境一定要换连接池否则并发一高数据库连接数就会被打满。4.3 DAO 层写查询从商品列表页到加购按钮的完整链路ProductDao 只需要两个方法一个是查全部商品用于列表页一个是按 ID 查单个商品用于加购校验public class ProductDao { public ListProduct listAll() { String sql SELECT id, name, price, stock FROM product; ListProduct result new ArrayList(); try (Connection conn JdbcUtils.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Product p new Product(rs.getInt(id), rs.getString(name), rs.getDouble(price), rs.getInt(stock)); result.add(p); } } catch (SQLException e) { throw new RuntimeException(查询商品列表失败, e); } return result; } public Product findById(int id) { String sql SELECT id, name, price, stock FROM product WHERE id ?; try (Connection conn JdbcUtils.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, id); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { return new Product(rs.getInt(id), rs.getString(name), rs.getDouble(price), rs.getInt(stock)); } } } catch (SQLException e) { throw new RuntimeException(查询商品失败, id id, e); } return null; } }逻辑说明findById 里的占位符 ? 通过 ps.setInt 绑定这比字符串拼接 SQL 安全得多——拼接就会被人传一个 1 OR 11 直接拖库。try-with-resources 语法保证 Connection、PreparedStatement、ResultSet 全部自动关闭连接会归还到连接池而不是泄露。每个方法都包 try-catch 转成 RuntimeException让 Servlet 层不用处理受检异常日志里也能看到具体出错的 SQL。商品列表页的 Servlet 很简单list 方法里 productDao.listAll() 拿到列表request.setAttribute 放进 request 域然后 forward 到 product_list.jsp。注意这里用 forward 是合理的因为列表查询不涉及数据变更用户刷新只是重新查一次。4.4 库存校验与数量上限加购容易超卖不能有第一版购物车最容易漏的就是库存校验。常见的翻车现场是用户把数量改成 999前端不管Servlet 不管最后下单时才发现库存不够。第二版加购时至少要做三道检查第一道是商品必须存在findById 返回 null 时直接提示“商品不存在”第二道是库存大于 0stock 0 时返回“已售罄”第三道是购物车中的累计数量不能超过库存。第三道在 Cart.add 里不好判断因为 Cart 不知道库存是多少所以正确的位置是 Servlet 层加购前把商品查出来算出“已在购物车里的数量 本次要加的数量”跟库存比int current 0; CartItem exist cart.getItems().stream() .filter(i - i.getProductId().equals(String.valueOf(product.getId()))) .findFirst().orElse(null); if (exist ! null) { current exist.getQuantity(); } if (current 1 product.getStock()) { // 库存不足提示后返回商品列表 req.setAttribute(error, 库存不足); req.getRequestDispatcher(/product_list.jsp).forward(req, resp); return; }这里只做“加购校验”真正的“扣减库存”要放在提交订单时配合数据库 update 语句的原子操作否则高并发下两个用户同时买最后一个商品就会出现超卖。第一版购物车系统不涉及下单但留这个钩子后面扩展时才不用重构加购逻辑。5. 购物车系统的 5 个经典翻车点现象、原因、处理5.1 重定向之后购物车空了request 域和 session 域搞混现象加购成功跳转到购物车页表格里却是空的偶尔刷新一下又看到商品了。原因很多新手把购物车对象放进了 request.setAttribute(cart, cart)然后用了 forward 跳转。forward 是一次请求内部的转发request 域还能读到但如果你在这个 Servlet 里先 sendRedirect第二次请求的 request 对象是全新的第一次设置进去的值已经销毁。解决购物车这类跨请求状态必须放 Session。检查自己的代码如果是 request.setAttribute全部改成 session.setAttribute。这是购物车开发里最经典的一次“请求生命周期”教学事故我早年也被坑过一整晚。5.2 中文商品名乱码POST 和 GET 要分开治现象从 MySQL 查出来的商品名称在页面上显示成“é”开头的一串问号。原因乱码的根源有三个只治其中一个不够——MySQL 连接串的 characterEncoding、Tomcat 处理请求的编码、JSP 响应的 ContentType 编码。解决写一个编码过滤器处理 POST 请求体WebFilter(/*) public class EncodingFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding(UTF-8); response.setCharacterEncoding(UTF-8); chain.doFilter(request, response); } }GET 请求的查询参数编码由 Tomcat 连接器决定Tomcat 9 默认 URIEncoding 就是 UTF-8如果是 Tomcat 8需要在 conf/server.xml 的 Connector 上加 URIEncodingUTF-8。最后确认 db.properties 里的 url 已经带 characterEncodingutf8。三处一起做乱码基本绝迹。5.3 刷新页面商品被重复添加Post/Redirect/Get 模式现象用户点一次“加入购物车”刷新购物车页面商品数量从 1 变成 2再刷新变 3。原因加购请求如果是 POSTServlet 处理完直接用 forward 跳转到购物车页浏览器的当前 URL 还是那个 POST 地址用户按 F5浏览器重新提交一次 POST购物车就多一件。解决用 Post/Redirect/Get 模式数据变更请求处理完成后返回一个 302 重定向让浏览器 GET 到干净的购物车地址resp.sendRedirect(req.getContextPath() /cart?actionlist);sendRedirect 走的是 302 响应浏览器地址栏变成新的 GET URLF5 刷的是这个 GET 地址不再重复提交。这是购物车里性价比最高的防重复提交手段。5.4 Session 掉了购物车跟着没了超时与持久化取舍现象用户下午加了几件商品隔一小时回来点结算购物车空了。原因Tomcat 默认 Session 超时是 30 分钟超时后 Session 被销毁购物车跟着没了。解决修改 web.xml 的超时时间session-config session-timeout60/session-timeout /session-config单位是分钟60 分钟对购物场景比较合适。但超时只是兜底真正的解法是购物车持久化——要么结算前把购物车写入数据库要么引入 Redis 以 SessionId 为 key 存购物车 JSON。第一版项目我建议做“结算前落库”因为它不需要引入新中间件在点击结算时读 Session 购物车、事务性写入订单表即可。Session 本身不适合存需要长期保留的数据重启 Tomcat 也照样丢。5.5 IDEA 里 404 和 ClassNotFound部署目录里的血泪现象Tomcat 启动没有报错但浏览器访问产品列表页 404或者运行时抛 ClassNotFoundException: com.mysql.cj.jdbc.Driver。原因IDEA 默认的 war exploded 部署时依赖 jar 没有自动拷贝到 WEB-INF/lib 目录classes 可能也没编译输出。解决打开 File - Project Structure - Artifacts选中当前项目的 war exploded 配置右侧 Available Elements 面板里把 tomcat 和 Maven 依赖的 jar 全部右键选择 Put into WEB-INF/lib然后 Build - Rebuild Project重启 Tomcat。检查 classes 是否包含 WEB-INF/classes 路径没有就手动加一个 Source 目录映射。用 Maven 的话直接在 pom 里加 maven-war-plugin 后跑 mvn clean package生成的 war 自带全部依赖部署时选这个 war 而不是 exploded就省了很多手工折腾。6. 把这套购物车拿去验收验证清单、度量标准与下一步6.1 手动验收用例一页表覆盖正常与异常用例操作步骤预期结果正常加购商品列表页点“加入购物车”跳转购物车页出现该商品数量 1重复加购同一商品再加一次购物车不出现两行数量变为 2修改数量输入 5 后回车或触发 onchange小计和总价按 5 件重新计算删除条目点“删除”该商品从购物车消失总价更新清空购物车点“清空”表格消失显示空购物车提示库存不足加购超过库存提示库存不足不写入购物车Session 过期等待超时后刷新购物车页购物车为空但页面不报错每完成一项就在后面打个勾。手动测试通过后才开始写自动化测试。6.2 把 DAO 层单独用 JUnit 测不必等 TomcatCart 类是纯内存对象不依赖数据库和容器这是最容易写单测的地方Test void testAddAndAccumulate() { Cart cart new Cart(); cart.add(new CartItem(1, 螺丝刀, 9.9, 2)); cart.add(new CartItem(1, 螺丝刀, 9.9, 3)); assertEquals(5, cart.getItems().get(0).getQuantity()); assertEquals(49.5, cart.getTotal(), 0.001); }double 比较必须带第三个参数 delta不然浮点误差会让断言失败。ProductDao 的测试需要数据库我会用 test 库跑真实查询断言 findById 能查到插进去的那条记录这是验证连接池配置最快的方式。6.3 再往前走一步从 Session 到 Redis从 Servlet 到 Spring MVC购物车跑通后下一步可以按这个顺序演进第一加用户表与登录拦截器购物车变成“登录后才能用”第二把 Session 里的购物车在结算时写入订单表加事务第三等并发来了把 Session 换成 RedisRedis 存购物车 JSON 可以做到多台 Tomcat 共享登录状态第四如果团队已经用 Spring把 CartServlet 的 WebServlet 换成 ControllerCart 和 ProductDao 原封不动地搬进去。你会发现核心的 Cart.class 和 DAO 几乎不用改这就是分层的回报。我自己第一次做购物车时把购物车对象放在 request 里被重定向丢数据折磨了一整晚后来养成了一个习惯动手前先画一遍“这一次请求从浏览器到 Servlet 再到 JSP 的生命周期”数据落在哪个作用域、会不会跨请求画完再写代码。这个习惯后来排查别人的老项目也特别好用。希望帮到你。本文还有配套的精品资源点击获取
返回列表