资讯动态

SSM框架奶茶店管理系统:从数据库设计到核心业务实现

发布时间:2026/10/27 6:33:26 来源:尧图企业网站定制
简介这是一套基于Java的SSM框架奶茶店管理系统完整项目资料包含设计文档与全部源码面向Java学习者、毕业设计开发者以及中小型奶茶店信息化管理人员用于解决商品、库存、订单、会员等日常运营管理效率低下的问题。压缩包共1364个文件大小18.1MB其中包含359个js前端脚本、183个jsp动态页面、165个css样式、140个java源码以及png/gif图片、xml配置、sql数据库脚本和说明文档等分别对应系统页面展示、业务逻辑、数据持久化与部署使用需求。已有66人学习下载适合需要快速上手SSM整合开发或参考完整管理系统设计流程的读者。资源提供了可运行的完整项目、数据库初始化脚本、SSM框架分层实现源码以及图文配套文档可帮助读者理解前后端交互、数据库表设计与核心业务模块开发省去从零搭建环境与排查问题的时间。1. 先别急着写代码这个奶茶店管理系统到底在做什么ssm140基于java的奶茶店管理系统的设计与实现看起来像是一串毕设编号实际上是一套完整的练习场景用 SSM 框架把奶茶店的商品、订单、会员和库存从零到一管起来。很多人是冲着“文档源码”来的想找一套能改改就交的工程但如果你没想清楚它要做什么改起来反而更痛苦。奶茶店不是标准电商小杯大杯、少糖去冰、加料换料这些属性会直接影响表设计和接口写法。这套系统适合正在做 Java 课程设计或毕业设计的学生也适合想用 SSM 把 CRUD 做扎实的中级开发者读完你至少能回答三个问题为什么选 SSM、表怎么设计、踩坑了去哪查。2. 选型先立住为什么这个题目还在用SSM以及奶茶店要管哪些业务2.1 SSM不是过气框架它在毕设和中小项目里的真实定位先回答一个很多人憋着的问题现在 Spring Boot 都到 3.x 了为什么这类题目还在用 SSM因为 SSMSpring Spring MVC MyBatis把三件事拆得非常清楚——Spring 管对象和事务Spring MVC 管路由和参数绑定MyBatis 管 SQL。对教学项目来说这个拆分反而比 Spring Boot 的一站式自动配置更能让人搞清楚每个环节在做什么。导师熟悉、参考代码多、面试时也常被追问所以它并没有过气而是变成了“课程设计与实现”这个细分场景里的主流选择。我一般把 SSM 当作“用最小依赖讲清楚 Java Web 全链路”的方式请求进来先过 DispatcherServlet再到 ControllerService 里写业务DAO 层用 MyBatis 操作表。这个链路里任何一个环节出问题你都能在源码里找到对应位置这对新手排查问题比面对黑匣子式的框架更有优势。如果你是冲着毕设来的选 SSM 还有一层现实好处答辩时老师问“Spring MVC 的处理流程”“MyBatis 怎么防 SQL 注入”你都能拿出实际代码讲不像 Spring Boot 那样容易答得虚。但边界也要说清楚。SSM 的配置繁琐是硬伤一个 XML 配错就启动失败而且做前后端分离的顺手程度不如 Spring Boot Vue 那套。如果你的题目明确写了 SSM那就按 SSM 来如果题目没限定我建议你先评估手里的时间两周要交的话Spring Boot MyBatis Plus 可能更快。选型不是越新越好而是看你要交付什么。2.2 从一杯奶茶倒推业务表商品、订单、会员、库存四件事做管理系统第一步不是写代码而是把业务流程梳理成一张表清单。奶茶店和标准电商最大的区别在哪第一商品有大量口味定制参数糖度、温度、加料、杯型如果每个组合都建一条 SKU表会爆炸第二门店库存是原料维度而不是成品维度卖一杯珍珠奶茶扣的是茶底、珍珠、奶盖的库存第三存在大量低频但必须记录的动作比如会员充值、积分兑换、原料报损。所以我建议把核心表拆成四组。商品组是product和product_attrproduct存基础信息product_attr存规格参数避免 SKU 爆炸。订单组是orders和order_item主表记订单号和总额明细表记每一杯的配置。会员组是member和member_account充值和积分分开记。库存组是raw_material、stock_log和recipe配方表原料主数据、库存流水、商品与原料的消耗关系各司其职。这样对账和报损都有据可查。以下是最小表集合这个规模对毕设和答辩足够又不至于把自己写死表名作用关键字段sys_user系统登录用户id, username, password, roleproduct商品主数据id, name, price, status, categoryproduct_attr商品规格参数id, product_id, attr_name, attr_valueorders订单主表id, order_no, member_id, total_amount, statusorder_item订单明细id, order_id, product_id, spec_json, quantitymember会员基础信息id, phone, name, pointsmember_account会员资金账户id, member_id, balanceraw_material原料主数据id, name, stock, warning_line, unitreservation原料入库记录id, material_id, qty, supplier, create_timestock_log库存流水id, material_id, change_qty, type, order_idrecipe商品配方id, product_id, material_id, qty这套表的思路是订单和商品用两张表解耦订单里的每杯奶茶用spec_json存 JSON 串库存用流水表记录每一次变动原料低于预警线时在列表页标红。你会发现这套设计并不复杂但每个字段都能对应一个真实业务动作答辩时老师问“为什么这么设计”你答得出来。2.3 把营业流程走一遍从点单到打烊的数据流光有表结构还不够我习惯把完整流程先写出来再对着流程检查哪些功能没覆盖。奶茶店一天的典型流程是店员登录系统顾客点单店员在系统里选择商品和规格糖度、温度、加料生成待支付订单收银完成后订单状态变成已支付系统自动按配方扣减原料库存同时给会员累计积分打烊前店长打开当日汇总页核对订单数和销售额再打开库存预警页决定明天要补哪些原料。对照这个流程你会发现系统的核心功能其实就六个登录、商品维护、点单下单、订单管理、会员管理、库存管理。这就是你写系统文档时的功能模块划分也是答辩 PPT 的目录。很多人的设计文档写得虚就是因为没走一遍流程模块和模块之间的数据联系说不清。把“点单 → 支付 → 扣库存 → 记积分”这条数据链写进文档的“业务流程分析”章节会比单纯贴用例图更有说服力。评审老师看到的是你能从业务推导出系统而不是反过来说“系统里有个订单表”。3. 搭建骨架Maven工程结构、数据库脚本与MyBatis映射3.1 工程目录与Maven依赖三个框架怎么拧在一起SSM 工程的目录结构其实有约定俗成的模板。我第一次搭的时候因为目录层级不对Spring 扫不到包踩了一下午。常见做法是分成 controller / service / mapper / entity 四层外加 config、interceptor 和 resources 下的 XML。给你一个可以直接照抄的骨架src/main/java/com/example/tea/ ├── controller/ # 接口层只做参数接收和结果封装 ├── service/ # 业务层事务和业务规则写在这里 │ └── impl/ ├── mapper/ # MyBatis 的 Mapper 接口只声明方法 ├── entity/ # 对应数据库表的实体类 ├── interceptor/ # 登录拦截器等 ├── config/ # 替代 XML 的 Java 配置类 └── common/ # 统一返回结果、异常处理 src/main/resources/ ├── mapper/ # MyBatis 的 XML 映射文件和 Mapper 接口对应 ├── spring/ # spring、springmvc、mybatis 的 XML 配置 └── jdbc.properties # 数据库连接配置注意一个容易翻车的点Spring 的包扫描路径要落在com.example.tea这一层不能写成com.example。写得太宽会把不该注册的类也扫进去写得太窄 Service 就注入不进去启动时直接报 NoSuchBeanDefinitionException。另一个点是 Mapper 接口和 Mapper XML 文件要放在同包名下并且在 Spring 配置里配上 MapperScannerConfigurer 去扫描接口MyBatis 才能把 XML 和接口方法绑定起来。pom.xml 里核心依赖就是下面这几个版本号我建议统一用一套经过验证的组合不要全部拉最新。Spring 的版本、MyBatis 的版本和 mybatis-spring 的版本三者之间有兼容性要求乱配会导致启动时 ClassNotFound。properties spring.version5.2.22.RELEASE/spring.version mybatis.version3.5.9/mybatis.version mybatis-spring.version2.0.7/mybatis-spring.version /properties dependencies dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-jdbc/artifactId version${spring.version}/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version${mybatis.version}/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version${mybatis-spring.version}/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.28/version /dependency dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.8/version /dependency dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId version2.13.4/version /dependency /dependencies解释一下为什么这么选。Spring 5.2.x 配 Spring MVC是 SSM 项目里最稳的一组组合网上资料多遇到问题搜就有答案。MyBatis 3.5.x 配 mybatis-spring 2.0.x接口开发加 XML 映射的经典方式都能用。Druid 做连接池是因为它有监控页面答辩时能现场展示连接池状态比默认的 dbcp 要好看。Jackson 负责 JSON 序列化后面处理订表明细的spec_json会用到。搜索热词里像“java”“ssm”“mybatis”这些本质都是围绕这套依赖展开的我的建议是先把最基础的三件套跑通再考虑引入 MyBatis Plus 这类增强工具。3.2 数据库建表核心DDL与字段类型选择数据库设计的好坏直接决定后面 SQL 好不好写。我给出两张最有代表性的表结构设计其余的照同样规范来就行。先看商品表。奶茶店有普通的单杯也有套餐用一个 status 字段控制上下架而不是物理删除这是所有管理系统的基础习惯。CREATE TABLE product ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT 商品名如珍珠奶茶, price DECIMAL(6,2) NOT NULL COMMENT 售价单位元, status TINYINT NOT NULL DEFAULT 1 COMMENT 1上架 0下架, category VARCHAR(20) DEFAULT NULL COMMENT 分类如奶茶/果茶/小料, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;价格为什么要用DECIMAL(6,2)而不是 FLOAT 或 DOUBLE因为浮点数在 MySQL 里做比较和累加会出现精度误差金额哪怕差一分钱对账的时候都很难看。create_time用 DATETIME 加默认值插入的时候少写一个字段。status 加索引是因为后台列表最常见的查询就是“只看上架的商品”这是基本功但很多人会忘。字符集选 utf8mb4是因为它才能完整存下 emoji 和一些特殊符号奶茶店商品名里偶尔会出现。再看订单明细表。这张表和前面提到的 spec_json 结合是奶茶店项目区别于图书馆管理系统的关键设计。CREATE TABLE order_item ( id INT NOT NULL AUTO_INCREMENT, order_id INT NOT NULL COMMENT 订单主表ID, product_id INT NOT NULL COMMENT 商品ID, spec_json VARCHAR(500) DEFAULT NULL COMMENT 规格存JSON串, quantity INT NOT NULL DEFAULT 1, unit_price DECIMAL(6,2) NOT NULL, PRIMARY KEY (id), KEY idx_order (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;spec_json 里存的是类似{sugar:半糖,temp:去冰,topping:[珍珠,椰果]}这样的字符串。这样做的好处是不管未来加多少种口味定制项都不需要改表结构坏处是没法针对某个口味做 SQL 统计。对毕设来说这个取舍是划算的。order_id 加索引是因为查订单明细永远通过订单号来查。单个可选小料这种多选场景JSON 数组也能存下。第三张关键表是库存流水表它记录每一次原料变动。精髓是不做物理删除只有追加。CREATE TABLE stock_log ( id INT NOT NULL AUTO_INCREMENT, material_id INT NOT NULL, change_qty DECIMAL(8,2) NOT NULL COMMENT 正数入库负数出库, type TINYINT NOT NULL COMMENT 1采购入库 2销售出库 3报损, order_id INT DEFAULT NULL COMMENT 关联订单可空, remark VARCHAR(100) DEFAULT NULL, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_material (material_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;为什么出库存负数而不是存一个方向字段因为计算当前库存时直接用SUM(change_qty)就能算出来不需要写分支条件。很小的设计点但会让后面的 SQL 简单很多。报损、采购、销售三类变动都走同一张表月底对账时按 type 分组就能把账平掉。我的血泪经验是一开始偷懒直接在 raw_material 表上改 stock 字段不做流水结果报损和退款各改各的对账对不上最后老老实实补了stock_log。3.3 MyBatis映射动态SQL与结果映射的两个关键点MyBatis 的 XML 映射文件里最常用也最容易出问题的是两个点动态 SQL 和结果映射。动态 SQL 解决“查询条件不确定”的问题结果映射解决“数据库字段名和 Java 属性名不一致”的问题。先看动态 SQL。典型场景是商品列表筛选管理员可能按名称模糊查、按分类精确查、按状态查也可能什么都不传查全部。这种场景不用动态 SQL 的话就得在 Java 里拼字符串又危险又难看。select idlistProducts resultTypecom.example.tea.entity.Product SELECT * FROM product where if testname ! null and name ! AND name LIKE CONCAT(%, #{name}, %) /if if testcategory ! null and category ! AND category #{category} /if if teststatus ! null AND status #{status} /if /where ORDER BY id DESC /selectwhere标签会自动处理掉第一个多余的 ANDif让条件可按需组装。为什么用 CONCAT 而不是直接写%#{name}%因为 MyBatis 的#{}是预编译占位符写在字符串字面量里它不会帮你拼进去直接写会报错或查不到数据。所有#{}都会转成 PreparedStatement 的?这也是 MyBatis 防 SQL 注入的根本原因答辩时这个点常被问到。再讲结果映射。数据库字段是下划线风格order_no、create_timeJava 实体是驼峰orderNo、createTime如果不配置MyBatis 默认匹配不上查出来的对象这些字段全是 null而且不报错。我见过有人在这种问题上排查了一整天才发现是驼峰没开。解决方案是在 MyBatis 全局配置里打开mapUnderscoreToCamelCasesettings setting namemapUnderscoreToCamelCase valuetrue/ /settings这样数据库的 order_no 会自动映射到 Java 的 orderNo。注意这个开关对 resultType 一样生效但如果你用了 resultMap 并手动指定了映射列手动指定的优先级更高。另一个和日期相关的坑表的 create_time 是 DATETIMEJava 里用 java.util.Date 接收没问题如果你实体里写的是 java.sql.Timestamp恰好又没手动格式化返回的 JSON 里时间会是一长串数字前端处理起来很别扭。建议实体统一用 java.util.Date配合 Jackson 的日期注解来输出。到这里骨架和映射层就能跑通了下一步开始写核心业务。4. 核心功能落地登录、下单与库存扣减的代码写法4.1 登录与拦截器用Spring MVC拦截器实现权限控制登录是每个管理系统的门面。SSM 项目里常见做法有两种引入 Apache Shiro或者用 Spring MVC 自带的拦截器。我的建议是毕设用拦截器就够。Shiro 功能强但配置项多对新手来说是一个额外的黑匣子答辩时也容易被追问细节。拦截器十几行代码就能解决问题逻辑完全掌握在自己手里。做法是登录成功后把 user 对象放进 session然后写一个 LoginInterceptor在 preHandle 里判断 session 里有没有 user没有就重定向到登录页并返回 false。public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object user request.getSession().getAttribute(loginUser); if (user null) { // 未登录重定向到登录页 response.sendRedirect(request.getContextPath() /login); return false; } return true; } }逻辑很简单session 里没有 loginUser 就说明没登录拦下来。注意sendRedirect要加request.getContextPath()否则部署在带项目名的路径下时跳转会 404。这个细节是很多人的翻车点。然后在 Spring MVC 配置里注册拦截器并排除登录接口和静态资源mvc:interceptors mvc:interceptor mvc:mapping path/**/ mvc:exclude-mapping path/login/ mvc:exclude-mapping path/css/**/ mvc:exclude-mapping path/js/**/ mvc:exclude-mapping path/images/**/ mvc:interceptor classcom.example.tea.interceptor.LoginInterceptor/ /mvc:interceptor /mvc:interceptors**匹配所有请求exclude-mapping 把登录页和静态资源放行。如果你做的是前后端分离后端只提供 JSON 接口那么拦截器挡住的应该是一个返回 JSON 的未登录结果而不是重定向。区分两种场景很重要JSP 页面开发时用重定向自然接口开发时用重定向会导致前端拿到一个 HTML 而不是 JSON处理起来非常痛苦。密码为什么存 MD5 摘要而不是明文因为一旦数据库泄露明文密码等于把系统权限送人。我一般用 MD5 加盐即MD5(password salt)salt 每个用户随机生成并单独存。这个方案在毕设层面足够应对答辩提问如果要上生产至少换 BCrypt。4.2 下单核心流程事务保证订单主表与明细一致下单是奶茶店系统最核心、也最值得在答辩时重点讲的业务。流程是插入订单主表拿到订单 ID再往 order_item 插入明细同时扣减原料库存、给会员累计积分。这四个动作必须在一个事务里任何一个失败其他都要回滚否则就会出现“订单下了但明细对不上”这种烂账。先写 Service 层核心方法Override Transactional(rollbackFor Exception.class) public Order createOrder(OrderDTO dto) { // 1. 创建订单主表 Order order new Order(); order.setOrderNo(generateOrderNo()); order.setMemberId(dto.getMemberId()); order.setTotalAmount(calcTotal(dto.getItems())); order.setStatus(1); // 1待支付 2已支付 3已完成 4已取消 orderMapper.insert(order); // 2. 插入订单明细 for (OrderItemDTO item : dto.getItems()) { OrderItem orderItem new OrderItem(); orderItem.setOrderId(order.getId()); orderItem.setProductId(item.getProductId()); orderItem.setSpecJson(JSON.toJSONString(item.getSpec())); orderItem.setQuantity(item.getQuantity()); orderItem.setUnitPrice(item.getUnitPrice()); orderItemMapper.insert(orderItem); // 3. 扣减原料库存 stockService.deductStock(item.getProductId(), item.getQuantity()); } // 4. 给会员加积分 if (dto.getMemberId() ! null) { memberService.addPoints(dto.getMemberId(), order.getTotalAmount().intValue()); } return order; }Transactional(rollbackFor Exception.class)是这里的主角。为什么一定要指定 rollbackFor因为 Spring 默认只在遇到 RuntimeException 时回滚如果你抛的是自定义的 CheckedException事务不会回滚数据就写进去了。rollbackFor Exception.class 的意思是无论什么异常都回滚这符合业务直觉。还有三个细节。第一generateOrderNo 我一般用日期加随机数生成例如20250612 四位随机数订单号可读性强。第二calcTotal 应该用明细里的 unit_price 乘 quantity 累加不能直接信任前端传过来的 totalAmount否则客户端改一下金额接口就收错钱。第三扣库存要放进事务里而且要在插入明细之后因为库存不足时要抛异常让整个事务回滚。库存扣减为什么单独说因为奶茶店的原料不是按商品一一对应的一杯奶茶会消耗茶底、珍珠、奶盖多种原料所以 deductStock 里要查配方表去扣多种原料。如果珍珠库存不够Java 侧要先查配方再逐项检查库存不够就抛异常。这个检查放在事务里才能保证并发时数据不错。4.3 库存预警与门店视角数据怎么展示才不算白做管理系统不能只有增删改查至少要有一个让人看得见价值的页面。奶茶店场景里我建议把“库存预警列表”和“当日销售汇总”做出来。库存预警是查询原料表中当前库存低于预警线的记录当日销售汇总是按时间段统计订单金额这两个查询答辩时都很好讲。先看库存预警核心 SQL 是这样select idselectLowStock resultTypecom.example.tea.entity.RawMaterial SELECT * FROM raw_material WHERE stock lt; warning_line ORDER BY (stock / warning_line) ASC /selectlt;是 XML 里小于号的转义直接写会导致 XML 解析报错这个细节比想象中更容易踩。排序用stock / warning_line的比例把最缺的原料排在前面方便店员按顺序补货。这个查询在管理后台展示成一个带红色标记的列表后端逻辑不复杂但视觉效果很能打。当日销售汇总则把 orders 表按时间聚合select idsumToday resultTypejava.util.Map SELECT COUNT(*) AS order_count, COALESCE(SUM(total_amount), 0) AS total_sales FROM orders WHERE create_time gt; #{todayStart} AND create_time lt; #{tomorrowStart} /select用 Map 作为返回类型虽然看起来不太面向对象但这类的统计查询返回的是二维表结构没有对应实体类用 Map 反而是最常见做法。COALESCE 是为了防止当天没有订单时 SUM 返回 NULL 导致前端拿到空值。你可以把“店员每天开工先打开库存预警页”“打烊后看一眼销售汇总”作为这个功能的用户故事写进文档会让系统设计显得完整而不是功能堆砌。4.4 统一返回结构与全局异常别让前端拿到的JSON长成随机形状不少 SSM 项目的接口返回值各不相同有的直接返回对象有的返回字符串有的返回 null前端拿着乱七八糟的数据还得自己猜。我习惯从一开始就统一返回结构这是让项目看起来“像个正经系统”的最快方式。public class ResultT { private Integer code; // 0 成功非 0 失败 private String msg; // 提示信息 private T data; // 业务数据 public static T ResultT success(T data) { ResultT r new Result(); r.code 0; r.msg ok; r.data data; return r; } public static T ResultT error(String msg) { ResultT r new Result(); r.code 500; r.msg msg; return r; } }Controller 里所有接口统一返回Result再用ControllerAdvice做全局异常处理把未捕获异常包装成Result.error返回而不是让 Tomcat 吐一个 500 错误页。这样前端只需要判断 code 是否为 0逻辑分支清晰调试时问题也容易定位。5. SSM项目避坑清单五个让新手翻车的真实问题和排查路径我把 SSM 奶茶店系统里最常见的五个坑按“现象 → 原因 → 解决”整理出来。这些坑在同类项目里几乎都会出现你会发现它们并不是技术难点而是藏在配置、约定和设计选择里。5.1 中文乱码不是改一个编码能解决的现象页面或接口返回的中文全部变成问号或者插入数据库后变成乱码。原因分三层JSP 或 HTML 页面本身的编码不是 UTF-8Tomcat 连接器默认的 URI 编码不是 UTF-8Spring MVC 的 CharacterEncodingFilter 没配置。任何一层没对齐就会乱码。解决三层一起动手。页面文件统一 UTF-8Tomcat 的 server.xml 里给 Connector 加URIEncodingUTF-8在 web.xml 注册 CharacterEncodingFilter把 encoding 设为 UTF-8forceEncoding 设为 true。注意 forceEncodingtrue 会同时覆盖请求和响应只设 encoding 不设 forceEncoding 的话响应可能还是乱码。还有个隐藏点filter 要放在其他 filter 前面否则可能被后面的逻辑覆盖。5.2 前端传JSON后端Controller参数全是null现象用 Ajax 发送 JSON 到后端POJO 参数所有字段都是 null但用表单提交就正常。原因pom.xml 里没加 Jackson 依赖或者加了但 Spring MVC 没开启消息转换器。Spring MVC 需要 Jackson 来完成 JSON 和对象的互相转换没依赖就转换不了。解决确认 pom 里加了 jackson-databind并且在 spring-mvc 配置里加mvc:annotation-driven/。这一行会帮忙注册默认的消息转换器包括 Jackson 的 MappingJackson2HttpMessageConverter。另一个常被忽略的点是后端接收对象参数要加RequestBody注解不加的话 Spring 会按表单参数去绑定JSON 数据自然绑不上。5.3 对账时发现库存与流水对不上现象raw_material 的 stock 字段和 stock_log 的 SUM(change_qty) 对不上且无法定位是哪一笔导致。原因直接在 stock 字段上做加减没有写流水日志。一旦并发操作或者某次更新语句写错数据就静默错了没有任何痕迹可查。解决建一张 stock_log 流水表每次变动都插入一条记录stock 字段只当冗余缓存来用对账时永远以流水表为准。这是一个设计层面的坑比代码 bug 更难发现。奶茶店场景尤其严重早上卖几十杯奶茶每杯按配方扣多种原料中间漏一步晚上就对不平。5.4 事务不生效现象订单主表插入成功明细插入失败后主表没有回滚数据库里躺着一条没有明细的脏订单。原因Transactional 只有在被 Spring 容器管理的 Service 方法被外部调用时才会生效。自己在类内部用 this 调用自己的另一个方法事务会绕过代理直接执行这是典型的 Spring 代理问题。解决确保事务方法是被外部调用的不要让同类里的方法用 this 调用本类的另一个事务方法。如果一定要内部调用就把事务方法拆到另一个 Service 类里去。另外确认 Transactional 写在实现类方法上且实现类确实被 Spring 扫描到了。启动时看日志里有没有 “Creating transaction manager” 开头记录没有就是事务配置没加载。5.5 MyBatis启动报BindingException现象启动时抛 org.apache.ibatis.binding.BindingException提示 Invalid bound statement (not found)。原因Mapper 接口方法找不到 XML 里的 statement。最常见是 XML 文件没被 Maven 打包进 target 目录。XML 放在 src/main/java 下时默认不会复制到 classes 目录放在 src/main/resources 下就没这个问题。解决把 XML 映射文件放到 src/main/resources/mapper/ 下并在 pom 里加一个 resource 配置把 XML 目录包含进构建产物。最快检查方法直接打开 target 目录看对应的 XML 在不在。不在就是没打包进去。另一个连带问题是 Mapper 接口和 XML 的 namespace 不一致也会报同样的错核对 namespace 要写成接口的全限定名。这五条做完SSM 项目基本能稳下来。你会发现它们都不是需要查几个小时源码的深坑而是配置、约定、设计选择这三类问题的集合。提前排掉后面写代码会顺很多。6. 上线前最后一步用Postman把核心接口过一遍并打开SQL日志写到这里系统能跑起来了但很多人的项目就停在“能跑”这个状态答辩演示时才发现接口返回的数据不对。我建议你花一小时做一次接口验收打开 Postman建一个名为 tea-shop 的集合把登录、商品列表、下单、库存预警四个请求按顺序放进去。先登录拿到 session带 cookie 请求商品列表再携带包含商品与规格的 JSON 去下单最后查库存预警确认数据变化。每跑一次检查三样东西HTTP 状态码是不是 200返回 JSON 里的 code 是不是期望值数据库里对应表的数据有没有变化。验证时有个小习惯我认为很重要把 MyBatis 的 SQL 日志打开。再复杂的业务逻辑只要能看到每一条实际执行的 SQL问题范围就缩小一大半。settings setting namelogImpl valueSLF4J/ /settings配合 logback 把 mapper 包下的日志级别设为 DEBUG这样每次查询都会打印 SQL 和参数。我做这类系统时所有对账问题都是靠 SQL 日志定位的。这个习惯建议你保留到以后做 Spring Boot 项目也一样有效。注意日志打印的参数是对应的实际值能看到 #{} 替换成什么这对于排查“参数传了但查不到数据”的问题尤其有用。还有一个进阶方向值得考虑把前后端分离。如果题目允许保留 SSM 后端只返回 JSON前端用 Vue 或原生 HTML 加 Ajax 来做。这一步能让项目从“课程作业”变成“可以演示的商品系统”不会增加太多工作量。但如果你只剩一周时间别碰这个方向把 JSP 页面打磨好一样能拿高分。最后说一个我的习惯项目交付前把数据库脚本、pom.xml、核心 SQL 单独存一份并在另一台干净机器上按文档从零跑一遍。跑通了才叫交付跑不通就是给自己埋雷。做毕设也好做内部小工具也好这一点都值得坚持。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑