ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

JavaWeb实战项目:Spring事务与库存扣减的并发安全实现

JavaWeb实战项目:Spring事务与库存扣减的并发安全实现 简介这是一套面向计算机专业本科生的JavaWeb课程设计实战资源聚焦鲜花销售业务场景完整实现用户管理、商品浏览、购物车、订单处理等核心功能适用于期末大作业、课程设计及JavaWeb入门项目练习。资源包共55个文件含31个Java源码涵盖Servlet、DAO、Service及实体类、17张界面截图PNG直观展示登录、首页、后台管理等页面效果、4个JFD配置文件支撑系统运行环境、1个SQL数据库脚本含建表语句与初始化数据及必要说明文档整体6.27MB结构清晰、开箱即用。已有474人学习下载项目经导师指导评审获98分高分代码规范、注释完整配套database.sql可一键导入MySQL目录模块划分明确如Entity、Interface、Realize、Forms等便于理解MVC分层逻辑与前后端交互流程。1. 这不是又一个“Hello World”式JavaWeb demo它真能跑通登录、下单、库存扣减、订单状态流转且数据库事务不丢数据——适合赶期末 deadline 的大三学生直接复现、调通、答辩你手头正压着三门课的期末大作业其中一门是《Java Web 应用开发》老师要求“必须含用户管理、商品展示、购物车、订单生成、后台管理五大模块”DDL 还剩 72 小时。你搜到一堆“JavaWeb鲜花系统源码”点开发现登录页能渲染但输完账号密码就 404数据库建了表但insert into order_detail总报Column order_id cannot be null更绝望的是web.xml里配了Filter却没写init-param导致中文乱码贯穿全站——这根本不是“能跑”而是“能编译但不能走通”。而这份标着“98分”的Flowerstore-master.zip我拆包实测过Tomcat 9.0.83 下启动零报错MySQL 8.0.33 中执行database.sql后管理员账号admin/123456登录即见完整后台用户下单后库存实时扣减、订单状态自动从“待支付”变“已发货”连Realize/Interface/OrderService.java里那个被导师圈出的“事务传播行为优化点”都留了注释。它不是教学玩具是经真实课堂评审验证的闭环业务系统——专为大三学生卡在“功能联调”和“答辩演示”两个生死关卡时提供可立即上手、改字段就能交、改样式就能答辩的硬核底座。2. 从解压到首页渲染五步完成本地环境搭建与基础验证2.1 环境清单与版本锚点为什么必须用 Tomcat 9 MySQL 8 JDK 11这不是“随便装个 Tomcat 就行”的项目。FlowerStore的pom.xml虽未显式声明但lib/目录下 jar 包倒推依赖servlet-api-4.0.1.jar对应 Servlet 4.0 规范Tomcat 8.5 是最低门槛但实测 Tomcat 9.0.83 最稳——因为web.xml中filter配置了encodingFilter其init-param值UTF-8在 Tomcat 9 才被完整支持低版本会静默忽略导致中文乱码。MySQL 方面database.sql中CREATE TABLE user (...) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci;明确指定了utf8mb4_0900_ai_ci排序规则这是 MySQL 8.0.1 之后引入的若用 MySQL 5.7CHARSETutf8mb4可运行但COLLATE会报错或降级为utf8mb4_general_ci导致LIKE模糊查询对 emoji 或生僻字失效。JDK 必须 ≥11因Entity/User.java中使用了var关键字声明局部变量如var roles roleDao.findAll();这是 JDK 10 特性JDK 8 编译直接失败。别跳过这步先确认java -version、mysql --version、catalina.sh version输出再动手。2.2 解压与目录结构速览关键文件在哪哪些能删哪些绝不能动解压Flowerstore-master.zip后核心目录结构如下删减非必要文件后Flowerstore-master/ ├── src/ # Java 源码根目录 │ ├── main/ # 主代码 │ │ └── java/ # com.flowerstore.* 包结构 │ │ └── flowerstore/ │ │ ├── controller/ # Servlet 入口LoginServlet, OrderServlet 等 │ │ ├── dao/ # 数据访问层UserDao, FlowerDao, OrderDao │ │ ├── entity/ # 实体类User, Flower, Order, OrderDetail │ │ ├── factory/ # 工厂类DAOFactory单例模式获取 DAO 实例 │ │ ├── service/ # 业务逻辑UserService, OrderService含事务控制 │ │ └── util/ # 工具类DBUtil连接池、StringUtil字符串处理 │ └── webapp/ # Web 资源 │ ├── WEB-INF/ │ │ ├── web.xml # 核心配置servlet mapping, filter, listener │ │ └── lib/ # 依赖 jarmysql-connector-java-8.0.28.jar, commons-dbcp2-2.9.0.jar 等 │ ├── index.jsp # 首页游客视图 │ ├── login.jsp # 登录页 │ ├── admin/ # 后台管理页面需 admin 权限 │ └── user/ # 用户中心页面 ├── database.sql # 建库建表及初始化数据含 admin/123456 ├── Tools/ # 辅助工具可能含数据库备份脚本或测试数据生成器 └── 文档.txt # 项目说明、功能列表、部署步骤务必先读提示Tools/和Forms/目录在本次部署中可忽略img/是静态图片资源确保其路径在 JSP 中正确引用如img srcimg/rose.jpgRealize/Interface/下的接口定义如OrderService.java是业务契约绝不能删除否则service/impl/中的实现类无法编译。2.3 数据库初始化执行database.sql的三个致命细节别直接双击.sql文件用 Navicat 执行database.sql开头有CREATE DATABASE IF NOT EXISTS flowerstore CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;这意味着必须先创建数据库在 MySQL 命令行中执行CREATE DATABASE flowerstore CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;再USE flowerstore;最后source /path/to/database.sql;。若跳过CREATE DATABASEUSE flowerstore;会报错后续建表全部失败。检查INSERT语句的引号database.sql中部分INSERT使用了中文全角引号‘’如INSERT INTO user VALUES(1,admin,123456,管理员,...);复制粘贴时极易混入。务必用 Notepad 或 VS Code 打开切换到“显示所有字符”模式确认所有单引号是 ASCII 的U0027而非‘U2018或’U2019。否则 MySQL 报ERROR 1064 (42000)。验证外键约束是否生效执行完database.sql后运行SHOW CREATE TABLE order_detail;确认输出中包含CONSTRAINTfk_order_detail_order_idFOREIGN KEY (order_id) REFERENCESorder(id) ON DELETE CASCADE。若缺失说明sql_mode中禁用了STRICT_TRANS_TABLES需在 MySQL 配置中添加sql_mode STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION并重启服务。2.4 Tomcat 部署与启动web.xml里的 Filter 是中文不乱码的唯一钥匙将整个Flowerstore-master/目录不是src/子目录直接复制到 Tomcat 的webapps/目录下重命名为flowerstore保持小写避免 Windows 大小写不敏感导致 Linux 部署失败。启动 Tomcat 前必须修改webapps/flowerstore/WEB-INF/web.xmlfilter filter-nameencodingFilter/filter-name filter-classorg.apache.catalina.filters.SetCharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value !-- 此处必须是 UTF-8不能是 utf-8 或 UTF8 -- /init-param init-param param-nameforceEncoding/param-name param-valuetrue/param-value !-- 强制覆盖 request/response 编码 -- /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern !-- 必须是 /*覆盖所有请求 -- /filter-mapping注意param-valueUTF-8/param-value中的UTF-8必须全大写加连字符这是 Tomcat 内置 Filter 的硬编码识别规则url-pattern/*/url-pattern若写成/或*.jsp则 AJAX 请求或静态资源请求不会被过滤中文依然乱码。启动 Tomcat 后访问http://localhost:8080/flowerstore/若看到鲜花首页含轮播图、分类导航说明部署成功。若首页空白检查 Tomcat 日志logs/catalina.out常见错误是ClassNotFoundException: com.mysql.cj.jdbc.Driver——此时确认webapps/flowerstore/WEB-INF/lib/mysql-connector-java-8.0.28.jar是否存在且未被其他同名 jar 覆盖。2.5 首次登录与权限验证admin 账号背后的三层校验链输入admin/123456登录系统实际执行了三重校验前端校验login.jsp中 JavaScript 检查用户名密码非空防误提交后端校验LoginServlet.java的doPost()方法中String username request.getParameter(username).trim(); // trim() 防空格注入 String password request.getParameter(password); User user userService.login(username, password); // 调用 service 层 if (user ! null 1.equals(user.getStatus())) { // status1 表示启用状态 request.getSession().setAttribute(user, user); // 写入 session response.sendRedirect(admin/index.jsp); // 重定向到后台首页 } else { request.setAttribute(msg, 用户名或密码错误或账号已被禁用); request.getRequestDispatcher(login.jsp).forward(request, response); }后台拦截admin/index.jsp开头有% if(session.getAttribute(user) null || !1.equals(((User)session.getAttribute(user)).getRole())) { response.sendRedirect(../login.jsp); } %强制校验 session 中的 user 对象及其 role 字段role1为管理员。逻辑说明userService.login()最终调用UserDao.login()其 SQL 为SELECT * FROM user WHERE username ? AND password ? AND status 1密码是明文存储database.sql中INSERT INTO user VALUES(...,123456,...)这是课程设计允许的简化但生产环境必须用 BCrypt 加密。status字段用于软删除role字段区分1(admin) 和0(user)这是权限控制的最小可行方案。3. 订单生成与库存扣减看懂OrderService里的事务边界与并发安全3.1 下单流程全景图从 JSP 表单到数据库四张表联动用户在user/cart.jsp点击“去结算”触发OrderServlet的doPost()方法。整个流程涉及四个实体表的原子性操作表名作用关键字段事务中操作order订单主表id,user_id,total_amount,status,create_timeINSERT新订单order_detail订单明细id,order_id,flower_id,quantity,priceINSERT多条明细foreach cart itemflower商品表id,name,stock,priceUPDATE stock stock - ? WHERE id ?扣减库存user用户表id,balanceUPDATE balance balance - ? WHERE id ?若支持余额支付OrderService.createOrder()方法用Transactional基于commons-dbcp2连接池的TransactionManager实现包裹整个流程。事务起点在OrderService层而非OrderServlet这是保证 ACID 的关键——若在 Servlet 层开启事务HTTP 请求超时会导致连接池泄漏。3.2OrderService.createOrder()源码精读为什么synchronized不是解药Realize/Interface/OrderService.java定义了接口service/impl/OrderServiceImpl.java是实现。核心方法public boolean createOrder(Order order, ListOrderDetail details) { Connection conn null; try { conn DBUtil.getConnection(); // 从连接池获取连接 conn.setAutoCommit(false); // 关闭自动提交开启事务 // 1. 插入订单主表 long orderId orderDao.insert(conn, order); if (orderId 0) throw new RuntimeException(订单主表插入失败); // 2. 批量插入订单明细 for (OrderDetail detail : details) { detail.setOrderId(orderId); // 绑定外键 if (orderDetailDao.insert(conn, detail) 0) throw new RuntimeException(订单明细插入失败); } // 3. 扣减库存关键 for (OrderDetail detail : details) { int affected flowerDao.updateStock(conn, detail.getFlowerId(), detail.getQuantity()); if (affected ! 1) throw new RuntimeException(库存扣减失败商品ID detail.getFlowerId()); } conn.commit(); // 提交事务 return true; } catch (Exception e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { DBUtil.closeConnection(conn); // 归还连接池 } }参数说明DBUtil.getConnection()返回的是commons-dbcp2管理的物理连接conn.setAutoCommit(false)将其置于手动事务模式flowerDao.updateStock()的 SQL 是UPDATE flower SET stock stock - ? WHERE id ? AND stock ?WHERE 子句中的stock ?是防止超卖的核心——若库存不足affected返回 0事务回滚。这里没有用synchronized因为它是 JVM 级锁对数据库行锁无效真正的并发安全靠数据库的SELECT ... FOR UPDATE或此处的UPDATE ... WHERE stock ?实现。3.3 库存扣减的“幻读”陷阱为什么UPDATE ... WHERE stock ?比SELECT stock更可靠新手常写两步// 错误示范先查再更新存在时间窗口 int currentStock flowerDao.getStock(conn, flowerId); if (currentStock quantity) { flowerDao.updateStock(conn, flowerId, quantity); // UPDATE SET stock stock - ? }这会导致幻读线程 A 查到stock10线程 B 同时查到stock10A 扣减后stock5B 仍按105判断成功再扣减得stock0但实际应拒绝 B 的请求。而UPDATE flower SET stock stock - 5 WHERE id 1 AND stock 5是原子操作数据库引擎在执行时会对id1的行加写锁B 的 UPDATE 会被阻塞直到 A 提交或回滚锁的粒度是行级且条件判断与更新在同一 SQL 中完成彻底规避竞态。database.sql中flower表的stock字段设为INT NOT NULL DEFAULT 0正是为此种乐观锁设计铺路。3.4 订单状态机status字段的五个值与状态流转约束order表的status是TINYINT取值含义如下status 值状态名触发动作谁可操作数据库约束0待支付用户下单后用户INSERT默认为 01已支付用户点击“确认支付”用户UPDATE status1 WHERE status02已发货管理员点击“发货”管理员UPDATE status2 WHERE status13已签收用户点击“确认收货”用户UPDATE status3 WHERE status24已取消用户在“待支付”时取消用户UPDATE status4 WHERE status0OrderService中所有状态变更方法如payOrder(),shipOrder()都包含严格的状态校验public boolean payOrder(long orderId, long userId) { Order order orderDao.findById(orderId); if (order null || order.getUserId() ! userId || order.getStatus() ! 0) { return false; // 只能支付自己的待支付订单 } return orderDao.updateStatus(orderId, 1) 0; // 仅当原 status0 时才更新成功 }逻辑说明orderDao.updateStatus()的 SQL 是UPDATE order SET status ? WHERE id ? AND status ?第三个?是旧状态值。这保证了状态只能按预设路径流转防止用户通过 URL 伪造请求将 status 从 0 直接改为 2即跳过支付直接发货。3.5 避坑订单生成失败的四大血泪现场与修复方案现象 1下单后页面卡死Tomcat 日志报java.lang.OutOfMemoryError: Java heap space原因webapps/flowerstore/WEB-INF/lib/下存在多个版本的commons-collections如commons-collections-3.2.1.jar和commons-collections4-4.4.jar导致类加载冲突DBUtil.getConnection()创建连接池时无限递归。解决删除lib/中所有commons-collections*只保留commons-dbcp2-2.9.0.jar及其依赖commons-pool2-2.11.1.jar。DBUtil类中static BasicDataSource dataSource初始化时setDriverClassName(com.mysql.cj.jdbc.Driver)必须与mysql-connector-java-8.0.28.jar版本匹配。现象 2库存显示扣减了但flower表stock字段没变原因flowerDao.updateStock()的 SQL 中WHERE stock ?条件未满足但代码未捕获affected0并抛异常导致事务未回滚订单却生成了。解决检查flowerDao.updateStock()方法末尾是否有return affected;并在OrderService.createOrder()中严格判断if (affected ! 1)。database.sql中flower表初始stock值必须 ≥10如INSERT INTO flower VALUES(1,玫瑰,img/rose.jpg,58.00,100,...)否则测试时易触发此问题。现象 3管理员发货后订单状态变为 2但用户中心看不到“确认收货”按钮原因user/orderList.jsp中状态判断逻辑错误写成了c:when test${order.status 1}已支付但发货后是status2应改为c:when test${order.status 2}。解决打开webapps/flowerstore/user/orderList.jsp搜索status 将所有1改为2并确保c:choose标签内c:otherwise覆盖status3已签收和status4已取消。现象 4同一用户连续下单两次第二次订单的order_detail中flower_id全是第一个订单的商品原因OrderServlet中request.getParameterValues(flowerId)返回数组但未清空购物车 session导致第二次请求时getParameterValues仍返回第一次的数据。解决在OrderService.createOrder()成功后添加request.getSession().removeAttribute(cart);并在OrderServlet.doPost()结尾response.sendRedirect(user/orderSuccess.jsp?orderId orderId);前执行。4. 后台管理功能实战如何快速扩展“鲜花分类管理”模块4.1 理解现有后台架构admin/目录下的 MVC 分离逻辑admin/目录是管理员专属空间其 MVC 结构清晰ViewJSPadmin/flowerList.jsp商品列表、admin/addFlower.jsp新增商品表单、admin/editFlower.jsp编辑表单ControllerServletFlowerServlet.java处理/admin/flower的 GET/POST 请求ModelDAO/ServiceFlowerDao.javaCRUD、FlowerService.java业务封装。FlowerServlet的doGet()方法根据request.getParameter(method)分发String method request.getParameter(method); switch (method) { case list: showFlowerList(request, response); break; case add: showAddPage(request, response); break; case edit: showEditPage(request, response); break; default: response.sendError(404); }这种method参数路由是 JavaWeb 课程设计的经典做法比每个功能独立 Servlet 更轻量。4.2 新增“分类管理”三步实现category表的 CRUD第一步数据库建表与初始化在database.sql末尾追加-- 分类表 CREATE TABLE category ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT 分类名称, description VARCHAR(200) COMMENT 描述, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci; -- 初始化数据 INSERT INTO category(name, description) VALUES(玫瑰系列,经典红玫瑰、白玫瑰、香槟玫瑰等),(百合系列,香水百合、亚洲百合、铁炮百合等);执行后category表即存在。第二步编写 DAO 与 Service新建src/main/java/com/flowerstore/dao/CategoryDao.javapublic class CategoryDao { public ListCategory findAll(Connection conn) throws SQLException { String sql SELECT * FROM category ORDER BY create_time DESC; return DBUtil.executeQuery(conn, sql, Category.class, new Object[]{}); } public int insert(Connection conn, Category category) throws SQLException { String sql INSERT INTO category(name, description) VALUES(?, ?); return DBUtil.executeUpdate(conn, sql, new Object[]{category.getName(), category.getDescription()}); } public int update(Connection conn, Category category) throws SQLException { String sql UPDATE category SET name ?, description ? WHERE id ?; return DBUtil.executeUpdate(conn, sql, new Object[]{category.getName(), category.getDescription(), category.getId()}); } public int delete(Connection conn, long id) throws SQLException { String sql DELETE FROM category WHERE id ?; return DBUtil.executeUpdate(conn, sql, new Object[]{id}); } }Category实体类entity/Category.java需有id,name,description,createTime字段及 getter/setter。第三步改造AdminServlet并新增 JSP修改AdminServlet.java或新建CategoryServlet.java添加categoryList方法private void categoryList(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { ListCategory categories categoryService.findAll(); request.setAttribute(categories, categories); request.getRequestDispatcher(/admin/categoryList.jsp).forward(request, response); } catch (Exception e) { e.printStackTrace(); request.setAttribute(msg, 分类列表加载失败); request.getRequestDispatcher(/admin/error.jsp).forward(request, response); } }新建webapps/flowerstore/admin/categoryList.jsp核心表格代码table classtable theadtrthID/thth分类名称/thth描述/thth操作/th/tr/thead tbody c:forEach items${categories} varc tr td${c.id}/td td${c.name}/td td${c.description}/td td a hrefcategory?methodeditid${c.id}编辑/a | a hrefjavascript:void(0) onclickif(confirm(确定删除)) location.hrefcategory?methoddeleteid${c.id}删除/a /td /tr /c:forEach /tbody /table参数说明c:forEach是 JSTL 标签需在 JSP 顶部声明% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %onclick中的confirm()是前端确认弹窗防止误删。4.3 前端样式统一复用admin/css/style.css的栅格系统admin/目录下css/style.css定义了响应式栅格.grid-12 { width: 100%; } .grid-6 { width: 50%; float: left; } .grid-4 { width: 33.33%; float: left; } .clearfix::after { content: ; display: table; clear: both; }在categoryList.jsp中将新增分类的表单包裹在.grid-12内div classgrid-12 clearfix h3新增分类/h3 form actioncategory methodpost input typehidden namemethod valueadd/ div classgrid-6input typetext namename placeholder分类名称 required//div div classgrid-6input typetext namedescription placeholder描述//div div classgrid-12button typesubmit提交/button/div /form /div这样新模块的 UI 与原有后台风格完全一致无需额外写 CSS。4.4 权限控制加固为什么admin/目录不能仅靠前端隐藏当前admin/下所有 JSP 都有% if(session.getAttribute(user) null || !1.equals(((User)session.getAttribute(user)).getRole())) { response.sendRedirect(../login.jsp); } %但这只是前端校验。攻击者可直接请求http://localhost:8080/flowerstore/admin/categoryList.jsp绕过菜单。必须在web.xml中添加security-constraintsecurity-constraint web-resource-collection web-resource-nameAdmin Resources/web-resource-name url-pattern/admin/*/url-pattern /web-resource-collection auth-constraint role-nameadmin/role-name /auth-constraint /security-constraint login-config auth-methodFORM/auth-method form-login-page/login.jsp/form-login-page form-error-page/login.jsp?error1/form-error-page /login-config security-role role-nameadmin/role-name /security-role逻辑说明url-pattern/admin/*/url-pattern匹配所有/admin/下资源auth-constraint要求用户必须拥有admin角色才能访问login-config配置表单认证当未登录或角色不符时自动跳转到login.jsp。这层容器级防护比 JSP 中的 Java 代码更可靠。4.5 避坑分类管理模块上线后的三大兼容性雷区现象 1新增分类后flowerList.jsp中分类筛选下拉框无数据原因FlowerServlet.list()方法中request.setAttribute(categories, categoryService.findAll());未执行或flowerList.jsp中select namecategoryId的options循环写成了c:forEach items${categorys} varc少了个e。解决检查FlowerServlet.java的showFlowerList()方法确认categoryService.findAll()被调用且request.setAttribute(categories, ...)正确在flowerList.jsp中用浏览器开发者工具查看 Network → Response确认categories是否传入。现象 2删除分类时flower表中该分类下的商品未级联删除导致脏数据原因category表与flower表无外键关联DELETE FROM category WHERE id ?不影响flower表。解决在CategoryDao.delete()方法中先执行DELETE FROM flower WHERE category_id ?需先在flower表添加category_id字段再删除category。或在database.sql中重建外键ALTER TABLE flower ADD CONSTRAINT fk_flower_category FOREIGN KEY (category_id) REFERENCES category(id) ON DELETE CASCADE;。现象 3categoryList.jsp中中文分类名显示为????原因CategoryDao.findAll()的 SQL 查询未指定字符集MySQL 连接未设置useUnicodetruecharacterEncodingUTF-8。解决修改DBUtil.java中getConnection()的 URLjdbc:mysql://localhost:3306/flowerstore?useUnicodetruecharacterEncodingUTF-8serverTimezoneGMT%2B8确保连接字符串包含characterEncodingUTF-8。5. 从 98 分到答辩满分三个让导师眼前一亮的进阶技巧5.1 日志增强用log4j2替换System.out.println让调试痕迹变成专业证据项目当前所有日志都是System.out.println(用户登录成功)答辩时导师问“如何排查线上订单失败”你只能翻代码。换成log4j2日志可分级、可输出到文件、可按模块过滤。步骤添加依赖将log4j-api-2.20.0.jar和log4j-core-2.20.0.jar放入webapps/flowerstore/WEB-INF/lib/配置文件在webapps/flowerstore/WEB-INF/classes/下新建log4j2.xml?xml version1.0 encodingUTF-8? Configuration statusWARN Appenders File nameFileAppender fileNamelogs/flowerstore.log PatternLayout pattern%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n/ /File Console nameConsoleAppender targetSYSTEM_OUT PatternLayout pattern%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n/ /Console /Appenders Loggers Logger namecom.flowerstore.service.OrderService leveldebug additivityfalse AppenderRef refFileAppender/ AppenderRef refConsoleAppender/ /Logger Root levelinfo AppenderRef refConsoleAppender/ /Root /Loggers /Configuration代码改造在OrderService.java顶部添加private static final Logger logger LogManager.getLogger(OrderService.class);将System.out.println(订单创建开始)替换为logger.debug(订单创建开始用户ID{}, userId);。效果答辩时你可以打开logs/flowerstore.log向导师展示“看这是用户 123 下单的完整链路从OrderService.createOrder开始到flowerDao.updateStock执行再到conn.commit耗时 127ms全程无异常”。这比口头描述有力十倍。5.2 数据库连接池调优把maxTotal20改成maxTotal50的真实依据DBUtil.java中BasicDataSource的默认配置是setMaxTotal(20)这对单机演示足够但若导师模拟 50 人并发下单会频繁出现java.util.NoSuchElementException: Timeout waiting for idle object。调本文还有配套的精品资源点击获取
返回列表