:從原理剖析到環(huán)境部署避坑指南)
簡介面向畢業(yè)設(shè)計與超市管理場景的進銷存管理系統(tǒng)采用Java語言開發(fā)基于Spring、SpringMVC、MyBatis整合框架搭配MySQL數(shù)據(jù)庫以B/S結(jié)構(gòu)實現(xiàn)。系統(tǒng)劃分管理員、員工、前臺三類角色管理員可管理用戶、員工、商品分類、商品信息、商品維護、入庫出庫記錄、銷售統(tǒng)計、留言板及訂單員工處理商品與出入庫記錄前臺首頁提供商品瀏覽、新聞資訊、留言反饋與購物入口覆蓋超市進銷存的完整業(yè)務(wù)鏈路。壓縮包內(nèi)共1390個文件以JSP動態(tài)頁面、Java業(yè)務(wù)源碼、JavaScript腳本、CSS樣式表為主輔以PNG/GIF圖片、XML配置文件、SQL數(shù)據(jù)庫腳本、Word論文和PPT答辯材料整體約31.22MB目錄結(jié)構(gòu)清晰便于定位代碼與文檔。已有110人學(xué)習(xí)下載適合需要完成畢業(yè)設(shè)計或課程設(shè)計的計算機專業(yè)學(xué)生可參考其多角色權(quán)限設(shè)計、進銷存流程實現(xiàn)及SSM整合思路。1. 從課設(shè)到簡歷為什么一套 SSMJSP 的超市進銷存管理系統(tǒng)到今天還有人做每年臨近畢業(yè)季「基于 SSMJSP 的 XX 超市進銷存管理系統(tǒng)」都是 Java 方向課程設(shè)計和畢業(yè)設(shè)計里出現(xiàn)頻率極高的選題。家百利超市進銷存管理系統(tǒng)就是這類項目的典型代表技術(shù)棧固定為 Spring SpringMVC MyBatis頁面層用 JSP業(yè)務(wù)范圍圈定在超市的進貨、銷售和庫存管理三條主線上。很多同學(xué)覺得這套技術(shù)?!咐稀沟∏∈沁@種老而穩(wěn)的組合把 Java Web 開發(fā)里最核心的東西——對象管理、請求流轉(zhuǎn)、SQL 操作、事務(wù)控制——完整地串了一遍。這套系統(tǒng)解決的不是什么高并發(fā)難題而是讓中小型超市的進銷存數(shù)據(jù)從 Excel 表格和手寫單據(jù)變成能對得上賬的數(shù)據(jù)庫記錄。適合正在做畢設(shè)或課設(shè)的學(xué)生也適合想快速補一套完整 SSM 項目經(jīng)驗的初級工程師照著源碼捋一遍業(yè)務(wù)閉環(huán)。2. 技術(shù)選型拆解SSMJSP 這套組合的邊界與合理性2.1 三個框架的分工Spring 管對象、SpringMVC 管請求、MyBatis 管 SQL在做任何一個 SSM 項目之前先把三個框架的邊界畫清楚后面讀源碼才不會迷路。Spring 負責(zé)對象管理也就是把 Service、Mapper 這些類交給容器統(tǒng)一創(chuàng)建和注入同時承擔(dān)事務(wù)管理和 AOP 切面SpringMVC 負責(zé)請求分發(fā)從瀏覽器進來的 URL 經(jīng)過 DispatcherServlet 找到對應(yīng)的 Controller 方法再返回視圖或數(shù)據(jù)MyBatis 負責(zé)數(shù)據(jù)庫訪問把 SQL 寫在 XML 映射文件或注解里返回的對象自動映射成 Java 實體。三者各管一段出了問題排查范圍天然被切開了。在源碼工程里這三個框架的協(xié)作關(guān)系可以從兩個配置文件里看出來。一個典型的 Spring 配置片段長這樣!-- 開啟注解掃描Controller 之外的所有 Bean 都從這里注冊 -- context:component-scan base-packagecom.jiabaili.service / context:component-scan base-packagecom.jiabaili.dao / !-- 數(shù)據(jù)源配置驅(qū)動、地址、賬號、密碼 -- bean iddataSource classorg.springframework.jdbc.datasource.DriverManagerDataSource property namedriverClassName valuecom.mysql.jdbc.Driver / property nameurl valuejdbc:mysql://localhost:3306/jbl_supermarket?characterEncodingutf8 / property nameusername valueroot / property namepassword value123456 / /bean !-- 把數(shù)據(jù)源交給 MyBatis 的 SqlSessionFactory -- bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource / property namemapperLocations valueclasspath:com/jiabaili/mapper/*.xml / /bean !-- 掃描 Mapper 接口動態(tài)生成實現(xiàn)類 -- bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.jiabaili.dao / /bean這段配置的邏輯是先掃描 Service 和 DAO 接口再把數(shù)據(jù)源注入 MyBatis 的會話工廠最后通過 MapperScannerConfigurer 把 DAO 接口和 XML 里的 SQL 綁定起來。參數(shù)里有三個地方需要根據(jù)實際環(huán)境改數(shù)據(jù)庫地址、賬號密碼、mapper XML 的掃描路徑。不少源碼工程的坑就出在 mapperLocations 的路徑寫錯導(dǎo)致啟動時報Invalid bound statement (not found)。這個報錯的意思是接口方法找到了但對應(yīng)的 SQL 沒加載進來優(yōu)先檢查路徑和 XML 文件名是否和接口名一一對應(yīng)。2.2 JSP 不過時只是被用錯了地方頁面層為什么不適合無腦前后端分離近幾年前后端分離成了默認姿勢但 SSMJSP 這套組合并沒有被淘汰。JSP 的核心優(yōu)勢是服務(wù)端渲染Java 代碼和 HTML 可以混編數(shù)據(jù)從 Controller 塞進 Model 之后直接在頁面上用 EL 表達式和 JSTL 標簽取出來不需要額外起 Node.js 服務(wù)也不需要解決跨域問題。對于超市進銷存這種以表單和表格為主的后臺管理系統(tǒng)頁面交互不復(fù)雜JSP 的開發(fā)效率反而高于 VueAxios 那一套??匆幌略创a里典型的進貨單 JSP 頁面片段感受一下數(shù)據(jù)是怎么從后端流到頁面的% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html head title進貨單列表/title /head body table border1 tr th進貨單號/th th供應(yīng)商/th th進貨日期/th th總金額/th /tr c:forEach items${purchaseList} varpurchase tr td${purchase.purchaseNo}/td td${purchase.supplierName}/td td${purchase.purchaseDate}/td td${purchase.totalAmount}/td /tr /c:forEach /table /body /html這里最需要注意的是${purchase.purchaseNo}這種 EL 表達式它背后對應(yīng)的是 Purchase 實體類的getPurchaseNo()方法。如果實體類沒有生成 getter頁面上會直接空白而且不報錯。新手最容易在這里翻車MySQL 里字段是purchase_noJava 實體屬性是purchaseNoMyBatis 開啟了駝峰映射就能對上沒開啟就要在 XML 里寫resultMap做手動映射。JSP 頁面取值取不到的時候先回 Mapper XML 里看 resultMap 或 resultType 配的是什么再回實體類里看 getter 是否存在不要第一反應(yīng)去改頁面。2.3 源碼工程的目錄結(jié)構(gòu)三個包和一個 resources 目錄拿到家百利進銷存系統(tǒng)的源碼工程第一件事不是急著運行而是先看目錄結(jié)構(gòu)。傳統(tǒng)的 SSM 項目按 MVC 分層源碼里一般會有一個頂級包名比如com.jiabaili下面按 controller、service、dao、entity 四個子包組織。entity 里放和數(shù)據(jù)庫表對應(yīng)的實體類dao 里寫接口service 里寫業(yè)務(wù)邏輯controller 里寫請求映射。resources 目錄下放著 Spring 配置文件、MyBatis 映射文件和 MyBatis 全局配置。src/main/java/com/jiabaili/ ├── controller/ # 頁面跳轉(zhuǎn)和請求參數(shù)接收 │ ├── LoginController.java │ ├── PurchaseController.java │ └── SaleController.java ├── service/ # 業(yè)務(wù)邏輯層事務(wù)邊界在這里 │ ├── PurchaseService.java │ └── SaleService.java ├── dao/ # MyBatis Mapper 接口 │ ├── PurchaseDao.java │ └── SaleDao.java └── entity/ # 數(shù)據(jù)庫表對應(yīng)的實體 ├── Purchase.java └── Product.java src/main/resources/ ├── spring/ # Spring 和 SpringMVC 配置文件 ├── mapper/ # MyBatis 的 XML 映射文件 └── mybatis-config.xml # MyBatis 全局配置拿到源碼先核對這個結(jié)構(gòu)。很多從網(wǎng)盤下載的源碼工程包名和實際目錄對不上或者少了 mapper 文件夾運行起來全是「找不到符號」。我一般會先全局搜索RequestMapping把所有 Controller 里的 URL 路徑列出來和數(shù)據(jù)庫表名對照一遍就能大概猜出這個系統(tǒng)的功能模塊劃分。超市進銷存系統(tǒng)的功能通常圍繞六個模塊轉(zhuǎn)登錄、商品管理、供應(yīng)商管理、進貨管理、銷售管理、庫存查詢源碼里的 Controller 類基本和這六個模塊一一對應(yīng)。3. 數(shù)據(jù)庫設(shè)計與業(yè)務(wù)閉環(huán)進銷存的核心不只是三張表3.1 七張核心表從商品表到庫存流水表的設(shè)計邏輯進銷存系統(tǒng)聽起來就是「進貨、賣貨、查庫存」但表結(jié)構(gòu)不能只設(shè)計三張表。如果進貨單和銷售單不拆主表和明細表一條進貨記錄里多個商品就無法存儲如果庫存只存當(dāng)前數(shù)量不記錄每次變動出了問題根本沒法追溯。常見的做法是設(shè)計七張表商品表、供應(yīng)商表、進貨單表、進貨明細表、銷售單表、銷售明細表、庫存表。有的系統(tǒng)還會加一張庫存流水表每次入庫出庫都追加一條記錄。表名核心字段作用t_productid, product_no, product_name, spec, unit, price商品主數(shù)據(jù)t_supplierid, supplier_name, contact, phone, address供應(yīng)商檔案t_purchaseid, purchase_no, supplier_id, purchase_date, total_amount, operator進貨單主表t_purchase_itemid, purchase_id, product_id, quantity, price, amount進貨單明細t_saleid, sale_no, sale_date, total_amount, operator銷售單主表t_sale_itemid, sale_id, product_id, quantity, price, amount銷售單明細t_stockid, product_id, quantity, last_update_time實時庫存兩個主表各自掛一個明細表這是進銷存系統(tǒng)里最核心的表結(jié)構(gòu)設(shè)計。主表存一筆單據(jù)的公共信息單號、日期、總金額、操作人明細表存這一筆單據(jù)里每個商品的數(shù)量和金額。以進貨為例一次采購 10 種商品t_purchase 表只多一條記錄而 t_purchase_item 表多 10 條。這樣設(shè)計的好處是兩個維度都能查按單號查整個進貨批次按商品查這個商品在不同批次里的進貨價。3.2 一次進貨動作背后的三次寫操作事務(wù)邊界的經(jīng)典示范看一個進銷存系統(tǒng)的源碼質(zhì)量最有效的辦法是看進貨邏輯的 Service 實現(xiàn)。一次進貨不是單純的 insert而是三個動作往進貨主表和明細表插入單據(jù)數(shù)據(jù)同時還要更新庫存表里的商品數(shù)量。這三個動作必須在一個數(shù)據(jù)庫事務(wù)里任何一個失敗都要全部回滾。下面是一個簡化版的進貨 Service 代碼Service public class PurchaseService { Autowired private PurchaseDao purchaseDao; Autowired private PurchaseItemDao purchaseItemDao; Autowired private StockDao stockDao; Transactional(rollbackFor Exception.class) public void addPurchase(Purchase purchase, ListPurchaseItem items) { // 動作一插入進貨單主表返回主鍵 ID purchaseDao.insertPurchase(purchase); // 動作二遍歷明細列表逐條插入進貨明細表 for (PurchaseItem item : items) { item.setPurchaseId(purchase.getId()); purchaseItemDao.insertItem(item); // 動作三更新庫存表商品存在則加數(shù)量不存在則插入 int affected stockDao.increaseStock(item.getProductId(), item.getQuantity()); if (affected 0) { Stock stock new Stock(); stock.setProductId(item.getProductId()); stock.setQuantity(item.getQuantity()); stockDao.insertStock(stock); } } } }這段代碼值得注意的地方有兩個。第一是Transactional(rollbackFor Exception.class)這個參數(shù)不是默認值——Spring 的事務(wù)默認只在拋出 RuntimeException 時回滾如果業(yè)務(wù)代碼拋的是自定義的 checked exception不指定 rollbackFor 的話庫存已經(jīng)更新了但單據(jù)沒插進去數(shù)據(jù)就永遠對不上了。第二是increaseStock返回受影響行數(shù)的寫法用affected 0來判斷是更新還是插入本質(zhì)上是一個簡單的「有則改、無則加」邏輯。這個寫法在并發(fā)場景下會有問題但對于課設(shè)和中小型超市的并發(fā)量來說足夠了。3.3 論文里的 E-R 圖和數(shù)據(jù)字典和代碼怎么對照著寫這套源碼附帶的論文里數(shù)據(jù)庫設(shè)計章節(jié)一般是重頭戲E-R 圖和數(shù)據(jù)字典加起來能占去十幾頁。寫論文時有一個常見誤區(qū)E-R 圖畫得很大很全但和數(shù)據(jù)字典里的字段對不上或者數(shù)據(jù)字典里的表和代碼里的建表 SQL 不一致。拿到源碼后我建議把三樣?xùn)|西放在一起核對——論文中的 E-R 圖、數(shù)據(jù)字典、resources 目錄下的建表 SQL 文件。很多版本的源碼里論文里寫了 12 張表實際 SQL 腳本只建了 7 張寫著寫著功能就缺了。數(shù)據(jù)字典的寫法也有套路每張表一個表格字段名、數(shù)據(jù)類型、是否主鍵、是否為空、默認值、說明。寫的時候不要照抄字段名就完事要把業(yè)務(wù)含義寫清楚。比如 t_stock 表的quantity字段說明不能只寫「數(shù)量」應(yīng)該寫「當(dāng)前實時庫存數(shù)量每次入庫加、出庫減由 Service 層事務(wù)保證一致性」。論文答辯時老師喜歡追問的就是這種細節(jié)——「你這個庫存表的更新策略是什么」「進貨單刪除的時候庫存要不要回滾」回答得上來說明系統(tǒng)真的是你寫的回答不上來代碼是不是自己寫的就不好說了。4. 在本地跑通源碼環(huán)境版本組合、配置修改和啟動步驟4.1 版本組合把 JDK、Tomcat、MySQL、Maven 的兼容矩陣先定下來SSMJSP 項目最容易翻車的地方不在代碼而在環(huán)境。網(wǎng)上流傳的源碼工程很多是在不同版本的 JDK、Tomcat、MySQL 下寫的直接拿自己機器上最新的環(huán)境去跑經(jīng)常出現(xiàn)各種玄學(xué)報錯。先給一組經(jīng)過驗證的兼容組合JDK 1.8、Tomcat 8.5、MySQL 5.7、Maven 3.6.3。這套組合和絕大部分 SSMJSP 課設(shè)源碼的編寫環(huán)境一致也是我建議優(yōu)先嘗試的組合。組件推薦版本說明JDK1.8SSM 項目基本都在 Java 8 上開發(fā)避免直接用 JDK 11Tomcat8.5支持 JSP 2.3 和 Servlet 3.1兼容性最好MySQL5.75.x 版本對 JDBC 驅(qū)動的兼容性比 8.x 省心Maven3.6.3依賴管理用3.8 有時會因中央倉庫策略報錯JDK 和 Maven 的環(huán)境變量配置是老生常談但要檢查是否真的生效可以在命令行執(zhí)行java -version和mvn -v確認輸出的版本號不是系統(tǒng)預(yù)裝的其他版本。MySQL 要注意 root 賬號的密碼是否和源碼里的jdbc.properties配置一致這是連接失敗的第一大原因。另外不建議一上來就用最新的 MySQL 8.0 和 Tomcat 10——Tomcat 10 的包名從javax.servlet改成了jakarta.servlet老 SSM 項目里的javax導(dǎo)入全會報 ClassNotFoundException不是改一行配置能解決的。4.2 從導(dǎo)入到啟動的七步操作以命令行方式跑通整個工程拿到源碼后按下面這幾步操作能避開大多數(shù)坑。先檢查 JDK 和 Maven 版本再導(dǎo)入數(shù)據(jù)庫腳本然后修改三個配置文件最后啟動 Tomcat。我用命令行的方式來寫因為很多網(wǎng)上下的源碼工程并不帶 IDE 配置直接用 Maven 命令驗證最靠譜。# 第一步確認 JDK 版本 java -version # 期望看到 1.8 字樣 # 第二步確認 Maven 版本 mvn -v # 期望看到 Apache Maven 3.x # 第三步創(chuàng)建數(shù)據(jù)庫并導(dǎo)入 SQL 腳本 mysql -u root -p # 在 MySQL 里執(zhí)行CREATE DATABASE jbl_supermarket DEFAULT CHARSET utf8mb4; # 然后退出 mysql 命令行回到系統(tǒng)命令行執(zhí)行下面這行 mysql -u root -p jbl_supermarket sql/jbl_supermarket.sql # 第四步編譯并打包工程 mvn clean package -DskipTests # 看到 BUILD SUCCESS 說明編譯通過 # 第五步部署到 Tomcat cp target/jbl-supermarket.war /path/to/tomcat/webapps/ # 第六步啟動 Tomcat /path/to/tomcat/bin/startup.sh # 第七步查看啟動日志 tail -f /path/to/tomcat/logs/catalina.out # 看到 Deployment of web application archive ... has finished 說明部署成功第三步里的 SQL 腳本是源碼自帶的建表和初始數(shù)據(jù)文件一般在項目根目錄的sql/或db/文件夾下。導(dǎo)入前先打開看一下確認里面有沒有CREATE DATABASE語句——如果有就不需要手動創(chuàng)建數(shù)據(jù)庫直接全量導(dǎo)入即可如果沒有就先建庫再指定庫名導(dǎo)入。第五步里部署 war 包之前我習(xí)慣把 Tomcat 的webapps目錄下原來自帶的ROOT、docs等文件夾清掉避免訪問 8080 端口時跳到 Tomcat 默認首頁讓人誤以為項目沒部署成功。4.3 三個必須改的配置數(shù)據(jù)庫連接、根路徑和字符編碼工程跑起來之前有三個配置項要逐個核對。第一個是數(shù)據(jù)庫連接信息通常在src/main/resources下的jdbc.properties里把jdbc.url、jdbc.username、jdbc.password改成自己本機的值。第二個是項目的上下文根路徑如果 war 包名是jbl-supermarket訪問地址就是http://localhost:8080/jbl-supermarket/登錄頁面的 URL 如果寫死了/login會跳轉(zhuǎn)到 Tomcat 根路徑下找不到資源。第三個是字符編碼JSP 頁面、Servlet 過濾器、數(shù)據(jù)庫連接串三處都要統(tǒng)一成 UTF-8缺一個就會出現(xiàn)中文亂碼。!-- web.xml 里配置 Spring MVC 的字符編碼過濾器必須在所有過濾器最前面 -- filter filter-nameencodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param init-param param-nameforceEncoding/param-name param-valuetrue/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping這段過濾器的forceEncoding參數(shù)很容易被忽略。如果不設(shè)成 trueSpring 只會在請求參數(shù)里帶有編碼信息時才會覆蓋默認編碼設(shè)了 true則強制所有請求和響應(yīng)都使用 UTF-8。很多本地開發(fā)環(huán)境沒問題、部署到服務(wù)器就亂碼的情況都是因為這臺服務(wù)器上的容器默認編碼不是 UTF-8。把這段配置加進去之后JSP 頁面頭部的contentTypetext/html;charsetUTF-8和數(shù)據(jù)庫連接串里的characterEncodingutf8三處對齊中文顯示問題基本就根治了。5. 避坑指南SSMJSP 項目最容易翻車的 5 個檢查點5.1 分頁查詢失效返回全表數(shù)據(jù)現(xiàn)象在商品列表頁點擊第二頁結(jié)果頁面把全部商品一次性列出來了或者點下一頁沒反應(yīng)SQL 日志里查出來的數(shù)據(jù)量是全部記錄數(shù)。原因SSM 項目里分頁通常用 PageHelper 插件但 PageHelper 的版本必須和 MyBatis 版本兼容。另一個常見原因是 MyBatis 全局配置里沒有注冊 PageHelper 的攔截器導(dǎo)致調(diào)用PageHelper.startPage()后沒有任何攔截效果。還有一種情況是startPage()后面緊接著的不是第一條查詢語句中間多了一條別的 SQLPageHelper 會把分頁參數(shù)作用到錯誤的查詢上。解決先確認 Maven 依賴里引入的是com.github.pagehelper:pagehelper再在mybatis-config.xml中注冊攔截器plugins plugin interceptorcom.github.pagehelper.PageInterceptor property namehelperDialect valuemysql / property namereasonable valuetrue / /plugin /pluginsreasonabletrue這個參數(shù)的作用是當(dāng)頁碼超出總頁數(shù)時自動跳到第一頁或最后一頁而不是報錯。排查時還要注意PageHelper.startPage( pageNum, pageSize )后面必須緊跟第一條select查詢中間不能有其他 SQL 或邏輯判斷。血淚經(jīng)驗是有人在一行日志輸出語句放在中間分頁就神秘失效了這不是玄學(xué)是 PageHelper 基于 ThreadLocal 的實現(xiàn)機制決定的。5.2 中文亂碼JSP 頁面改了 charset 也沒用現(xiàn)象登錄后首頁全是問號商品名稱顯示成「???」或者頁面能顯示中文但插入到數(shù)據(jù)庫里的值變成了亂碼。原因亂碼的傳播鏈條是「瀏覽器 → Tomcat → Servlet 過濾器 → 數(shù)據(jù)庫連接 → MySQL 表結(jié)構(gòu)」五個環(huán)節(jié)。只改 JSP 頁面的charsetUTF-8解決的是響應(yīng)方向的問題請求方向如果 Tomcat 的 URIEncoding 沒配置POST 請求體是 UTF-8 但 Tomcat 默認按 ISO-8859-1 解碼到 Java 代碼里就已經(jīng)是亂碼了。數(shù)據(jù)庫連接串沒加characterEncodingutf8從 Java 到 MySQL 也會亂碼。解決按順序檢查三處。第一處是jdbc.properties里的連接串確保包含useUnicodetruecharacterEncodingutf8第二處是web.xml里的 CharacterEncodingFilter已在上文給出第三處是 MySQL 建表語句里的字符集如果表結(jié)構(gòu)本身是 latin1代碼層全是 UTF-8 也救不回來。檢查已存在的表用這條 SQLSHOW TABLE STATUS FROM jbl_supermarket;看到latin1的行執(zhí)行ALTER TABLE t_product CONVERT TO CHARACTER SET utf8mb4;把整張表轉(zhuǎn)過來。記住一點UTF-8 和 utf8mb4 的差別在于 emoji 和生僻字不要省錢用 utf8直接上 utf8mb4 最穩(wěn)妥。5.3 Service 層兩個數(shù)據(jù)庫操作一個成功一個失敗沒回滾現(xiàn)象進貨時報了「明細不能為空」的異常但重新登錄系統(tǒng)發(fā)現(xiàn)庫存數(shù)量已經(jīng)增加了或者銷售單沒插入成功庫存卻被扣減了。原因最常見的兩個原因一是Transactional注解加在了類上但類的掃描路徑?jīng)]覆蓋到Spring 容器里這個 Service 根本不是一個代理對象二是事務(wù)方法內(nèi)部 catch 了異常并吞掉Spring 感知不到異常就不會回滾。還有一個隱蔽的原因Transactional默認只對 RuntimeException 回滾業(yè)務(wù)拋出的 checked exception比如自定義的BizException不會觸發(fā)回滾。解決把注解寫成Transactional(rollbackFor Exception.class)并且不要在大事務(wù)方法里用 try-catch 吞異常。如果確實需要捕獲異常做邏輯處理處理完之后要重新拋出或手動調(diào)用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()。通過 Spring 的聲明式事務(wù)別自己手動寫回滾邏輯那是把簡單問題復(fù)雜化。5.4 Tomcat 部署后 ClassNotFoundException但 Maven 編譯是成功的現(xiàn)象mvn package順利打出 war 包部署到 Tomcat 后卻報ClassNotFoundException: com.mysql.jdbc.Driver或者報找不到某個 Mapper 類。原因Maven 打包時依賴沒有打進去或者打進了重復(fù)的 jar。SSM 項目要在pom.xml里把 Spring、MyBatis、MySQL 驅(qū)動等依賴連同一同打進 war 包如果某個依賴被標記為provided作用域打包時會跳過它。另一個常見情況是 Tomcat 的lib目錄里放了一份老版本的 Servlet API和項目里引入的版本沖突。解決在項目根目錄執(zhí)行mvn dependency:tree看 MySQL 驅(qū)動和 MyBatis 的依賴樹上有沒有provided或compile標簽。然后在 Tomcat 的webapps目錄找到布署的 war 包解壓目錄進入WEB-INF/lib用ls | grep mysql確認驅(qū)動 jar 確實在。不推薦手動往 Tomcatlib目錄里扔 jar那樣會把所有部署在這個 Tomcat 上的項目都影響屬于擴大了排查范圍。5.5 JSP 頁面上 EL 表達式原樣輸出取不到值現(xiàn)象頁面上顯示的是${product.productName}這一串字符串而不是商品名稱本身瀏覽器里查看源碼能看到 EL 表達式?jīng)]被解析。原因JSP 頁面的isELIgnored被設(shè)成了 true或者 web.xml 的 Servlet 版本聲明太低JSP 2.0 以下默認不解析 EL。還有一種情況是 Controller 里往 Model 塞數(shù)據(jù)的 key 和頁面上取的 key 不一致比如 Controller 里寫model.addAttribute(productList, ...)頁面里取的是product。解決在 JSP 頁面頭部顯式聲明% page isELIgnoredfalse %同時檢查 web.xml 的命名空間是不是 Servlet 3.0 及以上版本。如果頁面確認要使用 JSTL 的${}表達式頁面頂部要加上% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %。排查順序是先看 JSP 頂部有沒有聲明再看 web.xml 版本最后看 Controller 里的 key 名和頁面是否一致。EL 表達式取不到值不像 Java 編譯錯誤有明確報錯頁面靜默空白很多人卡在這里半小時發(fā)現(xiàn)只是 key 少寫了一個字母。6. 給進銷存系統(tǒng)加統(tǒng)計報表從 JSP 表格到可視化圖表的升級路徑6.1 用 JFreeChart 在服務(wù)端生成圖片還是前端用 ECharts 拿 JSON 畫圖進銷存系統(tǒng)的論文里通常會提到「統(tǒng)計報表」模塊但很多源碼實現(xiàn)只是用 JSP 再畫一個表格把進貨總額、銷售總額列出來談不上可視化。做這個模塊的升級時有兩種方案。方案一是在 Java 端用 JFreeChart 生成柱狀圖或餅圖圖片直接把圖片路徑塞給 JSP 的img標簽展示優(yōu)點是純 Java 實現(xiàn)不需要前端引入第三方庫但圖表交互能力弱調(diào)整樣式得重新生成圖片。方案二是在 Controller 里把統(tǒng)計數(shù)據(jù)封裝成 JSON 返回給 JSPJSP 里用 ECharts 渲染靈活性和美觀度都更好。我傾向方案二因為它把前后端職責(zé)分得更清楚而且這套 JSON 接口將來改造前后端分離時可以無縫遷移。JSP 頁面里只需要加一段 JavaScriptscript src${pageContext.request.contextPath}/static/js/echarts.min.js/script script // 從后端接口取統(tǒng)計數(shù)據(jù)接口返回 [{name: 牛奶, value: 120}, ...] fetch(${pageContext.request.contextPath}/statistics/saleRank) .then(response response.json()) .then(data { const chart echarts.init(document.getElementById(saleChart)); chart.setOption({ title: { text: 本月商品銷量 TOP10 }, tooltip: {}, xAxis: { data: data.map(item item.name) }, yAxis: {}, series: [{ type: bar, data: data.map(item item.value) }] }); }); /script這段代碼用 fetch 調(diào)后端接口取 JSON然后喂給 ECharts邏輯很直白。需要說明的是Controller 里對應(yīng)的接口方法要返回ResponseBody并且把統(tǒng)計 SQL 的結(jié)果映射成Map或ListMap的格式ECharts 拿到后才能正確渲染。自己動手升級這個模塊時重點不在代碼本身而是統(tǒng)計口徑——銷量是按銷售明細表的 quantity 匯總銷售額是按 amount 匯總兩個 SQL 都要GROUP BY product_id后用ORDER BY SUM(...) DESC LIMIT 10控制返回條數(shù)。6.2 三個值得動手的改造方向Spring Boot 化、數(shù)據(jù)權(quán)限、緩存如果這套 SSMJSP 系統(tǒng)是用來找工作面試的項目不建議停留在「跑通即可」建議往三個方向動手改一改。第一個方向是 Spring Boot 化把 XML 配置改成application.yml啟動方式從 Tomcat 部署變成mvn spring-boot:run這個改造過程能把 Servlet 和 Spring 的底層理解再夯實一遍。第二個方向是數(shù)據(jù)權(quán)限給不同角色的登錄用戶配置不同的數(shù)據(jù)范圍——普通員工只能看到自己的操作記錄店長能看到全店數(shù)據(jù)這個功能在面試時很加分。第三個方向是首頁庫存預(yù)警加一層緩存把庫存低于閾值的商品列表緩存起來改數(shù)據(jù)庫時主動清緩存不涉及 Redis 的話先用ConcurrentHashMap手動實現(xiàn)一個簡單的 CacheUtil 類理解清楚緩存穿透和緩存一致性的概念后再上 Redis。我自己做這個課設(shè)題目時踩過最深的坑是事務(wù)回滾——當(dāng)時進貨單插入成功但明細插入時主鍵沖突沒拋異常導(dǎo)致庫存更新了兩次。后來養(yǎng)成了一個習(xí)慣寫完一個涉及多表更新的 Service 方法先故意制造一個錯誤跑一遍確認數(shù)據(jù)沒變再寫正常的業(yè)務(wù)邏輯。這個習(xí)慣幫我避免了好幾次線上事故。做課設(shè)和畢設(shè)也是一樣的道理代碼跑通只是起點把每個邊界情況都驗證過一遍答辯時才硬氣。希望幫到你。本文還有配套的精品資源點擊獲取