:數(shù)據(jù)庫設計與SpringMVC整合實戰(zhàn))
1. 項目整體定位與需求拆解1.1 這套SSM水果蔬菜商城到底在做什么手頭這個基于SSM的水果蔬菜商城系統(tǒng)乍一看就是一個典型的Java Web課程設計但真正把它從頭到尾跑通、部署、交付你會發(fā)現(xiàn)它幾乎是SSM技術棧的全要素演練場。項目本身并不花哨核心是圍繞生鮮果蔬的線上銷售場景做了一套包含前臺購物和后臺管理的完整閉環(huán)系統(tǒng)。前臺面向普通消費者提供注冊登錄、商品瀏覽、分類篩選、購物車、下單結(jié)算、訂單查詢這些基礎電商能力后臺面向運營人員提供商品管理、分類管理、會員管理、訂單處理、庫存調(diào)整等功能模塊。這類項目之所以在各大高校的項目庫里長盛不衰是因為它踩中的技術點非常典型SSM三大框架的整合、MySQL數(shù)據(jù)庫設計、Maven依賴管理、三層架構分層思想、會話狀態(tài)管理、事務控制基本覆蓋了Java企業(yè)級開發(fā)入門階段的全部核心技能。你把這個項目吃透往后去看Spring Boot項目很多東西都能無縫遷移因為底層思路是一致的。整套系統(tǒng)適合誰兩類人最值得花時間研究第一類是正在做課程設計或者畢業(yè)設計的學生需要一個能演示、能答辯、能擴展的完整項目第二類是自學Java Web開發(fā)、想通過一個綜合實戰(zhàn)把SSM知識點串起來的開發(fā)者。我自己帶過不少新人走這套流程說實話這個項目的難度曲線設計得相當友好——它不是那種一看就會、一寫就廢的玩具而是有一定業(yè)務復雜度、但每一步都有成熟方案可以參考的實戰(zhàn)型項目。1.2 生鮮電商場景的特殊性在哪里如果只是做一個普通商城那這個項目也就是平平無奇的CRUD練習。但主題落在水果蔬菜上業(yè)務邏輯就開始有意思了。生鮮商品跟數(shù)碼產(chǎn)品、服裝最大的區(qū)別在于庫存管理更敏感、商品規(guī)格更多樣、配送時效要求高、價格波動頻繁。你在數(shù)據(jù)庫設計的時候就不得不考慮這些因素。比如水果蔬菜的計價單位就很麻煩。蘋果可以按斤賣也可以按箱賣土豆按斤但有的精品超市按份賣還有按個賣的臍橙、按串賣的香蕉。如果商品表里只放一個簡單的price字段和unit字段后期擴展會很痛苦。我當時做這套系統(tǒng)的時候在商品表里單獨設計了price字段配合unit字段同時預留了originalPrice用于展示劃線價還加了一個salesCount做銷量排序——這些都是從真實生鮮電商平臺學來的細節(jié)。另外生鮮商品的分類層級也值得多琢磨。水果、蔬菜是一級分類往下還可以分葉菜類、根莖類、瓜果類、熱帶水果、進口水果等。雖然這個項目用一級分類也能跑通但如果分類表設計成pid自關聯(lián)的無限級結(jié)構你會發(fā)現(xiàn)前端菜單渲染、后臺分類管理都會靈活很多。我建議在動手寫代碼之前先把分類表的自關聯(lián)結(jié)構想清楚后面能省不少返工。2. 核心技術方案與數(shù)據(jù)庫設計2.1 SSM整合的底層邏輯三個框架各管一段很多人學SSM的時候最大的困惑是Spring、SpringMVC、MyBatis這三個框架到底是怎么協(xié)作的我用一句話概括Spring是總調(diào)度SpringMVC管請求分發(fā)MyBatis管數(shù)據(jù)庫讀寫。打個比方Spring就像公司總經(jīng)理SpringMVC是前臺接待把來訪的請求分發(fā)給各個部門MyBatis是檔案管理員專門負責從數(shù)據(jù)庫倉庫取數(shù)據(jù)、存數(shù)據(jù)。具體到項目代碼里SpringMVC負責接收瀏覽器發(fā)來的HTTP請求通過DispatcherServlet把請求路由到對應的Controller方法Controller調(diào)用Service層處理業(yè)務邏輯Service層通過MyBatis的Mapper接口與數(shù)據(jù)庫交互。而Spring在這中間做的事情是粘合——它管理Controller、Service、Mapper這些對象的創(chuàng)建和依賴關系同時負責管理數(shù)據(jù)庫連接池和事務。這套整合方案里有一個關鍵概念容器。Spring容器里注冊了Service對象SpringMVC的容器里注冊了Controller對象MyBatis的Mapper則通過動態(tài)代理機制生成實現(xiàn)類注冊進Spring容器。初次接觸的人容易搞混的地方在于為什么有的配置放在Spring的applicationContext.xml里有的放在SpringMVC的spring-mvc.xml里我當時的理解是Spring容器管業(yè)務層和持久層SpringMVC容器管控制層和視圖層。父子容器的關系搞清楚之后配置文件的邊界自然就清晰了。2.2 數(shù)據(jù)庫表設計實戰(zhàn)從需求到建表語句這個項目的核心表有六張左右我把設計思路逐一拆開講。用戶表user字段包含user_id主鍵、username、password、phone、email、create_time。密碼存儲我強烈建議不要用明文哪怕課程設計也要養(yǎng)成好習慣。可以用MD5加鹽的方式或者用Spring自帶的加密工具。我見過很多學生項目把密碼明文懟在數(shù)據(jù)庫里答辯的時候老師一問密碼安全問題就卡殼沒必要。商品表product核心字段包括product_id、category_id關聯(lián)分類表、name、description、price、original_price、stock、unit、image_url、sales_count、status。status字段很關鍵用于上下架控制0表示下架、1表示上架。做后臺商品管理的時候直接update這個字段就能實現(xiàn)上下架不用物理刪除數(shù)據(jù)。分類表category我建議做自關聯(lián)結(jié)構category_id、parent_id、name三個字段就夠了。這樣以后想擴展多級分類代碼層面幾乎不用動。購物車表cartcart_id、user_id、product_id、quantity、create_time。這里要注意的唯一約束問題同一個用戶加同一個商品不應該產(chǎn)生兩條購物車記錄而是合并數(shù)量??梢栽诖a里先查再插也可以直接給(user_id, product_id)加聯(lián)合唯一索引然后走insert ... on duplicate key update后者在高并發(fā)場景下更可靠。訂單表orderorder_id主鍵、order_no訂單編號、user_id、total_price、status、receiver_name、receiver_phone、receiver_address、create_time、pay_time。order_no一定要用時間戳加隨機數(shù)的組合生成避免并發(fā)下單的時候出現(xiàn)重復編號我見過用自增id當訂單號的項目被答辯老師追問得下不來臺。訂單明細表order_itemitem_id、order_id、product_id、product_name冗余字段防止商品改名或刪除后歷史訂單無法追溯、price、quantity、subtotal。這里有個設計經(jīng)驗商品名稱一定要冗余到訂單明細里因為商品表的數(shù)據(jù)會變而訂單是歷史事實不該跟著變。六張表的關系也不復雜用戶可以下多個訂單一個訂單對應多個明細購物車表和用戶、商品都關聯(lián)。如果時間充裕還可以加一張輪播圖表或者公告表讓首頁更豐滿但這六張表已經(jīng)能支撐整個業(yè)務流程跑通了。2.3 分層架構為什么Controller不能直接寫SQL做SSM項目最忌諱的是把所有代碼堆在Controller里一個方法寫幾百行SQL用字符串拼接業(yè)務邏輯和表現(xiàn)層全攪在一起。這種代碼當時跑通沒問題后面一加需求就崩潰。這個項目我采用的是標準的四層結(jié)構Controller層表現(xiàn)層、Service層業(yè)務層、Mapper層數(shù)據(jù)訪問層、domain/entity層實體層。Controller只做參數(shù)接收和視圖轉(zhuǎn)發(fā)不處理業(yè)務Service層寫具體業(yè)務邏輯比如下單時檢查庫存、計算總價、生成訂單號、扣減庫存要在一個事務里完成Mapper層只做簡單的數(shù)據(jù)讀寫。Service層還有一個關鍵設計面向接口編程。先寫接口再寫實現(xiàn)類Controller只管調(diào)用接口。這么做的好處是以后想換實現(xiàn)、加緩存、做事務增強都方便。我在項目里對訂單模塊和購物車模塊都做了接口類的設計代碼可讀性高很多。3. 項目工程搭建與SSM整合實操3.1 Maven依賴管理從零搭建工程骨架項目是標準的Maven工程groupId我用的是個人域名的反寫artifactId是produce包裝成war部署到Tomcat。剛?cè)腴T的人容易在pom.xml這塊卡住不知道要加哪些依賴。我列一份最小可用集合spring-core、spring-context、spring-webmvc、spring-jdbc、mybatis、mybatis-spring整合包、mysql-connector-java、druid連接池、junit測試用、jackson做JSON響應用、jstl和taglibsJSP頁面用。以上這些都是SSM項目的標配。有一個容易踩的坑是版本兼容問題。我最初隨便找了一個mybatis版本和mybatis-spring版本配在一起啟動的時候一直報錯查了半天發(fā)現(xiàn)是mybatis-spring版本和mybatis版本不兼容。后來統(tǒng)一用了一個成熟組合mybatis 3.5.x配mybatis-spring 1.3.x或者直接抄某開源項目的版本號組合就穩(wěn)了。版本這一塊別追求新穩(wěn)定最重要。3.2 配置文件三件套Spring、SpringMVC、MyBatis各司其職我建了三個核心配置文件applicationContext.xmlSpring主配置、spring-mvc.xmlSpringMVC配置、mybatis-config.xmlMyBatis配置。applicationContext.xml里面主要配置了數(shù)據(jù)源、SqlSessionFactory、Mapper掃描、Service層掃描、事務管理器。數(shù)據(jù)源我用的是Druid連接池配置了初始連接數(shù)、最大連接數(shù)、連接等待時間這些參數(shù)。SqlSessionFactoryBean的configLocation指向mybatis-config.xmlmapperLocations指向classpath:mapper/*.xml這樣MyBatis就知道去哪找SQL映射文件。spring-mvc.xml里配置了組件掃描只掃Controller包開啟注解驅(qū)動 mvc:annotation-driven/ 配置視圖解析器InternalResourceViewResolver設了prefix是/WEB-INF/views/suffix是.jsp同時配置靜態(tài)資源映射把js、css、images這些目錄放行。mybatis-config.xml里主要是開啟駝峰命名映射mapUnderscoreToCamelCase設為true這樣數(shù)據(jù)庫的user_id字段就能自動映射到Java實體的userId屬性省得寫一大堆resultMap。還有日志功能我用的是logback方便調(diào)試SQL。這三個配置文件的協(xié)作關系我建議新人在理解之后再復制粘貼不然出錯的時候完全沒法排查。整合的本質(zhì)就是Spring通過配置去創(chuàng)建各個組件的實例且把它們的依賴關系通過IoC注入串聯(lián)起來。3.3 前端頁面與靜態(tài)資源組織這個項目的前端用的是JSP JSTL CSS/JS。頁面文件放在WEB-INF/views目錄下按模塊分子目錄user、product、cart、order、admin。所有用戶可以直接訪問的頁面通過Controller轉(zhuǎn)發(fā)過去WEB-INF目錄下的頁面瀏覽器是直接訪問不到的這種設計本身也是一種安全控制。靜態(tài)資源CSS、JS、圖片、富文本編輯器等放在webapp/static目錄下通過spring-mvc.xml里的mvc:resources mapping/static/** location/static//放行。前端頁面我用了簡單的CSS樣式?jīng)]有引入前端框架。如果你想讓界面更好看可以引入Bootstrap或者LayuiLayui對后端開發(fā)者特別友好表格、分頁、彈窗組件直接調(diào)用封裝好的方法就行省了很多寫JS的時間。4. 核心功能模塊實現(xiàn)詳解4.1 會員注冊登錄與會話管理注冊登錄是所有電商系統(tǒng)的入口也是答辯時老師必看的功能。注冊的邏輯比較簡單表單提交到ControllerService層先校驗用戶名是否重復再用MD5加鹽的方式處理密碼最后插入數(shù)據(jù)庫。這里有個細節(jié)JSP表單提交的時候要設置accept-charsetUTF-8否則后端接收中文用戶名很容易亂碼。登錄之后我用Session保存用戶信息key是uservalue是User對象。攔截器配置是在spring-mvc.xml里注冊一個HandlerInterceptor配置攔截路徑是/order/、/cart/、/user/**這些需要登錄才能訪問的路徑放行路徑是登錄、注冊、商品列表、商品詳情。攔截器里從Session取用戶取不到就重定向到登錄頁。這么做比在每個Controller里判斷用戶是否登錄要優(yōu)雅得多。4.2 商品展示與購物車的存儲策略商品列表頁我做了分類導航加列表展示。左側(cè)是分類樹從數(shù)據(jù)庫查出所有分類在Service層按照父子關系組裝成樹形結(jié)構然后渲染到頁面上。商品列表支持按分類篩選、按銷量排序、按價格排序。價格排序我用的是SQL層面order by銷量排序也一樣不用在Java內(nèi)存里排序效率高很多。商品詳情頁包括商品圖片、名稱、價格、庫存、銷量和加入購物車按鈕。庫存信息要做個判斷如果庫存為0按鈕置灰并提示缺貨。購物車這塊有個經(jīng)典的方案選擇題存Session還是存數(shù)據(jù)庫這個項目兩套都做了。未登錄狀態(tài)下把購物車數(shù)據(jù)存在Session里登錄狀態(tài)下存數(shù)據(jù)庫。實現(xiàn)邏輯是用戶點擊加入購物車時判斷登錄狀態(tài)未登錄就存在Session的Map結(jié)構里登錄狀態(tài)就調(diào)用CartService寫入數(shù)據(jù)庫。結(jié)算的時候優(yōu)先讀數(shù)據(jù)庫購物車Session里的購物車在下單前合并進來。這套混合方案雖然代碼量多一點但用戶體驗和數(shù)據(jù)結(jié)構都兼顧了我自己做電商項目也一直沿用這個思路。4.3 訂單生成與事務控制的藝術下單流程是整套系統(tǒng)技術含量最高的部分也是面試和答辯的時候最容易被深挖的環(huán)節(jié)。我簡單地描述一下這個流程用戶確認購物車里的商品填寫收貨地址、姓名、電話點擊提交訂單后后端做以下幾件事第一步從購物車里查出用戶勾選的商品明細校驗每個商品的庫存是否充足第二步計算訂單總價第三步生成訂單號和訂單記錄狀態(tài)設為待付款第四步寫入訂單明細表第五步扣減商品庫存第六步清空購物車記錄。這六步操作必須放在同一個事務里任何一步失敗整個訂單流程都回滾不允許出現(xiàn)訂單生成了但庫存沒扣或者扣了庫存但訂單沒了的情況。事務配置在applicationContext.xml里通過Spring的DataSourceTransactionManager管理事務管理方式用的是注解Transactional在OrderServiceImpl的createOrder方法上標注配合rollbackFor Exception.class。這里要注意一個細節(jié)Transactional只對通過Spring代理調(diào)用的方法中拋出的RuntimeException默認回滾如果方法內(nèi)部catch住了異常而不拋出事務是不會回滾的。我第一次寫的時候就在Service方法里catch了SQLException打印了一下日志就算完了結(jié)果數(shù)據(jù)該回滾的沒回滾排查了好久才發(fā)現(xiàn)是這個原因。所以事務方法里要么不寫try-catch要么catch之后手動設置TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()。訂單狀態(tài)流轉(zhuǎn)我也用了狀態(tài)機的方式管理。定義了訂單狀態(tài)的常量待付款、待發(fā)貨、已發(fā)貨、已完成、已取消。用戶取消訂單是待付款狀態(tài)下可以操作超時訂單沒有自動取消的邏輯課程設計不需要定時任務后臺管理員可以修改訂單狀態(tài)。每次狀態(tài)變更之前先校驗當前狀態(tài)是不是可以流轉(zhuǎn)的狀態(tài)。這套狀態(tài)流轉(zhuǎn)邏輯維護起來清晰、不容易出錯。4.4 后臺管理商品與訂單的一體化管理后臺功能我放在了/admin路徑下同樣通過攔截器做權限控制。管理員登錄時校驗賬號的角色字段我用的方案是在用戶表加了一個role字段區(qū)分ADMIN和USER兩種角色。雖然這個項目沒有做RBAC權限模型但對于課程設計來說夠用了。后臺的商品管理支持分頁查詢、添加商品、編輯商品、上下架操作。圖片這塊本地存儲即可上傳到服務器的某個目錄然后數(shù)據(jù)庫存圖片的URL路徑。需要注意的一點是上傳圖片的時候要限制文件類型和大小。我寫了一個簡單的FileUploadUtil工具用Apache Commons FileUpload解析上傳請求限制圖片后綴只能是jpg、png、gif大小不能超過2MB然后生成UUID作為文件名存入upload目錄。訂單管理主要功能是查詢所有訂單、按訂單號或用戶查詢、查看訂單明細、修改訂單狀態(tài)發(fā)貨、完成。后臺首頁我還放了一個簡單的數(shù)據(jù)統(tǒng)計看板查詢總用戶數(shù)、總商品數(shù)、今日訂單數(shù)、累計銷售額這些用簡單的聚合SQL就能查出來。做這些統(tǒng)計不是為了炫技而是讓項目在演示的時候有完整感演示的時候老師看到數(shù)據(jù)面板會比看到純列表頁的印象分高不少。5. 常見問題與排查技巧實錄5.1 高頻問題速查表我整理了一份這個項目實戰(zhàn)中最容易踩的坑基本覆蓋了從搭建到運行的絕大多數(shù)問題直接給結(jié)論和解決方案?,F(xiàn)象根因解決方案啟動Tomcat后訪問頁面報404缺少SpringMVC的DispatcherServlet配置或web.xml版本過低檢查web.xml中servlet-mapping的url-pattern是否為/并確認配置正確加載JSP頁面中文亂碼編碼不一致統(tǒng)一項目為UTF-8web.xml加CharacterEncodingFilterJSP頭加pageEncodingUTF-8數(shù)據(jù)庫連接串加characterEncodingutf8靜態(tài)資源css/js加載不出來SpringMVC攔截了靜態(tài)資源請求在spring-mvc.xml配置mvc:resources mapping/static/** location/static//控制臺報Invalid bound statement (not found)Mapper接口和XML映射文件沒有綁定檢查mybatis配置中mapper-locations路徑是否正確確認Mapper接口和XML的namespace一致連接數(shù)據(jù)庫報Access denied數(shù)據(jù)庫賬號密碼錯誤或權限不足核對jdbc.properties配置用命令行測試數(shù)據(jù)庫連接事務不生效沒有配置事務管理器或方法被catch吞掉異常配置DataSourceTransactionManager并開啟注解驅(qū)動事務方法不要自行catch異?;騝atch后手動setRollbackOnly數(shù)據(jù)插入中文變問號數(shù)據(jù)庫表字符集不是UTF-8建庫建表時指定utf8mb4連接串加useUnicodetruecharacterEncodingutf8頁面報500錯誤日志有ClassNotFoundException缺少某個依賴包檢查pom.xml確認spring-webmvc、jacskon、mysql驅(qū)動等依賴是否完整啟動時報BeanCreationException注入的Bean不存在或類型不匹配查看具體Bean名稱檢查掃描包路徑是否覆蓋到對應類檢查接口與實現(xiàn)類的注入關系5.2 我踩過的幾個典型錯誤復盤第一個坑是MyBatis的resultType和resultMap混用。一開始寫SQL的時候我圖省事把多表查詢的結(jié)果映射到Map上發(fā)現(xiàn)返回的數(shù)據(jù)里字段名對不上。后來我給訂單明細查詢寫了一個專門的VO類OrderItemVO用resultMap做了映射代碼清晰了很多。我的經(jīng)驗是單表查詢用resultType省事多表聯(lián)查或者字段名跟實體不一致的時候老老實實寫resultMap別偷懶。第二個坑是Tomcat的部署熱更新問題。修改Java代碼之后有時候Tomcat沒有自動重新加載頁面還是執(zhí)行的老代碼。排查這個問題的時候我先clean、rebuild項目不行就重啟Tomcat最后發(fā)現(xiàn)是IDEA的Tomcat集成配置里沒有勾選Deploy applications on server startup相關的自動更新選項。配置好之后開發(fā)效率提升不少。第三個坑是Druid連接池配置了防火墻filter導致本地連接被攔截。這個問題的現(xiàn)象是開發(fā)環(huán)境連不上MySQL檢查防火墻規(guī)則之后發(fā)現(xiàn)是allow的IP配置不對。排查思路出現(xiàn)數(shù)據(jù)庫連接問題時先甩開連接池框架直接用JDBC連接測試如果JDBC能通問題大概率出在連接池的配置上。這個排查順序幫我在后面的項目里省了很多時間。5.3 項目寫完后還能怎么擴展如果你做完這個基礎版之后還有余力我建議按下面的優(yōu)先級做一些擴展能把項目從課程設計提升到接近企業(yè)級應用的水平。第一優(yōu)先級引入Spring Security或者簡單的Shiro做登錄認證和權限控制替換掉我用的Session攔截器方案。這個改動對代碼的侵入性不大但安全性和可擴展性提升很多。第二優(yōu)先級給商品模塊加一個搜索功能。最簡單的方案是用MySQL的LIKE模糊查詢但如果商品數(shù)量多起來就得引入Elasticsearch或者用MySQL全文索引。哪怕只是做一個簡單的搜索框整個系統(tǒng)的可用性都能上一個臺階。第三優(yōu)先級做一個基于Redis的購物車存儲。Redis的hash結(jié)構非常適合做購物車用戶id做key商品id做field購買數(shù)量做value。用Redis的好處是性能好、生命周期可控、天然支持分布式環(huán)境。第四優(yōu)先級把支付流程接上支付寶沙箱環(huán)境。沙箱支付的接入流程跟真實支付幾乎一致申請一個沙箱賬號、配置密鑰、回調(diào)驗簽一套流程走下來對支付的理解會深很多。而且答辯的時候展示支付寶的支付頁面效果非常加分。根據(jù)我個人的經(jīng)驗這種SSM商城項目最怕的不是代碼難寫而是一個人悶頭寫、寫完了沒人看。做項目的過程中每完成一個模塊就自己動手測試一遍業(yè)務流程把異常情況比如庫存不足、重復提交訂單、并發(fā)扣庫存這些場景測一測你會對這個系統(tǒng)的理解深很多。最后一個建議無論你把這個項目用在什么場景記得把數(shù)據(jù)庫設計的文檔、關鍵接口說明、部署步驟整理成一個README或者項目說明文檔。答辯的時候老師翻到你的說明文檔印象分會高不少后面自己回頭改代碼也能少花很多時間回憶當初的思路。這套系統(tǒng)的價值不在于它有多先進而在于它把一個完整業(yè)務閉環(huán)用最經(jīng)典的Java技術棧從頭到尾實現(xiàn)了一遍——你把這個閉環(huán)吃透了后面學什么框架都不慌。