ARTICLE DETAIL

资讯详情

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

JSP+SSM农场供销系统源码拆解:架构、事务与部署实战

JSP+SSM农场供销系统源码拆解:架构、事务与部署实战 简介一套面向农场管理人员和Java初学者的xx农场供销一体化系统源码及说明文档代码基于SSMSpringSpringMVCMyBatis与JSP技术栈结合MySQL数据库与Maven构建工具实现农产品信息管理、分类管理、在线订购、会员管理及配送等核心功能。系统按角色分为管理员端与会员端管理员可维护商品与分类、处理订购和会员数据会员则能浏览农产品并提交订购需求。压缩包约23.55MB内部源码与说明文档可配合IDEA或Eclipse进行部署数据库工具可使用Navicat或SQLyog。资源已吸引104人学习适合用来理解SSM各层整合、JSP动态页面开发、数据库表结构设计以及Maven项目管理流程。通过阅读源码和文档读者能掌握一套完整的农场供销业务闭环与前后端交互实现为课程设计或毕业设计提供可直接改造的参考。1. 农场供销一体化这套源码核心是 JSP SSM 的分层协作“农场供销一体化系统”这个名字听着像期末课设拆开看就是一套标准的 java SSM JSP 后台项目Spring 管业务 BeanSpringMVC 接 HTTP 请求MyBatis 读写 MySQL页面用 JSP 渲染。它要解决的业务不算大但很完整——农资采购入库、农产品销售出库、库存台账、结算对账四个环节在一个系统里闭环。这套源码最值得看的不是页面做得多精致而是三层架构怎么把供销单据串成一条可对账的数据流。它适合三类人拿 jsp 做毕设选题的学生想搞懂 SSM 三者怎么协作的 java 学习者以及要给农场或合作社搭内部管理系统的开发。源码本身能跑是基础真正有价值的是你能照着它的分层把供销业务的前因后果讲清楚然后把数据流改造成自己的。2. 先拆工程骨架SSM 与 JSP 在供销系统里各管哪一段2.1 从 Maven 工程目录看三层职责与 JSP 的落位拿到源码先别急着点运行先把目录扫一遍。这类项目通常是标准的 Maven 多模块或单模块结构webapp 是 JSP 的家java 包下面按 controller、service、mapper、entity 分层。我一般会先看有没有src/main/webapp/WEB-INF/jsp这个目录有它说明视图层走的是 JSP 转发不是前后端分离。farm-supply-chain ├── pom.xml ├── src/main/java/com/farm │ ├── controller # SpringMVC 的 C接收请求、校验参数、决定跳哪个 JSP │ │ ├── LoginController.java │ │ ├── GoodsController.java │ │ └── SalesController.java │ ├── service # 业务层采购、销售、库存扣减的事务在这里 │ │ ├── GoodsService.java │ │ └── SalesService.java │ ├── mapper # MyBatis 的接口层只管 SQL 映射 │ │ ├── GoodsMapper.java │ │ └── SalesMapper.java │ ├── entity # 与数据库表一一对应的 POJO │ │ ├── Goods.java │ │ └── SalesOrder.java │ └── common # 分页封装、统一返回结果、异常类 └── src/main/resources ├── jdbc.properties ├── spring-context.xml # Spring 容器数据源、事务、Mapper 扫描 ├── spring-mvc.xml # SpringMVC视图解析、Controller 扫描 └── mapper/GoodsMapper.xml # SQL 语句集中地 src/main/webapp ├── WEB-INF │ ├── web.xml # Servlet 容器入口版本号决定 EL 是否默认开 │ └── jsp │ ├── login.jsp │ └── goods/list.jsp这个结构在 java 课程设计和毕设里出现频率极高面试问 SSM 项目也喜欢从这棵树开始让你讲。业务上controller 不写 SQLmapper 不写 if 判断service 才是供销规则的执行者。判断一个 SSM 项目写得好不好先看 service 层是不是足够厚。2.2 把一张销售单变成一次事务Controller、Service、Mapper 怎么接力以“前台提交一张销售订单”为例完整链路是 JSP 表单 POST 到 ControllerController 收参数后调用 ServiceService 先扣库存再插单据最后返回视图让 JSP 展示结果。这里最容易写错的是把库存扣减写在 Controller 里然后事务注解失效。Controller RequestMapping(/sales) public class SalesController { Resource private SalesService salesService; RequestMapping(/create) public String create(SalesOrder order, Integer goodsId, Integer quantity, Model model) { try { salesService.createOrder(order, goodsId, quantity); model.addAttribute(msg, 下单成功); } catch (BizException e) { model.addAttribute(msg, e.getMessage()); } return sales/result; } }Controller 里只做了三件事接收表单参数、调用 service、把结果消息放进 Model 供 JSP 读取。注意Integer goodsId和Integer quantity是 JSP 表单里 input 的 name 值SpringMVC 会自动绑定不用手动 request.getParameter。这样写的意义是 Controller 变薄出错时你能快速定位是参数问题还是业务问题。再往下看 Service。供销系统的核心逻辑都压在 service 层尤其是库存扣减和订单生成的原子性。Service public class SalesServiceImpl implements SalesService { Resource private GoodsMapper goodsMapper; Resource private SalesMapper salesMapper; Transactional(rollbackFor Exception.class) public void createOrder(SalesOrder order, Integer goodsId, Integer quantity) { int rows goodsMapper.deductStock(goodsId, quantity); if (rows 0) { throw new BizException(库存不足); } order.setTotalAmount(order.getPrice().multiply(new BigDecimal(quantity))); salesMapper.insertOrder(order); SalesItem item new SalesItem(); item.setOrderId(order.getId()); item.setGoodsId(goodsId); item.setQuantity(quantity); item.setAmount(order.getPrice().multiply(new BigDecimal(quantity))); salesMapper.insertItem(item); } }这段代码解释了“供销一体化”里库存和订单为什么必须在一件事里完成。Transactional(rollbackFor Exception.class)表示任何异常都要回滚包括受检异常deductStock返回 0 表示库存扣减失败直接抛异常后面的 insert 就不会执行。如果这里没用事务会出现库存扣了但订单没生成的黑匣子账目。2.3 依赖清单与版本边界这套组合的默认安全区SSM 的版本搭配在 java 面试题里几乎是个固定考点实际跑项目时选错版本比写错代码更致命。常见做法是 JDK 8 Spring 5.2.x MyBatis 3.5.x MyBatis-Spring 2.0.xTomcat 用 8.5 或 9。这套组合经过大量项目验证是源码能直接跑起来的最大公约数。dependencies dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version5.2.22.RELEASE/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.10/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.6/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version scoperuntime/scope /dependency dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version3.1.0/version scopeprovided/scope /dependency /dependencies注意两个细节mysql 驱动的 scope 是 runtime编译期用不到运行时由 Tomcat 加载servlet-api 必须 provided否则和 Tomcat 自带的冲突启动直接报 NoSuchMethodError。如果你本机是 MySQL 5.7驱动版本可以降到 5.1.49但更省事的做法是库和驱动都统一到 8.0url 里加时区参数即可。3. 数据库建模与供销闭环库存、订单、结算怎么串成一条线3.1 供销核心表概览把业务拆成可对账的关系农场供销系统的复杂度不在技术在表结构设计。设计目标是让“采购进来多少、卖出去多少、还剩多少”三件事能互相印证。下面是核心表的分工字段以精简够用为准。表名职责关键字段与供销的关系goods商品档案goods_name、category、unit、price、status供销的对象上下架状态supplier供应商name、contact、phone采购来源warehouse_stock库存台账goods_id、quantity采购加、销售减实时余额purchase_order采购单order_no、supplier_id、total_amount、status入库依据purchase_item采购明细order_id、goods_id、price、quantity每笔采购的具体商品sales_order销售单order_no、customer、total_amount、status出库依据sales_item销售明细order_id、goods_id、quantity、amount每笔销货的具体商品settlement结算对账order_no、order_type、amount、status采购与销售统一记账这个拆分思路是“主表 明细表”模式采购单和销售单都需要流水号明细表记录商品级数据。库存表是中间的账本采购单审核通过给库存加销售单创建成功给库存减结算表再做金额层核对。三层对上了供销才不会出现账实不符。3.2 建表 SQL明细表为什么要冗余商品名称和单价看建表语句能判断作者有没有实战经验。一个典型的 sales_item 表会把 goods_name 和 price 冗余进来而不是查询时再去 join goods 表。原因是 JSP 列表页经常要展示历史单据单据一旦生成商品改名、调价都不该影响它。CREATE TABLE goods ( id INT PRIMARY KEY AUTO_INCREMENT, goods_name VARCHAR(64) NOT NULL, category VARCHAR(32) NOT NULL DEFAULT 蔬菜, unit VARCHAR(8) NOT NULL DEFAULT kg, price DECIMAL(10,2) NOT NULL, status TINYINT NOT NULL DEFAULT 1 COMMENT 1上架 0下架, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE warehouse_stock ( goods_id INT PRIMARY KEY, quantity INT NOT NULL DEFAULT 0, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT fk_stock_goods FOREIGN KEY (goods_id) REFERENCES goods(id) ); CREATE TABLE sales_order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, customer VARCHAR(64) NOT NULL, total_amount DECIMAL(10,2) NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待结算 1已结算 2已取消, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE sales_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, goods_id INT NOT NULL, goods_name VARCHAR(64) NOT NULL, price DECIMAL(10,2) NOT NULL, quantity INT NOT NULL, amount DECIMAL(10,2) NOT NULL, KEY idx_order (order_id), CONSTRAINT fk_item_order FOREIGN KEY (order_id) REFERENCES sales_order(id) );这里三个设计点值得抄金额字段统一用 DECIMAL禁止 float 和 double否则结算对账会因精度翻车sales_item 冗余 goods_name 和 price让 JSP 展示订单详情时少两张表 joinwarehouse_stock 以 goods_id 为主键天然保证一个商品只有一行库存记录并发扣减时好锁。3.3 扣库存不靠先查再减一条 UPDATE 的乐观锁语义很多人第一次写库存扣减是先 SELECT 查 quantity判断够不够再 UPDATE。这套写法在单用户调试时看不出来一旦两个人同时下单就超卖。正确做法是把判断写在 UPDATE 的 WHERE 条件里让数据库替你做原子校验。UPDATE warehouse_stock SET quantity quantity - #{quantity} WHERE goods_id #{goodsId} AND quantity #{quantity}MyBatis 中这条 SQL 的返回值就是 affected rows。如果库存够返回 1不够返回 0Service 层拿到 0 直接抛业务异常。它不依赖应用锁也不需要先 SELECT性能和对错两头都占。采购入库同理改成quantity quantity #{quantity}但要考虑并发下同一商品多笔采购单同时入库用追加而不是赋值覆盖。3.4 初始化数据别让外键和状态字段卡住第一次启动源码的说明文档一般会附 SQL 脚本但常见坑是只建表不给初始化数据。跑供销系统至少需要一条管理员账号、两类商品、一行初始库存否则登录进去全是空列表你根本分不清是功能没实现还是数据没喂进去。INSERT INTO goods (id, goods_name, category, unit, price, status) VALUES (1, 有机西红柿, 蔬菜, kg, 5.50, 1), (2, 农资复合肥, 农资, 袋, 120.00, 1); INSERT INTO warehouse_stock (goods_id, quantity) VALUES (1, 200), (2, 50); INSERT INTO sys_user (id, username, password, real_name) VALUES (1, admin, MD5(123456), 系统管理员);注意密码别用明文MD5 是这套老项目的常见做法虽然不够强但至少不会一眼泄露。status 字段的取值要在代码里统一商品 1 上架 0 下架订单 0 待结算 1 已结算 2 已取消。JSP 页面上显示中文状态时通常在 controller 里做映射不要在页面写死。4. 跑通源码的关键配置Idea 导入、Spring 整合、JSP 联调4.1 两种导入方式直接开 Maven 工程还是手动建 JSP 项目拿到源码后第一件事不是改代码是把工程正确导入 Idea。这里因人而异如果坚持用“idea 新建 jsp 项目”的传统方式需要手动把 web 目录指到 src/main/webapp再配置 Artifacts更省事的是直接用 Maven 方式打开。常见做法是 Idea 里 File - Open选择项目下的 pom.xml以 Maven 项目导入。等待依赖下载完成后检查右侧 Maven 窗口有没有出现 spring、mybatis 相关依赖然后配置 TomcatRun - Edit Configurations - 新建 Tomcat Server LocalDeployment 里添加 Artifact。这里有个区分如果它显示 war exploded说明是开发模式改 JSP 可以热更新如果是 war 包每次都要重新构建适合最终部署。提示老项目常见的问题是本地 Maven 仓库没有对应版本依赖idea 会一直卡在下载。把 Maven 的 settings.xml 里的镜像仓库配好再重新 import 一次基本能解决。4.2 jdbc.properties 与 spring-context.xml数据源、事务、Mapper 扫描三件事数据库连不上是启动失败的头号原因而大部分是因为驱动类名和时区参数。记住 MySQL 8 用com.mysql.cj.jdbc.DriverMySQL 5 用com.mysql.jdbc.Driverurl 里必须加serverTimezoneAsia/Shanghai否则报时区错误。jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/farm?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password你的密码Spring 配置文件里要做的三件事数据源、SqlSessionFactory、事务管理器。顺序不能乱事务管理器依赖数据源SqlSessionFactory 依赖 mapperLocations 指向 XML。context:property-placeholder locationclasspath:jdbc.properties/ bean iddataSource classorg.springframework.jdbc.datasource.DriverManagerDataSource property namedriverClassName value${jdbc.driver}/ property nameurl value${jdbc.url}/ property nameusername value${jdbc.username}/ property namepassword value${jdbc.password}/ /bean bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ /bean bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.farm.mapper/ /bean bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /bean tx:annotation-driven transaction-managertransactionManager/这里的classpath:mapper/*.xml是高频出错点如果你的 Mapper XML 放在 resources 之外的目录或者路径写成了classpath*:MyBatis 就找不到 SQL运行时直接报 Invalid bound statement。MapperScannerConfigurer 的 basePackage 是接口所在包它会把包下所有接口自动代理成 Mapper Bean。4.3 spring-mvc.xml视图解析器决定 JSP 跳到哪里SpringMVC 与 JSP 协作的关键是 InternalResourceViewResolver。它负责把 Controller 返回的字符串视图名解析成真正可访问的 JSP 路径。如果这个前缀后缀配置不对任何请求都会 404。mvc:annotation-driven/ context:component-scan base-packagecom.farm.controller/ bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/jsp// property namesuffix value.jsp/ /bean mvc:resources mapping/static/** location/static// mvc:resources mapping/upload/** location/upload///WEB-INF/jsp/前缀的含义是把 JSP 藏在 WEB-INF 下外部浏览器直接输入 URL 访问不到必须经 Controller 转发。这是 SSM 项目的标准安全姿势也能避免 JSP 源码直接暴露。静态资源单独配置是防止 DispatcherServlet 把 css/js 也当 controller 请求处理不加这段页面会全部裸奔。4.4 web.xmlservlet 版本与编码过滤器不能省web.xml 是第一道关卡版本声明直接关系到 JSP 的 EL 表达式是否默认开启。如果你的 web.xml 是 2.3 或 2.4 的老版本JSP 页面里的${goods.name}会被原样输出原因就是 EL 功能默认关闭或部分支持。用 Servlet 3.1 版本声明可以从根上避开这个坑。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_3_1.xsd version3.1 filter filter-nameencoding/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameencoding/filter-name url-pattern/*/url-pattern /filter-mapping servlet servlet-namedispatcher/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcher/servlet-name url-pattern//url-pattern /servlet-mapping /web-appCharacterEncodingFilter 必须放在所有过滤器最前面且 url-pattern 写/*否则 JSP 提交的中文表单数据到后端就是乱码。DispatcherServlet 的 url-pattern 用/表示除了.jsp之外的所有请求都进 SpringMVC.jsp请求由 Tomcat 的 Jasper 解析这样 JSP 页面本身不走 Controller 也能被访问到。4.5 跑通一次完整供销链路从登录到库存变化配置无误后启动 Tomcat浏览器访问http://localhost:8080/farm/正常会跳登录页。用 admin 登录之后按采购入库、销售出库、库存查询的顺序走一遍验证标准是仓库里商品数量先增加后减少结算页面的金额与订单明细一致。我一般会准备一张草稿纸把初始库存数写下来每做一笔就核对一次页面数字。如果下单后库存没变优先怀疑事务没生效或者 Mapper XML 的 update 语句写成了 insert。这一步跑通比看十页文档都管用因为它一次性验证了 Spring 容器、MyBatis 映射、JSP 视图解析三条链路。5. 排查与避坑JSP 项目跑不起来时先查这五个地方5.1 页面把${goods.name}原样打印出来现象JSP 页面没有报错但 EL 表达式没有被解析页面上直接显示${goods.name}这类原文。原因绝大多数是 web.xml 的 servlet 版本声明太低。Servlet 2.3 及以下默认 isELIgnored 为 true2.4 之后才默认支持 EL 解析。如果 web.xml 头声明的是 2.3 版本或者压根没有版本声明被容器当成老版本处理就会出现整页表达式裸奔。解决把 web.xml 的 version 改成 3.0 或 3.1schema 换成xmlns.jcp.org的 javaee 版本。改完记得 RebuildMaven 项目里 WEB-INF/web.xml 是资源文件不重新构建可能还在用旧的。5.2 数据库连接报 Communications link failure现象Tomcat 启动时 Spring 初始化数据源失败控制台出现Communications link failure或Access denied for user。原因驱动类名不匹配。MySQL 8 的驱动类已经改成com.mysql.cj.jdbc.Driver连接串少了serverTimezoneAsia/Shanghai也会直接失败。如果 pom 里引入的 mysql-connector-java 是 5.x而数据库是 8.x协议层就不兼容怎么配都连不上。解决先把数据库版本确认清楚再统一驱动。MySQL 8 配mysql-connector-java 8.0.x和com.mysql.cj.jdbc.Driver并在 url 后面拼?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai。这个坑最常见于本地装的是 8.0但网上搜到的老教程都是 5.x 写法。5.3 MyBatis 报 Invalid bound statement (not found)现象项目能启动Controller 也能进但一调用 Mapper 接口的方法就抛Invalid bound statement (not found): com.farm.mapper.GoodsMapper.getGoodsById。原因Mapper 接口和 XML 没有绑定成功。常见三种情况XML 文件的 namespace 和接口全限定名不一致XML 没被mapperLocations扫到比如放在了src/main/java下面而不是 resources接口方法名与 XML 里的 statement id 对不上。解决先检查 namespace 是否等于接口的全路径例如com.farm.mapper.GoodsMapper再确认 spring-context.xml 里mapperLocations路径是classpath:mapper/*.xml最后看方法名和 id 是否一字不差。改完 spring 配置必须重启Mapper 绑定是在容器启动时完成的热部署救不了。5.4 修改 JSP 后不生效运行的还是旧页面现象改了 JSP 的标题或表格列刷新浏览器没有任何变化甚至重启 Tomcat 还是老页面。原因JSP 是动态编译的Tomcat 的 work 目录缓存了上一次编译的 Java 文件和 class 文件。开发模式下 idea 没有把 resources 更新到 target或者浏览器强缓存了 .jsp 响应。另一个隐蔽原因是代码里对 JSP 响应设置了Cache-Control头导致浏览器端走了本地缓存。解决Idea 里 Build - Rebuild Project把 target 目录清理重建Tomcat 的 deployment 选 war exploded 并打开 On frame deactivation 的 Update classes and resources浏览器按 CtrlF5 强制刷新。如果还不行手动删掉 Tomcat 的work/Catalina目录再启动。5.5 Nginx 代理后页面能开但接口全部 404现象直接访问 Tomcat 端口一切正常接入 Nginx 做反向代理后JSP 页面能打开但表单提交和列表查询的路径全 404。原因JSP 本身由 Tomcat 渲染Nginx 只是透传所以页面能出来。404 的通常是/sales/xxx这类 Controller 路径大方向是请求没转发到 Tomcat 的上下文路径。例如 Tomcat 应用上下文是/farm而 Nginx 转发时把/farm剥掉了SpringMVC 收到的请求路径就对不上RequestMapping。解决Nginx 配置里转发时保留原始 URI不要用 rewrite 去掉前缀。另外要明确一件事nginx 本身不支持 JSP 解析所有.jsp页面和/api请求最终都要落到 TomcatNginx 只负责反向代理和静态资源分发。建议把/static/、/upload/这类路径直接用 Nginx 指向磁盘其余全部proxy_pass到 Tomcat 的 8080。6. 不推翻 JSP 的改造技巧先补接口回归、缓存和一张对账 SQL把 SSM JSP 这套跑通后很多人会纠结要不要推到重来换 Spring Boot。我建议先别急老系统最缺的不是框架是验证手段。我接手这类供销系统时第一件事不是重构而是先给 Service 层补上单元测试再用一个 JSON 接口做联调最后写对账 SQL 守住钱账一致。先给核心 Service 写测试。SSM 项目用 Spring Test 直接注入不启动 Tomcat 也能验证库存扣减逻辑这比在页面上点点点高效得多。RunWith(SpringJUnit4ClassRunner.class) ContextConfiguration(locations {classpath:spring-context.xml}) public class SalesServiceTest { Resource private SalesService salesService; Test public void testCreateOrderSuccess() { SalesOrder order new SalesOrder(); order.setOrderNo(SO20250101001); order.setCustomer(测试客户); salesService.createOrder(order, 1, 10); // 断言库存从 200 变为 190 } }然后加一个 JSON 接口不破坏 JSP 页面但给后续联调留后门。在 Controller 里用ResponseBody返回商品列表 JSON前端可以用 Postman 直接测不必每次打开浏览器。RequestMapping(/api/goods/list) ResponseBody public MapString, Object listGoodsApi() { ListGoods list goodsService.listAll(); MapString, Object result new HashMap(); result.put(code, 0); result.put(data, list); return result; }接口验证稳定后再考虑缓存。JSP 页面访问量上来时商品列表和分类是热点数据但注意别缓存库存数字缓存只放商品基础信息库存仍实时查库。这段改造是可选的重点是让系统先有可回归的测试和可对账的 SQL。最后是守住账目的对账 SQL。跑完十笔订单后用一条语句核对销售明细金额之和与销售单总额是否一致再查库存为负数的商品这两条检查能让供销系统的数据问题在半小时内暴露。-- 核对销售单总额与明细是否一致 SELECT order_id, SUM(amount) AS item_total FROM sales_item GROUP BY order_id HAVING item_total (SELECT total_amount FROM sales_order WHERE id order_id); -- 找出库存为负数的商品 SELECT goods_id, quantity FROM warehouse_stock WHERE quantity 0;老系统的价值不在技术新颖而在于业务闭环完整可验证。我现在的习惯是不管接手什么 JSP 老项目先把对账 SQL 和接口测试补上再谈重构。希望帮到你。本文还有配套的精品资源点击获取
返回列表