戰(zhàn):從環(huán)境搭建到訂單事務(wù)的完整解析)
簡(jiǎn)介這份資源是面向計(jì)算機(jī)專業(yè)學(xué)生與JavaWeb初學(xué)者的一套SSM框架零食商城系統(tǒng)完整項(xiàng)目適用于畢業(yè)設(shè)計(jì)、課程設(shè)計(jì)或框架練手場(chǎng)景。項(xiàng)目基于SpringSpringMVCMyBatis實(shí)現(xiàn)涵蓋用戶購(gòu)物模塊與后臺(tái)管理員模塊配套論文完整覆蓋需求分析、系統(tǒng)設(shè)計(jì)、數(shù)據(jù)庫(kù)E-R圖與數(shù)據(jù)表設(shè)計(jì)、詳細(xì)設(shè)計(jì)及測(cè)試說(shuō)明可幫助讀者理解從需求到落地的完整開發(fā)流程。壓縮包共1690個(gè)文件約128.98MB包含182個(gè)java源碼、88個(gè)jsp頁(yè)面、114個(gè)xml配置、128個(gè)js與104個(gè)css前端資源以及138個(gè)jar依賴、532個(gè)class編譯文件、292張jpg與32張png圖片素材另有1個(gè)sql建庫(kù)腳本和1個(gè)doc論文文檔目錄結(jié)構(gòu)清晰便于按模塊查閱與二次開發(fā)。目前已有6164人學(xué)習(xí)下載適合需要參考SSM分層架構(gòu)、數(shù)據(jù)庫(kù)建模與商城業(yè)務(wù)實(shí)現(xiàn)的讀者對(duì)照學(xué)習(xí)。1. 從一份 SSM 零食商城源碼說(shuō)起它到底能幫你解決什么如果你正在搜「javaweb零食商城系統(tǒng)設(shè)計(jì)與實(shí)現(xiàn)ssm項(xiàng)目含論文和源碼」大概率不是單純想買個(gè)零食而是被三件事卡住了畢業(yè)設(shè)計(jì)要交一個(gè)能跑起來(lái)的 JavaWeb 項(xiàng)目、簡(jiǎn)歷上需要一個(gè)能講清楚業(yè)務(wù)閉環(huán)的完整案例、或者課程設(shè)計(jì)要求你從零搭一套帶數(shù)據(jù)庫(kù)的電商系統(tǒng)。零食商城這個(gè)選題之所以被反復(fù)用是因?yàn)樗鼧I(yè)務(wù)邊界清晰——商品、分類、購(gòu)物車、訂單、用戶五個(gè)模塊就能撐起一個(gè)完整的 MVC 分層既不會(huì)像秒殺系統(tǒng)那樣復(fù)雜到失控也不會(huì)像留言板那樣單薄到?jīng)]東西可寫。SSM 指的是 Spring SpringMVC MyBatis 這套組合在 SpringBoot 一統(tǒng)天下之前它是 JavaWeb 項(xiàng)目最主流的落地方式也是很多高校課程設(shè)計(jì)和企業(yè)老項(xiàng)目維護(hù)的真實(shí)技術(shù)棧。你拿到的這份「含論文和源碼」的資料核心價(jià)值不在于代碼有多優(yōu)雅而在于它提供了一個(gè)可運(yùn)行、可拆解、可二次開發(fā)的完整參照物。這篇文章不替你復(fù)述論文而是把這份源碼從環(huán)境搭建、分層結(jié)構(gòu)、核心業(yè)務(wù)實(shí)現(xiàn)到常見(jiàn)翻車點(diǎn)按一線開發(fā)的視角拆開講一遍讓你能真正把它跑起來(lái)、改得動(dòng)、講得出。適合誰(shuí)看手里已經(jīng)有這份源碼但跑不起來(lái)的同學(xué)、想照著它自己重寫一遍的開發(fā)者、以及需要把 SSM 分層講清楚應(yīng)對(duì)答辯的人。下面從環(huán)境配置開始一步步來(lái)。2. 把 SSM 零食商城在本地跑起來(lái)環(huán)境、數(shù)據(jù)庫(kù)與啟動(dòng)順序2.1 先確認(rèn)技術(shù)棧版本別讓版本沖突背鍋拿到一份 SSM 源碼第一件事不是急著導(dǎo)入 IDE而是先看pom.xml或lib目錄確認(rèn)它用的是 Maven 還是傳統(tǒng) Web 項(xiàng)目結(jié)構(gòu)。這兩種結(jié)構(gòu)的導(dǎo)入方式完全不同搞錯(cuò)了會(huì)在 IDEA 里折騰半天。常見(jiàn)做法是先看項(xiàng)目根目錄有沒(méi)有pom.xml有就是 Maven 項(xiàng)目沒(méi)有就是傳統(tǒng)的 Dynamic Web Project需要手動(dòng)配 Tomcat 和依賴。版本這塊是血淚經(jīng)驗(yàn)的重災(zāi)區(qū)。SSM 項(xiàng)目里最容易出問(wèn)題的是三組版本JDK、Spring 和 MyBatis。JDK 建議用 8 或 11Spring 5.x 配 JDK 8 最穩(wěn)Spring 4.x 就別上 JDK 17 了反射和模塊化會(huì)直接讓你啟動(dòng)失敗。MyBatis 和 MyBatis-Spring 的版本要匹配比如 MyBatis 3.5.x 通常配 mybatis-spring 2.0.x。下面是一個(gè)典型的依賴版本組合你可以對(duì)照自己項(xiàng)目里的pom.xml檢查!-- pom.xml 關(guān)鍵依賴版本按項(xiàng)目實(shí)際情況調(diào)整 -- properties spring.version5.3.20/spring.version mybatis.version3.5.10/mybatis.version mybatis.spring.version2.0.7/mybatis.spring.version mysql.connector.version8.0.29/mysql.connector.version jdk.version1.8/jdk.version /properties這段配置里spring.version控制整個(gè) Spring 生態(tài)的版本SpringMVC 和 Spring JDBC 都跟著它走mybatis.spring.version是 MyBatis 和 Spring 的粘合層版本不匹配最常見(jiàn)的報(bào)錯(cuò)是NoClassDefFoundError或者 SqlSessionFactory 注入失敗。MySQL 驅(qū)動(dòng) 8.x 和 5.x 的差別也很大8.x 必須寫com.mysql.cj.jdbc.DriverURL 還要帶時(shí)區(qū)和 SSL 參數(shù)5.x 則是com.mysql.jdbc.Driver。這一步確認(rèn)清楚后面能省掉一半的排查時(shí)間。2.2 數(shù)據(jù)庫(kù)建表與初始數(shù)據(jù)導(dǎo)入零食商城的數(shù)據(jù)庫(kù)一般包含這幾張核心表用戶表user、商品表product、商品分類表category、購(gòu)物車表cart、訂單表orders、訂單明細(xì)表order_item。源碼里通常會(huì)帶一個(gè).sql文件直接導(dǎo)入即可。但導(dǎo)入之前要注意字符集零食商品名里經(jīng)常有中文和特殊符號(hào)字符集設(shè)錯(cuò)會(huì)出現(xiàn)亂碼。-- 建庫(kù)時(shí)指定字符集避免中文商品名亂碼 CREATE DATABASE snack_mall DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; -- 導(dǎo)入前先切庫(kù) USE snack_mall; -- 商品表核心字段示例注意價(jià)格用 DECIMAL 不用 FLOAT CREATE TABLE product ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL COMMENT 商品名稱, category_id INT NOT NULL COMMENT 分類ID, price DECIMAL(10,2) NOT NULL COMMENT 單價(jià)保留兩位小數(shù), stock INT DEFAULT 0 COMMENT 庫(kù)存, image_url VARCHAR(255) COMMENT 主圖路徑, status TINYINT DEFAULT 1 COMMENT 1上架 0下架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;這里有兩個(gè)參數(shù)值得說(shuō)清楚。價(jià)格字段用DECIMAL(10,2)而不是FLOAT是因?yàn)楦↑c(diǎn)數(shù)在金額計(jì)算上會(huì)出現(xiàn)精度丟失0.1 0.2 不等于 0.3 這種問(wèn)題在訂單結(jié)算時(shí)是致命的。status字段用TINYINT做邏輯刪除或上下架標(biāo)記比物理刪除安全得多用戶的歷史訂單里還能查到已下架商品的信息。導(dǎo)入完成后用SELECT COUNT(*) FROM product;確認(rèn)數(shù)據(jù)條數(shù)如果為 0 說(shuō)明導(dǎo)入腳本沒(méi)執(zhí)行成功檢查一下 SQL 文件里有沒(méi)有USE語(yǔ)句或者編碼問(wèn)題。2.3 IDEA 導(dǎo)入與 Tomcat 配置的完整步驟Maven 項(xiàng)目的導(dǎo)入相對(duì)標(biāo)準(zhǔn)IDEA 里選Open定位到pom.xml所在目錄等依賴下載完。如果依賴下載卡住檢查 Maven 的settings.xml有沒(méi)有配國(guó)內(nèi)鏡像。傳統(tǒng) Web 項(xiàng)目則要選Import Module然后手動(dòng)指定 Web 根目錄通常是web或WebContent再在Project Structure里把WEB-INF/lib下的 jar 加到模塊依賴。Tomcat 配置這一步新手最容易翻車的是 Artifact 沒(méi)配對(duì)。在Run/Debug Configurations里新建 Tomcat ServerDeployment標(biāo)簽頁(yè)添加 Artifact要選xxx:war exploded而不是xxx:war前者支持熱部署改 JSP 不用重啟。Application context建議設(shè)成/或/snack設(shè)成/snack的話訪問(wèn)路徑就是http://localhost:8080/snack/。# 啟動(dòng)前確認(rèn)端口沒(méi)被占用Tomcat 默認(rèn) 8080 # Linux/Mac lsof -i:8080 # Windows netstat -ano | findstr :8080如果 8080 被占用要么改 Tomcat 端口要么殺掉占用進(jìn)程。啟動(dòng)后控制臺(tái)沒(méi)有報(bào)錯(cuò)但頁(yè)面 404八成是Application context和你的訪問(wèn)路徑對(duì)不上或者web.xml里DispatcherServlet的url-pattern配成了/之外的路徑??吹紺lassNotFoundException就先檢查依賴有沒(méi)有真正打進(jìn)WEB-INF/lib或 Maven 的target目錄。啟動(dòng)成功、首頁(yè)能打開、數(shù)據(jù)庫(kù)能查到商品列表這三步都過(guò)了才算真正跑通。3. SSM 分層結(jié)構(gòu)拆解Controller、Service、Mapper 各管什么3.1 一次商品列表請(qǐng)求的完整調(diào)用鏈理解 SSM 分層最好的方式不是背概念而是跟著一次請(qǐng)求走一遍。用戶在瀏覽器訪問(wèn)/product/list請(qǐng)求先到web.xml里配置的DispatcherServlet它根據(jù)spring-mvc.xml里的組件掃描和注解找到ProductController中標(biāo)注了RequestMapping(/product/list)的方法。Controller 不直接查數(shù)據(jù)庫(kù)而是調(diào)用ProductService接口的getProductList()方法。Service 層做業(yè)務(wù)判斷——比如過(guò)濾掉已下架商品、計(jì)算分頁(yè)參數(shù)——然后調(diào)用ProductMapper接口。Mapper 是 MyBatis 的代理接口它對(duì)應(yīng)一個(gè) XML 文件里面寫著真正的 SQL。SQL 執(zhí)行結(jié)果通過(guò) MyBatis 映射成Product實(shí)體對(duì)象一路返回給 ControllerController 把數(shù)據(jù)塞進(jìn)Model最后DispatcherServlet轉(zhuǎn)發(fā)到product_list.jsp渲染。這條鏈路里每一層只干自己的事Controller 管請(qǐng)求參數(shù)和視圖跳轉(zhuǎn)Service 管業(yè)務(wù)邏輯和事務(wù)Mapper 管 SQL 和數(shù)據(jù)映射。分層的好處是改 SQL 不用動(dòng)業(yè)務(wù)代碼改業(yè)務(wù)規(guī)則不用動(dòng) Controller。壞處是新手容易把邏輯寫錯(cuò)層——比如在 Controller 里直接調(diào) Mapper或者在 Service 里寫HttpServletRequest這兩種都會(huì)讓代碼變得難以維護(hù)和測(cè)試。3.2 配置文件三件套web.xml、spring-mvc.xml、applicationContext.xmlSSM 項(xiàng)目的配置分散在三個(gè)文件里各管一攤。web.xml是 Web 應(yīng)用的入口負(fù)責(zé)注冊(cè)DispatcherServlet、ContextLoaderListener和字符編碼過(guò)濾器。spring-mvc.xml管 Web 層配置組件掃描只掃 Controller、視圖解析器、靜態(tài)資源放行。applicationContext.xml管 Service 和 Dao 層配置數(shù)據(jù)源、SqlSessionFactory、事務(wù)管理器、組件掃描掃 Service。!-- web.xml 關(guān)鍵片段注冊(cè) Spring 上下文和 MVC 分發(fā)器 -- context-param param-namecontextConfigLocation/param-name param-valueclasspath:applicationContext.xml/param-value /context-param listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener 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-mappingContextLoaderListener加載的是父容器applicationContext.xmlDispatcherServlet加載的是子容器spring-mvc.xml。子容器能訪問(wèn)父容器的 Bean反過(guò)來(lái)不行。所以 Service 能被 Controller 注入但 Controller 不能被 Service 注入。如果你在 Service 里用Autowired注入了一個(gè) Controller啟動(dòng)就會(huì)報(bào)找不到 Bean。url-pattern配/表示攔截所有請(qǐng)求靜態(tài)資源css、js、圖片需要在spring-mvc.xml里用mvc:resources或mvc:default-servlet-handler放行否則頁(yè)面樣式會(huì)全部丟失。3.3 MyBatis 映射文件與實(shí)體類的對(duì)應(yīng)關(guān)系MyBatis 的核心是「接口 XML」的映射模式。ProductMapper是一個(gè)接口方法名對(duì)應(yīng) XML 里的id參數(shù)類型對(duì)應(yīng)parameterType返回值對(duì)應(yīng)resultType或resultMap。零食商城里最典型的查詢是帶分類篩選和分頁(yè)的商品列表!-- ProductMapper.xml 分頁(yè)查詢商品配合 PageHelper 使用 -- select idselectByCategory resultMapBaseResultMap SELECT id, name, category_id, price, stock, image_url, status FROM product WHERE status 1 if testcategoryId ! null AND category_id #{categoryId} /if ORDER BY create_time DESC /selectresultMap比resultType多一層字段映射能力當(dāng)數(shù)據(jù)庫(kù)列名和實(shí)體屬性名不一致時(shí)比如category_id對(duì)categoryId要么開mapUnderscoreToCamelCase要么手寫resultMap。if標(biāo)簽是動(dòng)態(tài) SQL 的基礎(chǔ)test里的categoryId是接口方法的參數(shù)名多個(gè)參數(shù)要用Param注解指定名字否則 MyBatis 會(huì)按param1、param2來(lái)取很容易寫錯(cuò)。分頁(yè)如果用了 PageHelper 插件SQL 里不用寫LIMIT插件會(huì)在執(zhí)行前自動(dòng)拼接但要注意 PageHelper 只對(duì)緊跟其后的第一條查詢生效中間插了別的查詢就會(huì)失效。4. 零食商城核心業(yè)務(wù)實(shí)現(xiàn)購(gòu)物車、訂單與庫(kù)存扣減4.1 購(gòu)物車的數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)與增刪改查購(gòu)物車有兩種常見(jiàn)實(shí)現(xiàn)存 Session 和存數(shù)據(jù)庫(kù)。存 Session 簡(jiǎn)單但換設(shè)備就丟存數(shù)據(jù)庫(kù)能持久化但要多一張表。零食商城這類項(xiàng)目通常用數(shù)據(jù)庫(kù)方案表結(jié)構(gòu)大致是cart(id, user_id, product_id, quantity, create_time)用user_id product_id做唯一索引避免同一商品重復(fù)插入。// CartServiceImpl 添加購(gòu)物車的核心邏輯 Override public void addToCart(Integer userId, Integer productId, Integer quantity) { // 先查是否已在購(gòu)物車 Cart existing cartMapper.selectByUserAndProduct(userId, productId); if (existing ! null) { // 已存在則累加數(shù)量 existing.setQuantity(existing.getQuantity() quantity); cartMapper.updateQuantity(existing); } else { Cart cart new Cart(); cart.setUserId(userId); cart.setProductId(productId); cart.setQuantity(quantity); cartMapper.insert(cart); } }這段邏輯的關(guān)鍵在于「先查后寫」但嚴(yán)格來(lái)說(shuō)在高并發(fā)下會(huì)有競(jìng)態(tài)問(wèn)題——兩個(gè)請(qǐng)求同時(shí)查到不存在然后都執(zhí)行 insert唯一索引會(huì)攔下第二個(gè)并拋異常。零食商城這種場(chǎng)景并發(fā)不高用try-catch兜住DuplicateKeyException再轉(zhuǎn)成更新操作就夠了。如果要做嚴(yán)謹(jǐn)可以用INSERT ... ON DUPLICATE KEY UPDATE一條 SQL 搞定。數(shù)量字段要校驗(yàn)上限比如單商品最多 99 件防止用戶手輸一個(gè)超大數(shù)字把庫(kù)存邏輯搞崩。4.2 下單流程從購(gòu)物車到訂單表的事務(wù)處理下單是整個(gè)系統(tǒng)里最需要事務(wù)的地方。用戶點(diǎn)「結(jié)算」系統(tǒng)要做四件事生成訂單主記錄、把購(gòu)物車商品轉(zhuǎn)成訂單明細(xì)、扣減商品庫(kù)存、清空購(gòu)物車。這四步必須在一個(gè)事務(wù)里任何一步失敗都要回滾否則會(huì)出現(xiàn)「訂單生成了但庫(kù)存沒(méi)扣」或者「庫(kù)存扣了但購(gòu)物車沒(méi)清」的臟數(shù)據(jù)。// OrderServiceImpl 下單核心方法Transactional 保證原子性 Override Transactional(rollbackFor Exception.class) public Integer createOrder(Integer userId, ListInteger cartIds) { // 1. 查詢購(gòu)物車選中項(xiàng) ListCart cartList cartMapper.selectByIds(cartIds); if (cartList.isEmpty()) { throw new BusinessException(購(gòu)物車為空); } // 2. 生成訂單主記錄 Orders order new Orders(); order.setUserId(userId); order.setOrderNo(generateOrderNo()); order.setStatus(0); // 0待支付 orderMapper.insert(order); // 3. 轉(zhuǎn)訂單明細(xì)并扣庫(kù)存 for (Cart cart : cartList) { Product product productMapper.selectById(cart.getProductId()); if (product.getStock() cart.getQuantity()) { throw new BusinessException(庫(kù)存不足 product.getName()); } // 扣庫(kù)存用帶條件的 UPDATE 防止超賣 int affected productMapper.reduceStock(cart.getProductId(), cart.getQuantity()); if (affected 0) { throw new BusinessException(庫(kù)存扣減失敗); } OrderItem item new OrderItem(); item.setOrderId(order.getId()); item.setProductId(cart.getProductId()); item.setQuantity(cart.getQuantity()); item.setPrice(product.getPrice()); orderItemMapper.insert(item); } // 4. 清空已下單的購(gòu)物車項(xiàng) cartMapper.deleteByIds(cartIds); return order.getId(); }Transactional的rollbackFor Exception.class很關(guān)鍵默認(rèn) Spring 只對(duì)RuntimeException回滾如果拋的是受檢異常事務(wù)不會(huì)回滾這個(gè)坑很多人踩過(guò)??蹘?kù)存的 SQL 要寫成UPDATE product SET stock stock - #{qty} WHERE id #{id} AND stock #{qty}用affected 0判斷是否扣減成功這樣即使并發(fā)也不會(huì)把庫(kù)存扣成負(fù)數(shù)。訂單號(hào)生成不要用時(shí)間戳裸拼同一毫秒的并發(fā)會(huì)重復(fù)常見(jiàn)做法是時(shí)間戳 用戶 ID 后幾位 隨機(jī)數(shù)。4.3 庫(kù)存扣減的并發(fā)安全與超賣防范超賣是電商系統(tǒng)的經(jīng)典問(wèn)題。假設(shè)庫(kù)存只剩 1 件兩個(gè)用戶同時(shí)下單如果代碼是「先查庫(kù)存再扣減」兩個(gè)請(qǐng)求都查到庫(kù)存為 1都認(rèn)為可以下單最后庫(kù)存變成 -1。解決辦法有三種悲觀鎖SELECT ... FOR UPDATE、樂(lè)觀鎖版本號(hào)、條件更新WHERE stock qty。零食商城項(xiàng)目推薦用條件更新實(shí)現(xiàn)最簡(jiǎn)單性能也夠用。上面reduceStock的 SQL 就是這種方案把判斷和扣減合并成一條原子操作數(shù)據(jù)庫(kù)的行鎖會(huì)保證同一行的更新串行執(zhí)行。樂(lè)觀鎖適合更新沖突少的場(chǎng)景需要在表里加version字段每次更新帶上版本號(hào)比對(duì)。悲觀鎖FOR UPDATE會(huì)鎖行到事務(wù)結(jié)束并發(fā)高時(shí)容易造成鎖等待零食商城這種量級(jí)不太需要。注意庫(kù)存扣減和訂單創(chuàng)建必須在同一個(gè)事務(wù)里且扣庫(kù)存的 UPDATE 要放在訂單明細(xì)插入之前這樣庫(kù)存不足時(shí)能盡早拋異常減少無(wú)效的數(shù)據(jù)庫(kù)操作。還有一個(gè)容易被忽略的點(diǎn)取消訂單和超時(shí)未支付要回補(bǔ)庫(kù)存。很多源碼只做了扣減沒(méi)做回補(bǔ)用戶取消訂單后庫(kù)存就永久少了?;匮a(bǔ)邏輯要判斷訂單狀態(tài)只有「待支付」和「已取消」之間的轉(zhuǎn)換才回補(bǔ)已支付的訂單取消要走退款流程不能簡(jiǎn)單加庫(kù)存。5. 這份源碼最容易翻車的地方5 個(gè)高頻問(wèn)題排查5.1 啟動(dòng)報(bào) 404首頁(yè)和接口都訪問(wèn)不到現(xiàn)象Tomcat 啟動(dòng)無(wú)報(bào)錯(cuò)但訪問(wèn)任何路徑都是 404。原因通常是三種Application context配錯(cuò)、url-pattern不是/、或者 Artifact 沒(méi)部署。解決方法是先在 IDEA 的 Tomcat 配置里確認(rèn)Deployment有war explodedApplication context設(shè)成/然后檢查web.xml里DispatcherServlet的url-pattern。如果訪問(wèn)/能出首頁(yè)但/product/list404那就是 Controller 的RequestMapping路徑和訪問(wèn)路徑不一致或者組件掃描沒(méi)掃到 Controller 所在的包。5.2 數(shù)據(jù)庫(kù)連不上報(bào) Access denied 或 Unknown database現(xiàn)象啟動(dòng)時(shí)拋CannotGetJdbcConnectionException。原因一般是jdbc.properties里的用戶名密碼不對(duì)、數(shù)據(jù)庫(kù)名拼錯(cuò)、或者 MySQL 8 的驅(qū)動(dòng)類名和 URL 參數(shù)沒(méi)改。解決方法是先確認(rèn)數(shù)據(jù)庫(kù)真的建了、賬號(hào)能登錄然后檢查 URL 格式。MySQL 8 的 URL 要寫成jdbc:mysql://localhost:3306/snack_mall?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse少了serverTimezone會(huì)報(bào)時(shí)區(qū)錯(cuò)誤少了useSSLfalse會(huì)有警告。驅(qū)動(dòng)類名必須是com.mysql.cj.jdbc.Driver。5.3 中文亂碼商品名和訂單備注全變問(wèn)號(hào)現(xiàn)象頁(yè)面顯示的中文變成???或亂碼。原因可能出在三個(gè)環(huán)節(jié)數(shù)據(jù)庫(kù)字符集、連接 URL 字符集、JSP 頁(yè)面編碼。解決方法是三層都設(shè)成 UTF-8。數(shù)據(jù)庫(kù)建庫(kù)時(shí)用utf8mb4連接 URL 加characterEncodingutf8JSP 頁(yè)面頭部加% page contentTypetext/html;charsetUTF-8 languagejava %同時(shí)在web.xml里配CharacterEncodingFilter強(qiáng)制請(qǐng)求和響應(yīng)都用 UTF-8。三個(gè)地方缺一個(gè)都可能亂碼排查時(shí)按「數(shù)據(jù)庫(kù) → 連接 → 頁(yè)面」的順序逐個(gè)確認(rèn)。5.4 MyBatis 報(bào) Invalid bound statementMapper 找不到現(xiàn)象調(diào)用 Mapper 方法時(shí)拋BindingException: Invalid bound statement (not found)。原因是接口和 XML 沒(méi)有正確關(guān)聯(lián)。檢查四點(diǎn)XML 的namespace是否等于 Mapper 接口的全限定名XML 文件是否放在resources下且目錄結(jié)構(gòu)和接口包名一致applicationContext.xml里mapperLocations配置的路徑能不能匹配到 XML方法名和 XML 里的id是否完全一致。Maven 項(xiàng)目還有一個(gè)坑X(jué)ML 放在src/main/java下默認(rèn)不會(huì)被打包需要在pom.xml的resources里配置包含**/*.xml。5.5 事務(wù)不生效庫(kù)存扣了訂單沒(méi)生成現(xiàn)象下單過(guò)程中拋異常但庫(kù)存已經(jīng)被扣減數(shù)據(jù)不一致。原因是Transactional沒(méi)生效。常見(jiàn)原因有四個(gè)方法不是public的同類內(nèi)部方法直接調(diào)用沒(méi)走代理異常類型不在回滾范圍內(nèi)Service 沒(méi)被 Spring 管理沒(méi)加Service或沒(méi)在掃描包內(nèi)。解決方法是確認(rèn)方法public、加rollbackFor Exception.class、從外部調(diào)用而不是this.xxx()、檢查組件掃描配置。如果用了多數(shù)據(jù)源還要確認(rèn)事務(wù)管理器綁定了正確的數(shù)據(jù)源。6. 從能跑到能講二次開發(fā)與答辯演示的實(shí)用技巧把項(xiàng)目跑起來(lái)只是第一步真正讓它產(chǎn)生價(jià)值的是你能改得動(dòng)、講得清。二次開發(fā)建議從「加一個(gè)功能」入手比如給商品加一個(gè)「收藏」功能涉及新建favorite表、寫 Mapper、Service、Controller 和 JSP完整走一遍分層流程比看十遍代碼都管用。改的時(shí)候注意別破壞原有事務(wù)邊界新功能如果涉及多表寫入記得加Transactional。答辯演示有個(gè)實(shí)用技巧提前準(zhǔn)備好數(shù)據(jù)別現(xiàn)場(chǎng)從空庫(kù)開始操作。商品列表至少準(zhǔn)備 20 條數(shù)據(jù)、覆蓋 3 個(gè)以上分類訂單表準(zhǔn)備幾條不同狀態(tài)的記錄待支付、已支付、已取消這樣演示篩選、分頁(yè)、狀態(tài)流轉(zhuǎn)時(shí)不會(huì)冷場(chǎng)。演示下單流程時(shí)可以提前把某個(gè)商品庫(kù)存改成 1現(xiàn)場(chǎng)演示「庫(kù)存不足」的提示這比只演示成功路徑更能體現(xiàn)你對(duì)邊界情況的理解。論文部分和源碼要對(duì)得上。常見(jiàn)問(wèn)題是論文里寫的技術(shù)棧和源碼實(shí)際用的不一致比如論文寫「使用 Redis 緩存商品信息」但代碼里根本沒(méi)有 Redis。答辯老師如果對(duì)著論文問(wèn)代碼答不上來(lái)就很尷尬。建議把論文里的每個(gè)技術(shù)點(diǎn)都在源碼里找到對(duì)應(yīng)位置找不到的就從論文里刪掉寧可少寫也不要寫虛的。最后說(shuō)一個(gè)我自己的習(xí)慣拿到任何一份 SSM 源碼先不改代碼而是把a(bǔ)pplicationContext.xml、spring-mvc.xml、web.xml三個(gè)文件從頭到尾讀一遍把每個(gè) Bean 的作用和依賴關(guān)系畫在紙上。這一步花半小時(shí)后面改代碼和排查問(wèn)題的效率能翻倍。SSM 的配置雖然繁瑣但它是理解整個(gè)項(xiàng)目結(jié)構(gòu)的鑰匙配置讀懂了代碼就是水到渠成的事。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取