設計與實現(xiàn))
簡介本資源為基于SpringBoot的小型超市管理系統(tǒng)畢業(yè)設計全套資料面向計算機專業(yè)學生及Java初學者解決零售場景下數(shù)字化管理斷層、供應鏈信息孤島與動態(tài)決策支持不足等問題。系統(tǒng)采用B/S架構(gòu)集成商品分類管理、采購流程優(yōu)化與銷售數(shù)據(jù)分析等核心模塊前后端通過HTML、CSS、JavaScript與后端交互提升易用性與響應速度。壓縮包共402個文件約13.18MB以158個Java源碼、58個Vue組件、30個JavaScript腳本、25個XML配置及45張JPG、36張PNG界面截圖為主另含SQL建庫腳本、CSS樣式、JSON數(shù)據(jù)與項目說明文檔結(jié)構(gòu)完整便于二次開發(fā)。已有147人學習下載。讀者可獲得完整可運行的代碼工程、數(shù)據(jù)庫腳本與LW文檔通過銷售統(tǒng)計與經(jīng)營報表模塊理解決策支持邏輯適合作為課程設計或畢業(yè)設計參考快速掌握SpringBoot與MySQL整合開發(fā)流程。1. 一個超市老板的賬本為什么值得用 Spring Boot 重寫很多小型超市的日常運轉(zhuǎn)靠的是一本手寫臺賬加一臺老收銀機。老板白天盯貨、晚上對賬最怕兩件事一是庫存對不上二是月底盤不清。這個標題要解決的就是把「進貨、銷售、庫存、會員、員工」這幾本賬從紙面搬到一套能跑起來的 Web 系統(tǒng)里。技術選型上Spring Boot 負責把后端接口和業(yè)務邏輯快速搭起來MySQL 負責把商品、訂單、庫存這些結(jié)構(gòu)化數(shù)據(jù)存住前端用最樸素的頁面把操作暴露給收銀員和管理員。適合誰看適合正在做課程設計、畢業(yè)設計或者想給身邊小超市做一套輕量管理工具的開發(fā)者。它不追求高并發(fā)追求的是「一個人能維護、一臺普通電腦能跑、數(shù)據(jù)不丟」。2. 需求拆解與技術選型為什么是 Spring Boot MySQL2.1 小型超市的業(yè)務邊界到底在哪先把「小型」兩個字釘死。小型超市通常指單店經(jīng)營、SKU 在幾百到兩三千之間、收銀臺一到兩個、沒有連鎖調(diào)撥需求。它的核心業(yè)務只有四條線商品管理、庫存管理、銷售收銀、基礎報表。會員和員工管理屬于加分項不是第一版必須做的。我一般會先把業(yè)務畫成三張表的關系商品表是主數(shù)據(jù)庫存表跟著商品走銷售訂單表在收銀時產(chǎn)生并反向扣減庫存。會員表獨立存在訂單表里存一個會員 ID 做關聯(lián)即可。這樣拆的好處是后面加促銷、加退貨都只是在訂單表上做擴展不會把商品表搞臟。提示不要一上來就設計「供應商、采購、退貨、促銷、積分」全套。第一版把商品、庫存、訂單跑通比堆二十張空表有價值得多。2.2 為什么選 Spring Boot 而不是別的后端框架Spring Boot 在這個場景里的優(yōu)勢很具體起步依賴把 Web、JDBC、模板引擎一次性拉齊不用自己配 web.xml內(nèi)嵌 Tomcat 讓打包出來的 jar 直接java -jar就能跑省掉單獨裝容器的步驟application.properties里改幾行就能切換數(shù)據(jù)庫連接。對比一下常見選擇純 Servlet 寫起來太碎每個請求都要自己處理參數(shù)和轉(zhuǎn)發(fā)SSM 配置量大對只想做一個小系統(tǒng)的人來說心智負擔重Node 或 Python 方案不是不行但很多課程設計和答辯環(huán)境默認認 Java 技術棧Spring Boot 的兼容性最好。數(shù)據(jù)庫選 MySQL 的理由更直接免費、資料多、mysqldump備份一條命令搞定、圖形化工具遍地都是。小型超市的數(shù)據(jù)量MySQL 單機綽綽有余。2.3 項目分層與目錄結(jié)構(gòu)我習慣用 controller / service / mapper / entity 四層配合 resources 下的 mapper XML 和靜態(tài)頁面。目錄大致長這樣supermarket/ ├── src/main/java/com/example/supermarket/ │ ├── controller/ # 接收請求做參數(shù)校驗 │ ├── service/ # 業(yè)務邏輯事務在這里 │ ├── mapper/ # 數(shù)據(jù)庫訪問接口 │ └── entity/ # 與表對應的實體類 ├── src/main/resources/ │ ├── mapper/ # MyBatis 的 XML 映射文件 │ ├── static/ # 前端頁面與靜態(tài)資源 │ └── application.properties └── pom.xml分層的意義在于收銀邏輯扣庫存、生成訂單必須放在 service 層并加事務不能寫在 controller 里。否則一旦扣庫存成功、生成訂單失敗賬就亂了。這是小型系統(tǒng)最容易翻車的地方。2.4 依賴清單與版本選擇思路pom.xml里我一般只留必要的幾個依賴避免版本沖突dependencies !-- Web 能力內(nèi)嵌 Tomcat Spring MVC -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- 數(shù)據(jù)庫訪問MyBatis 比 JPA 更貼近手寫 SQL適合報表類查詢 -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.1/version /dependency !-- MySQL 驅(qū)動 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- 連接池HikariCP 是 Spring Boot 默認無需額外配置 -- /dependencies參數(shù)說明MyBatis 的版本要和你用的 Spring Boot 大版本匹配2.x 系列對應 Spring Boot 2.x 和 3.x 都能用但 3.x 項目建議用更新的 starter。MySQL 驅(qū)動從 8.0 開始包名是com.mysql.cj.jdbc.Driver連接串里要帶時區(qū)和字符集參數(shù)否則中文商品名會變問號。3. 數(shù)據(jù)庫設計五張核心表怎么落地3.1 商品表與庫存表的分工商品表存「是什么」庫存表存「有多少」。分開的理由是庫存會頻繁變動商品信息很少變分開后庫存表的更新不會鎖住商品表的讀。CREATE TABLE product ( id BIGINT PRIMARY KEY AUTO_INCREMENT, barcode VARCHAR(32) NOT NULL UNIQUE COMMENT 條形碼收銀掃碼用, name VARCHAR(64) NOT NULL COMMENT 商品名, category VARCHAR(32) COMMENT 分類, price DECIMAL(10,2) NOT NULL COMMENT 售價, status TINYINT DEFAULT 1 COMMENT 1上架 0下架 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE stock ( product_id BIGINT PRIMARY KEY, quantity INT NOT NULL DEFAULT 0 COMMENT 當前庫存, warn_line INT DEFAULT 10 COMMENT 預警線, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;參數(shù)說明DECIMAL(10,2)存金額不要用 float否則 0.10.2 會出現(xiàn)精度誤差對賬時能讓你懷疑人生。utf8mb4是為了支持 emoji 和生僻字商品名。庫存表用product_id做主鍵保證一個商品只有一條庫存記錄。3.2 訂單表與訂單明細表的一對多訂單表存「這一單的總信息」明細表存「這一單買了哪些商品」。這是典型的一對多也是數(shù)據(jù)庫多對多關系的落地方式。CREATE TABLE sale_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE COMMENT 訂單號, member_id BIGINT COMMENT 會員ID可空, total_amount DECIMAL(10,2) NOT NULL, pay_type TINYINT COMMENT 1現(xiàn)金 2掃碼, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE order_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL, product_id BIGINT NOT NULL, quantity INT NOT NULL, price DECIMAL(10,2) NOT NULL COMMENT 成交單價防止商品調(diào)價后歷史訂單失真, INDEX idx_order (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;參數(shù)說明明細表里冗余存一個price是因為商品售價會變歷史訂單必須保留成交時的價格。idx_order索引是為了按訂單查明細時不用全表掃。訂單號order_no建議用「日期隨機數(shù)」生成不要用自增 ID 直接暴露給用戶。3.3 會員表與員工表的最小字段集CREATE TABLE member ( id BIGINT PRIMARY KEY AUTO_INCREMENT, phone VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(32), points INT DEFAULT 0 COMMENT 積分, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE staff ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL COMMENT 存哈希不存明文, role TINYINT DEFAULT 1 COMMENT 1收銀員 2管理員 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;參數(shù)說明密碼字段長度給 64是為了存 SHA-256 或 BCrypt 的結(jié)果。千萬不要存明文這是最基礎的安全底線。員工表用role區(qū)分權(quán)限收銀員只能進收銀頁管理員才能改商品和看報表。3.4 連接配置與初始化腳本application.properties里幾個關鍵項spring.datasource.urljdbc:mysql://localhost:3306/supermarket?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai spring.datasource.usernameroot spring.datasource.passwordyour_password spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver mybatis.mapper-locationsclasspath:mapper/*.xml mybatis.configuration.map-underscore-to-camel-casetrue參數(shù)說明serverTimezone必須設否則連接 MySQL 8 會報時區(qū)錯誤。map-underscore-to-camel-casetrue讓數(shù)據(jù)庫的product_id自動映射到 Java 的productId省掉大量手動映射。初始化時把建表 SQL 放在schema.sql里Spring Boot 啟動會自動執(zhí)行方便反復重建測試環(huán)境。4. 核心功能實現(xiàn)從收銀到庫存扣減4.1 商品查詢與分頁接口商品列表要支持按名稱模糊查、按分類篩、分頁。MyBatis 的 XML 寫法select idlistProducts resultTypecom.example.supermarket.entity.Product SELECT id, barcode, name, category, price, status FROM product where if testkeyword ! null and keyword ! AND name LIKE CONCAT(%, #{keyword}, %) /if if testcategory ! null and category ! AND category #{category} /if /where ORDER BY id DESC LIMIT #{offset}, #{size} /select邏輯說明where標簽會自動處理第一個條件前的 AND避免拼接出WHERE AND的語法錯誤。CONCAT(%, #{keyword}, %)用#{}而不是${}能防止 SQL 注入。offset和size由 service 層根據(jù)頁碼算出來比如第 2 頁每頁 10 條offset 就是 10。4.2 收銀下單與庫存扣減的事務處理這是整個系統(tǒng)最不能出錯的地方。收銀動作包含三步生成訂單、寫明細、扣庫存。三步必須在一個事務里任何一步失敗全部回滾。Service public class SaleService { Autowired private SaleOrderMapper orderMapper; Autowired private OrderItemMapper itemMapper; Autowired private StockMapper stockMapper; Transactional(rollbackFor Exception.class) public String checkout(ListCartItem items, Long memberId) { // 1. 先校驗庫存避免扣減到一半才發(fā)現(xiàn)不夠 for (CartItem item : items) { Stock stock stockMapper.selectByProductId(item.getProductId()); if (stock null || stock.getQuantity() item.getQuantity()) { throw new RuntimeException(庫存不足 item.getProductId()); } } // 2. 生成訂單主記錄 SaleOrder order new SaleOrder(); order.setOrderNo(generateOrderNo()); order.setMemberId(memberId); order.setTotalAmount(calcTotal(items)); orderMapper.insert(order); // 3. 寫明細并扣庫存 for (CartItem item : items) { OrderItem oi new OrderItem(); oi.setOrderId(order.getId()); oi.setProductId(item.getProductId()); oi.setQuantity(item.getQuantity()); oi.setPrice(item.getPrice()); itemMapper.insert(oi); stockMapper.decrease(item.getProductId(), item.getQuantity()); } return order.getOrderNo(); } }邏輯說明Transactional(rollbackFor Exception.class)里的rollbackFor很關鍵默認只回滾運行時異常加上它之后受檢異常也會回滾。先校驗再扣減是為了在并發(fā)不高的小型超市場景下用最簡單的方式避免超賣。如果以后并發(fā)上來了再改成UPDATE stock SET quantity quantity - #{qty} WHERE product_id #{id} AND quantity #{qty}靠數(shù)據(jù)庫行鎖保證原子性。4.3 庫存預警與低庫存查詢庫存預警不需要定時任務收銀扣減后順手查一下就行或者管理員進首頁時查一次。SELECT p.name, s.quantity, s.warn_line FROM stock s JOIN product p ON p.id s.product_id WHERE s.quantity s.warn_line ORDER BY s.quantity ASC;邏輯說明用 JOIN 把商品名帶出來前端直接展示。quantity warn_line是預警條件warn_line默認 10管理員可以按商品單獨調(diào)。這個查詢走的是 stock 表全掃但庫存表通常只有幾千行性能沒問題。4.4 銷售報表的按日匯總老板最想看的是「今天賣了多少錢、哪些貨走得快」。SELECT DATE(create_time) AS day, COUNT(*) AS order_count, SUM(total_amount) AS total FROM sale_order WHERE create_time #{start} AND create_time #{end} GROUP BY DATE(create_time) ORDER BY day DESC;參數(shù)說明start和end用日期字符串傳入end取次日零點這樣能覆蓋當天全部訂單。DATE(create_time)會用到函數(shù)數(shù)據(jù)量大時索引失效但小型超市一天幾百單完全夠用。如果以后要優(yōu)化可以加一個sale_date冗余字段并建索引。5. 避坑與排查那些讓我返工三次的問題5.1 中文亂碼從連接串到建表字符集現(xiàn)象商品名存進去變成問號或者頁面顯示亂碼。原因三個地方任意一處字符集不對都會出問題——數(shù)據(jù)庫連接串沒帶characterEncodingutf8、建表時用了latin1、前端頁面沒聲明 UTF-8。解決連接串加useUnicodetruecharacterEncodingutf8建表統(tǒng)一DEFAULT CHARSETutf8mb4HTML 頭部加meta charsetUTF-8。三處對齊后基本不會再犯。5.2 事務不生效方法內(nèi)部調(diào)用踩的坑現(xiàn)象收銀時庫存扣了但訂單沒生成數(shù)據(jù)對不上。原因Transactional標注的方法被同一個類里的另一個方法直接調(diào)用Spring 的代理沒攔住事務根本沒開。解決把事務方法抽到獨立的 service 類里或者通過注入自身代理調(diào)用。我一般直接把收銀邏輯單獨放一個SaleServicecontroller 只調(diào)它不繞彎。5.3 庫存超賣并發(fā)下的經(jīng)典問題現(xiàn)象兩個收銀臺同時賣最后一件商品結(jié)果庫存變成 -1。原因先查后扣的寫法在并發(fā)下不安全兩個線程都查到庫存為 1都執(zhí)行扣減。解決把扣減改成帶條件的原子更新UPDATE stock SET quantity quantity - #{qty} WHERE product_id #{id} AND quantity #{qty}然后判斷影響行數(shù)為 0 就拋異?;貪L。這是最省事的方案不用引入分布式鎖。5.4 訂單號重復別用時間戳裸奔現(xiàn)象同一秒內(nèi)兩筆訂單訂單號撞了插入報唯一鍵沖突。原因直接用System.currentTimeMillis()做訂單號毫秒級并發(fā)會重復。解決訂單號用「yyyyMMddHHmmss 4 位隨機數(shù)」或者用數(shù)據(jù)庫自增 ID 拼前綴。更穩(wěn)妥的是單獨建一張序列表但小型系統(tǒng)用隨機數(shù)足夠。5.5 連接池耗盡忘了關流和長事務現(xiàn)象系統(tǒng)跑一段時間后報「無法獲取數(shù)據(jù)庫連接」。原因手寫 JDBC 時忘了關 Connection或者某個事務里做了耗時操作比如發(fā)短信導致連接被長時間占用。解決統(tǒng)一用 MyBatis 或 JdbcTemplate它們會管理連接釋放。事務方法里只做數(shù)據(jù)庫操作把外部調(diào)用挪到事務外。HikariCP 默認最大連接數(shù)是 10小型超市夠用但別在事務里 sleep。6. 進階技巧讓這套系統(tǒng)多活兩年第一版跑通之后真正決定這套系統(tǒng)能不能長期用的是幾個細節(jié)。我踩過的最大教訓是一開始圖省事把所有配置寫死在代碼里結(jié)果換臺機器部署就要改代碼重新打包。后來我把數(shù)據(jù)庫地址、端口、備份路徑全挪到application.properties用不同 profile 區(qū)分開發(fā)和生產(chǎn)部署時只改配置文件不動一行 Java。第二個習慣是給關鍵操作留日志。收銀、改價、刪商品這三類動作我都會在 service 里打一條帶操作人和時間的日志。不是為了審計是為了出問題時能回溯。有一次庫存對不上就是靠日志發(fā)現(xiàn)某筆訂單被重復提交了兩次。第三個技巧是備份。MySQL 的mysqldump配合系統(tǒng)的定時任務每天凌晨導出一份 SQL 文件保留最近七天。命令很簡單mysqldump -u root -p supermarket /backup/supermarket_$(date %Y%m%d).sql參數(shù)說明-u和-p后面跟賬號密碼supermarket是庫名重定向到帶日期的文件?;謴蜁r用mysql -u root -p supermarket 備份文件.sql。這個習慣看起來笨但真出事的時候它是唯一的后悔藥。最后一個建議是控制功能膨脹。小型超市系統(tǒng)的價值在于穩(wěn)定和簡單不在于功能多。每次想加新模塊之前先問一句老板真的會用嗎如果答案模糊就先不加。我見過太多課程設計堆了十幾個模塊最后連收銀主流程都跑不順。把商品、庫存、訂單這三條線打磨到不出錯比什么都強。希望幫到你。本文還有配套的精品資源點擊獲取