网上花店JavaWeb项目实战:Servlet、会话管理与订单事务全解析
简介一份面向JavaWeb初学者的“网上花店”页面设计素材包适合正在完成课程设计、期末作业或想了解商品展示类网页布局的读者。资源以鲜花商城展示为核心整合HTML页面结构、CSS样式与JPG/PNG/GIF图片素材可直观参考首页结构、商品卡片、轮播位、图片展示等静态前端实现并对应涉及Servlet、JSP、MySQL数据库交互等JavaWeb后端知识点便于从页面出发继续扩展登录、下单、库存查询等功能。压缩包共7个文件包含html页面、css样式表和多张图片素材整体仅232KB结构简洁清晰下载后用浏览器打开即可查看页面效果也方便二次修改。对于初学网页设计者可重点观察CSS如何控制花店页面的整体配色与布局对于JavaWeb学习者也可以此为基础补充Servlet与数据库代码还原一个完整的在线花店演示项目。已有2487人学习适合快速获取网页设计参考、完成课程项目演示或进行JavaWeb综合练手的读者。1. 从分类页到购物车一个网上花店项目到底在考什么任何一个做过电商类 JavaWeb 项目的人都清楚网上花店这类题目绝不是把几张商品图片排进 HTML 页面那么简单。它要求你把「网页设计」的表层和「JavaWeb」的底层同时打通前端要能呈现花束、价格、库存、轮播图后端要能支撑这些数据的存取、购物车加减、订单生成、库存扣减。一个花店系统本质上是用户、商品、订单、购物车这四张表之间的数据流转而这一流转在面试和课程设计里最常被追问的恰恰是 Servlet 生命周期、HttpSession 状态管理、JDBC 事务边界这类最基础也最容易出错的地方。很多 5 年经验以上的后端工程师回看这类项目会觉得「不就是 CRUD 吗」。但换个角度想如果你的 CRUD 没有考虑并发下单时的库存超卖没有处理会话过期后购物车数据的持久化没有在删除分类时校验关联商品——那这个网上花店项目交出去第一轮代码评审就会被挂掉。本文直接以「网上花店」为蓝本把网页设计层、控制器层、服务层、数据层完整串一遍重点落在购物车、下单事务、图片上传和部署排错这几个硬骨头上。新手能照着把项目跑起来老手能在这里看到状态管理、事务隔离和资源释放的边界问题。2. 先看清楚 JavaWeb 花店项目的技术选型Servlet 还是 Spring 系2.1 从标题反推需求边界决定要不要引入框架网上花店网页设计要求「能看、能买、能查订单」但如果平台限定是 JavaWeb那你首先要确认它允许用的是哪种技术组合。常见做法是两种纯 Servlet JSP JDBC或者 Servlet MyBatis JSP。前者的优点是依赖极少适合课程设计和需要手写 SQL 的考核场景后者的好处是 SQL 与 Java 代码分离扩展性好一些但引入 mybatis.jar 和对应版本时容易踩坑。一个经验是能用纯 Servlet 讲清楚原理就别一开始上 Spring。Spring Boot 会让项目在二十分钟内跑起来但它屏蔽了 DispatcherServlet、过滤器链、Web 容器初始化这些 JavaWeb 最核心的概念。而网上花店项目在最常见的答辩里被问的第一个问题往往是「请求从 URL 到页面的完整路径」。你如果只答得出 RequestMapping而说不出 Servlet 的 init、service、destroy印象分会打折扣。因此本文按「Servlet JSP JDBC MySQL」为主线后面每节给出的代码都基于这一组合不让框架替你藏掉细节。2.2 web.xml 里的注册顺序决定了你排错的方向纯 Servlet 项目里最先要配置的就是 web.xml或者基于 Servlet 3.0 以上的注解方式。这个文件不仅声明 Servlet 映射还控制过滤器和监听器的执行顺序这是网上花店项目里最容易被忽略的关键点。一个典型的过滤器顺序配置如下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 display-nameOnlineFlowerShop/display-name filter filter-nameEncodingFilter/filter-name filter-classcom.flower.filter.EncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameEncodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping filter filter-nameLoginFilter/filter-name filter-classcom.flower.filter.LoginFilter/filter-class /filter filter-mapping filter-nameLoginFilter/filter-name url-pattern/order/*/url-pattern url-pattern/cart/*/url-pattern /filter-mapping servlet servlet-nameFlowerServlet/servlet-name servlet-classcom.flower.servlet.FlowerServlet/servlet-class load-on-startup1/load-on-startup /servlet servlet-mapping servlet-nameFlowerServlet/servlet-name url-pattern/flower/*/url-pattern /servlet-mapping /web-app以上配置传递了两个关键信号EncodingFilter 对全站生效置于所有 Filter 之前否则 POST 里的中文花名一定会乱码LoginFilter 只拦截 /order 和 /cart 两个路径前缀这也意味着商品浏览、用户注册不能被拦截否则游客无法访问首页。load-on-startup 设为 1 表示容器启动时即初始化 FlowerServlet对需要预加载分类列表的场景来说这个数字能减少第一次访问的等待时间。实际踩过的坑是如果你使用的是 Tomcat 10 及以上javax.servlet 包已经迁移到 jakarta.servlet网上大量 JavaWeb 教程代码直接以 javax 编译启动时会出现 ClassNotFoundException。此时要么换 Tomcat 9要么把代码里所有 javax.servlet 前缀统一替换为 jakarta.servlet。2.3 JSP 页面只做渲染别把业务逻辑堆进 scriptlet网上花店网页设计的「网页」部分大多数初学者会直接在 JSP 里写 JDBC。这样确实能在十分钟内看到数据但后续加购物车时会发现JSP 页面中维护一个会话级别的购物车非常混乱——你在页面里 new 了 Connection 和 Statement却没有在页面结束时关闭它们Tomcat 的连接池很快会耗尽。正确的分工是JSP 只负责用 EL 表达式和 JSTL 标签渲染数据Servlet 负责收集参数、调用 Service、转发或重定向DAO 类负责 JDBC 操作。以花店首页要展示「热销花束」为例Servlet 端只需要做三件事WebServlet(/flower/list) public class FlowerListServlet extends HttpServlet { private FlowerService flowerService new FlowerService(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String categoryId req.getParameter(categoryId); ListFlower flowers flowerService.listByCategory(categoryId); req.setAttribute(flowers, flowers); req.getRequestDispatcher(/WEB-INF/jsp/flower_list.jsp) .forward(req, resp); } }这段代码核心在于 forward 与 sendRedirect 的选择。forward 是请求内部转发URL 不变request 域里的 flowers 可以传给 JSPsendRedirect 是重定向浏览器会发起第二次 GET 请求request 域会被清空。因此列表查询用 forward而登录成功、加入购物车、下单成功后则建议用 sendRedirect这样能防止用户刷新页面导致表单重复提交。至于 req.setCharacterEncoding(UTF-8) 必须写在读取任何参数之前否则中文参数仍然是 ISO-8859-1 解码。3. 花店数据库建模与 JDBC 访问层的设计要点3.1 五张表解决核心业务字段设计要方便 SQL 联查网上花店的业务闭环是「用户 → 浏览花材 → 加入购物车 → 生成订单 → 扣减库存」。用一个用户表、一个分类表、一个花材表、一个购物车表、一个订单主表和订单明细表就能覆盖。不要为每张表堆几十个字段电商里常见的价格、库存、销量、状态这几个字段必须有但冗余字段越少越好。推荐的表结构设计如下CREATE TABLE flower ( id INT PRIMARY KEY AUTO_INCREMENT, category_id INT NOT NULL, name VARCHAR(64) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, sales INT NOT NULL DEFAULT 0, cover_image VARCHAR(128), description TEXT, status TINYINT DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE cart_item ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, flower_id INT NOT NULL, quantity INT NOT NULL DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_flower (user_id, flower_id) ); CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, user_id INT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, status TINYINT NOT NULL DEFAULT 0, receive_name VARCHAR(32), receive_phone VARCHAR(20), receive_address VARCHAR(128), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, flower_id INT NOT NULL, flower_name VARCHAR(64), price DECIMAL(10,2) NOT NULL, quantity INT NOT NULL );这份建表语句里有三个细节值得注意。cart_item 设置了联合唯一键 uk_user_flower这保证了同一用户对同一花材只存在一条购物车记录添加购物车时用 INSERT ... ON DUPLICATE KEY UPDATE quantity quantity 1 即可合并数量。orders 表单独存储了 order_no 作为业务单号不直接暴露自增主键这是为了避免订单号被猜解和遍历。order_item 冗余了 flower_name 和下单时的 price因为商品名称和价格以后可能调整历史订单必须保留下单时的快照这是网上花店项目在数据一致性上最容易被忽略的设计。3.2 DAO 里的 Connection 关闭顺序决定了数据库会不会报 Too many connectionsJavaWeb 项目如果使用 C3P0 或 Druid 连接池DAO 层最容易出的问题不是 SQL 写错而是连接没有被归还。初学阶段最常见的写法是用完 Connection 后调用 conn.close()但 close 方法在连接池场景下是归还连接而非真正关闭物理连接。因此如果某段代码在 conn 创建后、执行 SQL 前抛了异常连接就永远无法归还。建议在每个 DAO 方法里用 try-with-resources 统一管理资源public Flower findById(int id) { String sql SELECT id, category_id, name, price, stock, sales, cover_image, description FROM flower WHERE id ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, id); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { return mapRow(rs); } } } catch (SQLException e) { throw new RuntimeException(查询花材失败, id id, e); } return null; }这段代码里的 try-with-resources 会自动按声明逆序关闭资源先 ResultSet再 PreparedStatement最后 Connection。PreparedStatement 使用预编译的 ? 占位符这既避免了拼接 SQL 导致的花材名中英文引号造成的语法错误也在一定程度上防御了 SQL 注入。查询结果通过 mapRow 方法手动映射为 Flower 对象避免引入反射框架在课程设计环境里带来的兼容性问题。提示如果你发现 MySQL 服务端持续报 Too many connections先用 SHOW PROCESSLIST 查看当前连接数再检查是否所有 DAO 方法都用了 try-with-resources。很多情况下不是配置不够而是某个异常路径漏掉了连接归还。3.3 分类页和搜索页的 SQL 要练到能直接背出来网上花店网页设计里花材列表页最常见的搜索条件是分类、价格区间和销量排序。这三个条件的组合查询是面试官最喜欢让候选人手写的一种 SQL也是项目答辩时最容易出彩的一个环节。SELECT id, name, price, stock, sales, cover_image FROM flower WHERE status 1 if testcategoryId ! null and categoryId ! AND category_id #{categoryId} /if if testminPrice ! null AND price gt; #{minPrice} /if if testmaxPrice ! null AND price lt; #{maxPrice} /if ORDER BY choose when testorderBy salessales DESC/when when testorderBy price_ascprice ASC/when otherwisecreate_time DESC/otherwise /choose LIMIT #{offset}, #{pageSize}上述 SQL 用 MyBatis 的动态 SQL 语法来写如果你用的是纯 JDBC则需要用 StringBuilder 按条件拼接并通过 List 收集参数。注意一个细节LIMIT 的 offset 应由后端从页码计算得出公式是 (page - 1) * pageSize。不要让前端传 offset否则用户可以跳过某一页或把 offset 改成负数分页参数的服务端校验是网上花店项目里常见的加分点。排序字段不能由前端随意传入后再拼进 ORDER BY因为这样会造成 SQL 注入。推荐的防御方式是后端定义白名单映射比如把 orderBy 的合法值限制为 sales、price_asc、price_desc不在白名单内的默认按 create_time DESC。这一步虽然写起来多几行 if/switch但在答辩时容易讲清楚安全设计思路。4. 购物车与订单闭环从 Session 存储到数据库持久化的完整路径4.1 购物车为什么不能用 Session 存这决定了项目能不能算合格网上花店这类 JavaWeb 项目初学教程里最常见的做法是把购物车放进 HttpSession用一个 MapInteger, Integer 记录花材 ID 和数量。这样做的好处是实现简单不需要额外的表设计但它的致命缺陷是用户一旦关掉浏览器或会话过期整个购物车内容全部丢失。对于任何一个真正的电商场景购物车都是需要跨会话保留的。因此我建议的路线是未登录用户可以把购物车临时放在 Session 中一旦用户登录把 Session 中的购物车数据合并进 cart_item 表后续所有购物车操作都走数据库Session 中只保留 userId。合并逻辑至少要处理两种冲突Session 中存在的花材 ID 在数据库中可能已存在记录需要做数量累加Session 中的花材可能已经被下架或删除需要过滤。数据库版购物车添加操作的 SQL 如下INSERT INTO cart_item (user_id, flower_id, quantity, create_time) VALUES (?, ?, ?, NOW()) ON DUPLICATE KEY UPDATE quantity quantity VALUES(quantity);这段语句依赖 3.1 节里建立的 uk_user_flower 唯一索引。新增数据时如果用户和花材的组合已经存在就自动把两边的数量相加省去了先查再插的两次网络往返。VALUES(quantity) 在 MySQL 8.0.20 及以上版本里被弃用更推荐的写法是使用别名语法例如 INSERT INTO ... VALUES (...) AS new ON DUPLICATE KEY UPDATE quantity quantity new.quantity在 5.7 环境里两种写法都能用这一点需要根据你的 MySQL 版本选择。4.2 下单事务里必须同时做四件事少一件都是 bug从购物车生成订单是网上花店项目里最值得扩写的一串业务逻辑。它包含写入 orders 主表获取订单 ID、根据订单 ID 写入多行 order_item、扣减 flower 表对应花材的库存、清空对应用户的购物车记录。这四步中任何一步失败都不能让另外三步生效否则会出现「有订单但没扣库存」或「库存扣了但购物车还留着」的数据不一致。扣库存的 SQL 有一个面试必考的关键点UPDATE flower SET stock stock - ?, sales sales ? WHERE id ? AND stock ?;WHERE 条件里的 stock ? 是防止库存扣成负数的唯一可靠手段。你的应用层 if 判断不可靠因为多线程并发时多个请求可能同时读到相同的库存快照两个请求都通过 if 判断后执行 UPDATE就会导致超卖。把这个条件写进 SQL 后如果影响行数为 0说明库存不足此时抛出库存不足异常并回滚整个事务。以下是下单方法的完整骨架public Order createOrder(int userId, String receiverName, String receiverPhone, String receiverAddress, ListCartItem items) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交开启事务 conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); String orderNo generateOrderNo(); // 1. 插入订单主表 double total 0; for (CartItem item : items) { total item.getPrice() * item.getQuantity(); } insertOrder(conn, orderNo, userId, total, receiverName, receiverPhone, receiverAddress); // 2. 获取订单自增 ID插入订单明细 int orderId getGeneratedOrderId(conn); for (CartItem item : items) { insertOrderItem(conn, orderId, item); } // 3. 扣减库存逐条校验 for (CartItem item : items) { int rows deductStock(conn, item.getFlowerId(), item.getQuantity()); if (rows 0) { throw new BusinessException(库存不足: item.getFlowerName()); } } // 4. 清空购物车 clearCart(conn, userId, items); conn.commit(); return buildOrderResult(orderNo, total); } catch (Exception e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ignored) {} } throw new RuntimeException(e); } finally { if (conn ! null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException ignored) {} } } }这段代码把事务边界拉得清清楚楚setAutoCommit(false) 之后的 so-called 自动提交全部失效直到显式 commit 或 rollback。事务隔离级别设置为 READ_COMMITTED是为了允许订单列表查询读到其他事务已提交的数据同时避免脏读。扣库存逐条执行并检查返回行数目的是精确定位是哪一个花材出了问题而不是笼统抛出一个 SQLException。需要注意的关键问题是这里必须保证整个方法只使用一个 Connection。如果你在 DAO 方法内部自行获取连接那事务就根本无法跨 DAO 生效。常见做法是把 Connection 作为参数向下传递或者用 ThreadLocal 在当前线程内持有连接。对这个项目来说把 conn 传进 DAO 方法最直白也最容易讲给评审听。4.3 订单状态机JavaWeb 里最简单也最需要的状态设计网上花店订单状态不需要太复杂常规四态就够了待支付0、已支付1、已发货2、已完成3再加一个取消态4。但一定要把状态流转的合法性检查写进代码否则会出现「已发货的订单被用户取消」的严重 bug。订单状态更新的 SQL 要带上旧状态条件UPDATE orders SET status ? WHERE id ? AND user_id ? AND status ?;例如用户取消订单只能从待支付状态进入取消状态所以 SQL 的最后一个 ? 填 0。如果影响行数为 0说明该订单当前状态不允许取消前端也要做相应提示。状态更新还牵扯到库存回补如果取消的订单之前扣过库存要执行一次反向的 UPDATE flower SET stock stock ?如果只是从已支付流转到已发货则不动库存。这个「哪些操作要回补库存哪些不用」的判断可以单独抽成一个方法用 switch 或者 if 把操作类型和当前状态组合起来做校验。5. 网页设计侧的花店首页轮播图、花材卡片和图片上传的落地做法5.1 静态资源路径与 JSP 页面分离别把图片塞进数据库网上花店网页设计这块最容易在答辩时被挑战的点是图片存储方案。很多初学项目把图片转成 Base64 字符串存进 TINYTEXT 字段这样做的结果就是页面上每个img标签会携带几十 KB 的冗余字符数据库行数一多性能就急剧下降。正确的做法是图片以文件形式存储在服务器的 upload 目录下数据库里只存相对路径例如 /upload/rose_001.jpg。上传图片的 Servlet 在 web.xml 或注解中需要显式配置 multipart 支持WebServlet(name UploadServlet, urlPatterns {/admin/flower/upload}, multipartConfig MultipartConfig(maxFileSize 5 * 1024 * 1024, maxRequestSize 20 * 1024 * 1024, fileSizeThreshold 1024 * 1024)) public class UploadServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Part filePart req.getPart(coverImage); String submittedFileName filePart.getSubmittedFileName(); String ext submittedFileName.substring(submittedFileName.lastIndexOf(.)); String newFileName System.currentTimeMillis() _ UUID.randomUUID() ext; String uploadDir getServletContext().getRealPath(/upload); filePart.write(uploadDir File.separator newFileName); resp.getWriter().write(/upload/ newFileName); } }代码里为什么必须重新生成文件名而不是沿用原始文件名因为两个用户同时上传同名文件会互相覆盖。使用时间戳加 UUID 的组合可以基本保证文件名在分布式环境下也不会冲突。getRealPath(/upload) 得到的是应用部署目录下的物理路径注意如果使用 Tomcat重新部署后会清空这些上传文件所以更稳妥的做法是把 upload 目录配置在 Tomcat 的虚拟主机外部但这超出了本标题的范围不展开。maxFileSize 限制单张图片 5MB这对花束照片是足够的上限太大会拖慢页面加载。5.2 首页热销区用限流 SQL 和缩略图双管齐下网上花店首页通常会放一个「本周热销」区域按销量降序取前 8 个商品。这个查询比较简单需要的是 SQL 层面的性能考量在没有大流量压力时直接执行 SELECT ... ORDER BY sales DESC LIMIT 8 就能完成任务。但为了在数据量变大后不至于第一屏加载太慢推荐两个优化手段。第一给 sales 字段建索引让 ORDER BY sales DESC 走索引扫描避免 filesort。第二不要再把原图直接展示在列表页列表页的 img src 指向压缩后的缩略图。可以用 Java 的 ImageIO 做一次等比缩放把宽度限制在 400px 内BufferedImage original ImageIO.read(new File(sourcePath)); int newWidth 400; int newHeight (int) (original.getHeight() * (400.0 / original.getWidth())); BufferedImage thumbnail new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB); thumbnail.getGraphics().drawImage(original, 0, 0, newWidth, newHeight, null); ImageIO.write(thumbnail, jpg, new File(thumbnailPath));这段代码把原图读取为 BufferedImage 后按宽度 400px 等比计算目标高度再绘制到新的 BufferedImage 上。注意 TYPE_INT_RGB 会把透明背景填充为黑色如果花束图片是 PNG 且需要透明底要改成 TYPE_INT_ARGB。这也是一个典型的 JavaWeb 项目里网页设计环节「细节到位」的加分操作。5.3 用 JSTL 与 EL 渲染花材卡片避免 JSP 里出现 Java 片段网上花店网页设计的前端部分推荐花材卡片用 c:forEach 循环渲染而不是在 JSP 里写 for 循环加 out.println。一个清爽的列表页片段像这样c:forEach items${flowerList} varflower div classflower-card img src${flower.coverImage} alt${flower.name} / h3a href${pageContext.request.contextPath}/flower/detail?id${flower.id} ${flower.name} /a/h3 p classprice${flower.price}/p button onclickaddToCart(${flower.id}, 1)加入购物车/button /div /c:forEach这里所有变量都通过 EL 表达式从 request/session 作用域读取页面上没有任何 Java 代码。addToCart 按钮绑定的 onclick 调用一个 JavaScript 函数该函数以异步方式把花材 ID 和数量发送到 /cart/add成功后局部刷新按钮文案为「已加入」。价格用 ${flower.price} 直接展示但如果要做千分位格式化或促销价划线可以使用 JSTL 的 fmt:formatNumber它需要引入 fmt.tld这是后面你自己扩展时要注意的依赖。6. 部署排错与两个值得做的进阶验证技巧6.1 Tomcat 部署到 80 端口的两分钟配置本地开发时 Tomcat 默认端口是 8080要把它改成 80 直接访问不用加端口号编辑 conf/server.xml 里的 Connector 节点Connector port80 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 /修改前确认 80 端口没有被 IIS 或 nginx 占用。在 Linux 环境里非 root 用户绑定 80 端口会提示权限不足需要执行 setcap 或使用 authbind 赋权Windows 开发机上直接修改再重启 Tomcat 通常就够了。项目打成 WAR 包放进 webapps 目录启动后访问根路径前要确认项目路径名例如 flower_shop.war 默认映射到 /flower_shop如果想以 / 直接访问把 WAR 包改名为 ROOT.war 再部署。6.2 用启动日志验证项目是否注册成功启动后的第一件事不是打开浏览器而是看 Tomcat 日志里这两行信息Deployment of web application archive [flower_shop.war] has finished INFO [main] org.apache.catalina.startup.HostConfig.deployWAR如果日志直接报 Error 并附带 NoClassDefFoundError优先检查 lib 目录下的 mysql-connector-java.jar 版本是否与 MySQL 8.0 对应如果报 java.sql.SQLException: Access denied for user则先测 MySQL 命令行能否用配置里的账号密码登录再检查 jdbcUrl 里是否漏了 useUnicodetruecharacterEncodingUTF-8 导致中文写入乱码。6.3 商品管理页加一个「查看商品详情」的浏览量自增逻辑这是一个很适合收尾的验证技巧给花材详情页添加浏览量字段每次访问详情时执行一次自增 UPDATE。它既能让页面有真实感又能验证 DAO 层的 update 方法是否在所有分支都能正确释放连接。UPDATE flower SET views views 1 WHERE id ?;关键点在于区分「有真实用户访问」和「爬虫刷量」。如果不想让刷新一次就加一次可以结合 Session 做去重在 Session 里维护一个 visitedFlowerIds 的 Set只有当某个花材 ID 不在 Set 里时才执行自增并加入 Set。这样有效防止了同一个人反复刷新详情页导致浏览量虚高也顺带练习了 HttpSession 中对象属性的修改和回写是 JavaWeb 项目里一个很自然的进阶练习。最后在确认上述所有模块都跑通后建议你回到建表语句把 cart_item、orders 这两张表上的外键约束补上。许多网上花店教程为了省事会省掉 ON DELETE CASCADE这会导致分类或商品删除时出现孤儿数据。补充外键并开启事务后你的网上花店项目从数据层到展示层就形成了一个真正能自圆其说的闭环了。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →