
简介这是以Java语言为基础采用SpringBoot集成MyBatis框架开发的超市仓库管理系统项目中整合了Apache Shiro安全组件与MyBatisPlus增强工具适用于高校课程设计、毕业设计以及想深入学习主流后端框架的开发者满足学习与工程实践双重需求。压缩包为zip格式共包含392个文件文件类型非常丰富其中Java源码有106个HTML页面49个JavaScript脚本42个CSS样式9个图片类资源较多还有SQL脚本、XML映射文件、JSON配置以及Maven构建相关的配置说明整体包体约10.3MB结构有层次便于查阅和学习附带完整数据库初始化脚本。该资源上线以来已有425人学习浏览说明适合初学者对照参考。通过该资源可以完整获得一个超市管理系统的源码与数据库脚本并可在IDEA中配合Navicat、Git、Maven进行环境搭建掌握从数据库设计、后端业务编写到前端页面渲染的整个流程能够快速启动项目并据此二次扩展。项目前端使用layui框架页面风格统一逻辑清晰还提供了必要的静态资源与样式配置。1. 拿到这套 SpringBoot 超市仓库管理系统源码先看清它在解决什么开学季超市仓库里最典型的乱象是Excel 记流水账库存永远是昨天的数据月底盘点对不上。这套基于 Java SpringBoot MyBatis 的超市仓库管理系统源码把收货、入库、销售出库、退供应商四件事统一进一套事务模型前端用 layui 出后台界面登录鉴权交给 Shiro数据库侧提供 SQL Server 脚本并兼容 MySQL 5.0项目构建走 Maven配置好就能跑。拆这份源码的价值不在于页面有多炫而在于商品表、库存表、出入库流水表和用户角色表之间的关系被理成了标准的仓库作业模型。适合做 Java 课设、毕业设计二次开发也适合中小团队拿它当仓库后台脚手架。要看懂这套系统核心是抓住一个点库存表与流水表之间靠什么保证数据一致以及 MyBatis 在写入链路里如何配合事务。2. 工程骨架与 SQL Server 数据模型先从源码里对齐构建环境2.1 Maven 工程结构与 SpringBoot 静态资源目录拿到源码后先看目录。这是一个典型的 SpringBoot MyBatis 前后端半分离项目前端资源没有拆成独立工程而是放在后端代码里由 SpringBoot 统一托管。常规做法是src/main/resources/static放 layui 那套页面资源页面通过 Ajax 调用接口Controller 负责页面跳转和 JSON 返回。supermarket/ ├── mvnw.cmd # Maven WrapperWindows 下不需要预装 Maven ├── pom.xml # 依赖与打包配置 ├── src/main/java/.../controller # 页面路由与 JSON 接口 ├── src/main/java/.../service # 事务层出入库核心逻辑 ├── src/main/java/.../mapper # MyBatis Mapper 接口 ├── src/main/resources/mapper # Mapper XML真正写 SQL 的地方 ├── src/main/resources/static # layui.css、laydate.css、dtree.css 等 ├── src/main/resources/application.yml # 数据源与 MyBatis 配置 └── src/main/resources/sql # SQL Server 初始化脚本这里容易踩坑的是static和mapper两个目录。static里那堆layui.css、layer.css、laydate.css、public.css、dtree.css说明页面是基于 layui 后台模板改出来的如果页面出现样式 404先确认 SpringBoot 是否把 static 映射到根路径。mapper目录是 MyBatis 的 SQL 存放点application.yml里的mapper-locations必须写对否则启动时直接报Invalid bound statement (not found)。源码自带mvnw.cmd说明构建环境已经用 Maven Wrapper 封装过团队里有人没装 Maven 也能用它拉依赖执行命令这个细节在课设答辩时值得单独提一下。2.2 SQL Server 核心表商品、库存、出入库单怎么关联仓库系统最重要的不是查询快而是“一张入库单提交下去库存表和流水表同时变化”。这套源码的表结构围绕五个业务对象设计关系如下。表名作用关键字段核心关联product商品档案id, sku, name, spec, unit, purchase_price, sale_price被库存和明细引用supplier供应商id, name, contact_phone, address入库单关联供货商stock实时库存id, product_id, warehouse_id, quantity, safe_stock商品仓库唯一stock_in_master入库单主表id, order_no, supplier_id, oper_user, create_time一对多明细stock_in_item入库明细id, master_id, product_id, quantity, price回写 stock 数量sys_user / sys_role / user_role账号与角色id, username, password, role_codeShiro 权限过滤设计上stock.quantity是系统中唯一允许保存“当前数量”的字段stock_in_item和stock_out_item只记录每一次变动明细。业务上不要在产品表里加库存字段否则改一次价格就会污染历史流水库存对账时全乱。safe_stock字段是给低库存报表用的阈值后面第 4 章的告警查询就靠它和quantity做比较。2.3 建表脚本与从 SQL Server 切到 MySQL 5.0 的关键差异源码包里给出的是 SQL Server 脚本默认库名建议建为supermarket。我实际部署时也遇到只有 MySQL 5.0 环境的情况所以把建表语句做了兼容调整核心部分如下。CREATE TABLE product ( id BIGINT IDENTITY(1,1) PRIMARY KEY, sku NVARCHAR(32) NOT NULL UNIQUE, name NVARCHAR(64) NOT NULL, spec NVARCHAR(64), unit NVARCHAR(8), purchase_price DECIMAL(10,2) DEFAULT 0, sale_price DECIMAL(10,2) DEFAULT 0, status TINYINT DEFAULT 1 ); CREATE TABLE stock ( id BIGINT IDENTITY(1,1) PRIMARY KEY, product_id BIGINT NOT NULL, warehouse_id BIGINT NOT NULL DEFAULT 1, quantity INT DEFAULT 0, safe_stock INT DEFAULT 10, CONSTRAINT uk_stock_product UNIQUE (product_id, warehouse_id) );ID 列使用IDENTITY(1,1)是 SQL Server 的自增写法换到 MySQL 5.0 时要改成AUTO_INCREMENTNVARCHAR改成VARCHARTINYINT两边通用。切换数据源时pom.xml里把 SQL Server 驱动换成 MySQL 驱动URL 写成jdbc:mysql://localhost:3306/supermarket?useUnicodetruecharacterEncodingutf8注意 MySQL 5.0 的驱动类名是com.mysql.jdbc.Driver不要用 8.x 的com.mysql.cj.jdbc.Driver否则会报 ClassNotFound 或 SSL 握手失败。提示数据库名如果改成了别的名字application.yml里的DatabaseName或 URL 路径必须同步改否则启动后连的是另一个空库页面能打开却查不到任何数据。3. SpringBoot MyBatis 的出入库核心链路从表结构到可执行代码3.1 实体类、Mapper 接口与 XML 文件的映射关系上面建好的表到 Java 代码里就是实体类。源码的 Mapper 层采用“接口定义 XML 写 SQL”的方式接口方法名必须与 XML 的id一致这是 MyBatis 约定大于配置的核心。Data public class Stock { private Long id; private Long productId; private Long warehouseId; private Integer quantity; private Integer safeStock; }public interface StockMapper { Stock selectByProductAndWarehouse(Param(productId) Long productId, Param(warehouseId) Long warehouseId); int decreaseStockByLock(Param(delta) Integer delta, Param(productId) Long productId, Param(warehouseId) Long warehouseId); }Data由 Lombok 生成 getter 和 setter让实体类保持干净。Param注解在 MyBatis 多参数方法里必须写否则 XML 中拿不到参数名启动或调用时报There is no getter for property named productId。Mapper 接口本身不写 SQL真正逻辑在src/main/resources/mapper/StockMapper.xmlSpringBoot 启动时通过MapperScan扫描接口并绑定代理对象Service 层直接注入即可。3.2 入库事务一张入库单同时写主表、明细和库存入库是仓库系统里最容易出现数据不一致的环节。假设先插明细再改库存中途断电或抛异常就会出现库存加了但明细没落库。源码在 Service 层使用Transactional把三个动作框进同一事务这是整条链路的可靠性根基。Service public class StockInService { Autowired private StockInMapper stockInMapper; Autowired private StockMapper stockMapper; Transactional(rollbackFor Exception.class) public void doStockIn(StockInMaster master, ListStockInItem items) { stockInMapper.insertMaster(master); for (StockInItem item : items) { stockInMapper.insertItem(item); Stock stock stockMapper.selectByProductAndWarehouse( item.getProductId(), master.getWarehouseId()); int newQty stock.getQuantity() item.getQuantity(); stockMapper.decreaseStockByLock(item.getQuantity(), item.getProductId(), master.getWarehouseId()); } } }等等上面的写法有问题查询旧值再加回写并发下会互相覆盖。更稳妥的做法是直接写更新语句让数据库在单行上完成原子加减。下面这个 XML 方法更安全。update iddecreaseStockByLock UPDATE stock SET quantity quantity - #{delta} WHERE product_id #{productId} AND warehouse_id #{warehouseId} AND quantity gt; #{delta} /update这里把“查出旧值、计算新值、写回”三步压缩成一条 UPDATE数据库对同一行的更新会自动加锁天然避免并发覆盖。WHERE里带quantity #{delta}可以防止库存扣成负数。gt;是 XML 文件中的标准转义写错会导致 XML 解析直接失败。事务中如果主表、明细、库存任何一个步骤失败rollbackFor Exception.class都会让整单回滚保证三张表最终要么全部成功要么全部不动。3.3 MyBatis 动态 SQL 与缓存配置的边界实际业务里入库单明细数量是不固定的所以 XML 中大量使用if和foreach做动态 SQL。以查询商品为例按名称、SKU、分类多条件筛选时常见写法是这样。select idselectProductByCondition resultTypecom.supermarket.entity.Product SELECT id, sku, name, spec, unit, purchase_price, sale_price, status FROM product where if testname ! null and name ! AND name LIKE CONCAT(%, #{name}, %) /if if testsku ! null and sku ! AND sku #{sku} /if if teststatus ! null AND status #{status} /if /where ORDER BY id DESC /selectwhere标签会自动去掉第一个条件前的AND比手动拼接WHERE 11干净得多。参数name、sku、status由前端传参直接映射。注意LIKE拼接时使用CONCAT而不是字符串加法这样在 SQL Server 和 MySQL 下都能跑。关于 MyBatis 一二级缓存一级缓存默认开在 SqlSession 生命周期内同一个事务里重复查同一条 SQL 会直接读缓存二级缓存默认关闭开启需要在 XML 写cache/。出入库这种写多读多、且对实时一致性要求高的场景我不建议开启二级缓存否则一个线程扣了库存另一线程在同一 SQL 上可能读到旧缓存值。保持默认配置让每次库存查询直接落库才是这里正确的取舍。4. 基于 Shiro 的后台权限控制与低库存报表从表数据到页面4.1 用户、角色、权限三张表在 Shiro 中如何落地超市管理系统一定不止一个操作员仓库管理员、收银员、店长看到的功能应当不同。源码使用sys_user、sys_role、user_role三张表完成 RBAC 设计角色表结构如下。字段类型说明idBIGINT主键role_nameNVARCHAR(32)角色名称用于界面显示role_codeNVARCHAR(32)角色标识用于 URL 拦截statusTINYINT启用状态1 启用 0 停用remarkNVARCHAR(255)备注用户登录时Shiro 的 Realm 会调用doGetAuthenticationInfo验证账号密码然后再从用户表 join 角色表查出角色标识放回 Subject。Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username (String) principals.getPrimaryPrincipal(); ListString roles userMapper.selectRolesByUsername(username); SimpleAuthorizationInfo info new SimpleAuthorizationInfo(); info.addRoles(roles); return info; }这里的selectRolesByUsername在 XML 里通过sys_user、user_role、sys_role三表 join 查出role_code列表。角色标识建议用admin、stock、cashier这类英文业务代号不要用中文直接入库否则后续在过滤链里写roles[仓库管理员]时一旦页面编码或库表排序规则不一致角色匹配就会失效。4.2 URL 过滤链和页面路由的权限控制权限不散落在每个 Controller 方法里而是统一在ShiroConfig的filterChainDefinitionMap中声明。中间层配置我一般这样加。Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean factory new ShiroFilterFactoryBean(); factory.setSecurityManager(securityManager); MapString, String filter new LinkedHashMap(); filter.put(/login, anon); filter.put(/logout, logout); filter.put(/admin/**, roles[admin]); filter.put(/stock/**, roles[admin,stock]); filter.put(/**, authc); factory.setFilterChainDefinitionMap(filter); factory.setLoginUrl(/login); return factory; }LinkedHashMap保证过滤规则从上到下短路匹配/**必须放在最后否则前面写的具体规则全部失效。roles[admin,stock]表示 admin 和 stock 两个角色都能访问多个角色用逗号分隔。前端 layui 页面如果请求被拦截返回 401需要在 Ajax 层做统一跳转处理不能只弹错误信息否则用户看到接口报错却不知道要重新登录这种问题在联调阶段最常见。4.3 低库存告警与 MyBatis 分页报表的组合写法报表页要列出“当前库存低于安全库存”的商品不能靠 Java 内存过滤因为数据量大了以后一次全表加载会压垮数据库。正确做法是让 SQL Server 完成过滤和聚合。SELECT p.sku, p.name, p.unit, s.quantity, s.safe_stock, (s.safe_stock - s.quantity) AS shortage_qty FROM stock s JOIN product p ON s.product_id p.id WHERE s.quantity s.safe_stock ORDER BY shortage_qty DESCshortage_qty是计算列不能直接在 WHERE 中引用所以过滤条件还是要写s.quantity s.safe_stock。分页部分源码如果集成了 MyBatis-Plus就在配置类里注册PaginationInnerInterceptorController 接收current和size参数返回IPageProductStockVO。GetMapping(/stock/alarm) public Result pageAlarm(RequestParam(defaultValue 1) long current, RequestParam(defaultValue 10) long size) { PageProductStockVO page new Page(current, size); IPageProductStockVO result stockMapper.selectAlarmPage(page); return Result.success(result); }这里传入的Page对象会被 MyBatis-Plus 拦截器解析成带有LIMIT的分页语句同时自动生成 COUNT 查询。如果发现分页总数对、但当前页数据重复通常是 join 后分页主键不唯一造成的解决办法是分页查询 SQL 和 COUNT 查询 SQL 分开写避免 MyBatis-Plus 自动生成的 COUNT 包含多余字段。5. 本地跑通源码的三个关键配置与排查实践5.1 application.yml 里的数据源与 MyBatis 核心参数把源码导入 IDEA 后最先要改的是application.yml。SpringBoot 内置 Tomcat 8.5 可以直接用不需要单独装外部容器配置文件里的关键项如下。spring: datasource: driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver url: jdbc:sqlserver://localhost:1433;DatabaseNamesupermarket;encryptfalse username: sa password: root123 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.supermarket.entity configuration: map-underscore-to-camel-case: truemap-underscore-to-camel-case不配置的话SQL Server 返回的product_id、create_time这类字段没法自动映射到实体的productId、createTime页面表格全是 null。这个参数配上后可以省掉大量手写resultMap的工作。encryptfalse是连接 SQL Server 2019 及以上版本时的常见配置不开加密连接会出现 TLS 握手失败。5.2 启动顺序与库存核对流程源码自带mvnw.cmd在 Windows 下可以直接用它构建不要求电脑上预装 Maven。我一般这样执行mvnw.cmd clean package -DskipTests java -jar target/supermarket.jar先打包再运行的好处是编译错误和运行时错误能分阶段暴露日志定位更清晰。项目启动后先做三件事验证系统是否真实可用打开登录页输入 admin 账户能进入后台说明 Shiro 过滤链没有误拦静态资源。手动录入一张入库单然后去数据库中查询stock_in_master、stock_in_item、stock三张表确认数量同步增加。人为把safe_stock调大返回低库存报表页刷新确认告警商品能出现在列表第一页。5.3 从 SQL Server 切换到 MySQL 5.0 时的三个差异点如果课设环境要求用 MySQL 5.0 重新建库差异集中在自增主键、字段类型、驱动配置三处。SQL Server 的IDENTITY(1,1)要替换成AUTO_INCREMENT驱动换成com.mysql.jdbc.DriverURL 改成jdbc:mysql://localhost:3306/supermarket?useUnicodetruecharacterEncodingUTF-8。MySQL 5.0 对DATETIME默认值处理比 SQL Server 严格建表时日期字段尽量不要写DEFAULT CURRENT_TIMESTAMP改在 Java 代码里用new Date()生成时间MyBatis XML 配合if testcreateTime ! null动态插入兼容性更稳。切库完成后重新执行上面核对步骤里的入库操作然后在 SQL Server Management Studio 或 Navicat 里把主表、明细表、库存表三张表再查一遍确认数量变动一致后再进入下一步功能验收。本文还有配套的精品资源点击获取