畢設(shè)實(shí)戰(zhàn):從數(shù)據(jù)庫(kù)設(shè)計(jì)到核心業(yè)務(wù)代碼)
最近遇到好幾個(gè)準(zhǔn)備計(jì)算機(jī)畢業(yè)設(shè)計(jì)的同學(xué)都在找SSM房屋裝修管理系統(tǒng)的源碼和實(shí)現(xiàn)文檔。這個(gè)題目我在帶的課程設(shè)計(jì)里也完整走過(guò)一遍所以單獨(dú)寫(xiě)一篇把從需求到數(shù)據(jù)庫(kù)再到核心業(yè)務(wù)代碼的整套思路都拆開(kāi)講講。房屋裝修管理系統(tǒng)不是簡(jiǎn)單的新增刪除它把業(yè)主、裝修公司、管理員三個(gè)角色串在一條業(yè)務(wù)線上業(yè)主發(fā)裝修需求公司報(bào)價(jià)雙方簽合同然后施工、報(bào)進(jìn)度、驗(yàn)收、評(píng)價(jià)。對(duì)于一個(gè)畢設(shè)來(lái)說(shuō)它比普通的學(xué)生管理、圖書(shū)管理更能體現(xiàn)完整的業(yè)務(wù)閉環(huán)答辯時(shí)也更有東西可講。這篇文章適合正在做JavaWeb畢業(yè)設(shè)計(jì)、需要快速理解SSM項(xiàng)目或者準(zhǔn)備用這套源碼二改的同學(xué)看完之后你至少能說(shuō)清楚每一張表是干什么的每個(gè)核心接口是怎么流轉(zhuǎn)的。1. 畢設(shè)選題里的安全牌為什么SSM加裝修管理仍然能打1.1 選題同質(zhì)化嚴(yán)重裝修管理好在哪每年畢業(yè)設(shè)計(jì)提交上來(lái)打開(kāi)一看一半是校園二手交易平臺(tái)一半是圖書(shū)管理系統(tǒng)剩下的是各種商城。不是說(shuō)這些題目不好而是太容易撞車(chē)開(kāi)題、答辯、論文查重的時(shí)候都很痛苦。房屋裝修管理系統(tǒng)的好處在于它有一個(gè)真實(shí)的行業(yè)場(chǎng)景業(yè)務(wù)流程比單一模塊的CRUD復(fù)雜一點(diǎn)但又不至于復(fù)雜到做不完。裝修行業(yè)里有一個(gè)天然的多角色協(xié)作鏈條業(yè)主想裝修裝修公司想接單中間有報(bào)價(jià)、合同、施工、驗(yàn)收、評(píng)價(jià)。這個(gè)鏈條本身就是一套狀態(tài)機(jī)需求先等待報(bào)價(jià)報(bào)價(jià)后等待業(yè)主確認(rèn)確認(rèn)后生成合同合同開(kāi)始施工施工階段要不斷更新進(jìn)度最后驗(yàn)收。每個(gè)狀態(tài)變化都對(duì)應(yīng)一個(gè)業(yè)務(wù)動(dòng)作每個(gè)動(dòng)作都會(huì)落到數(shù)據(jù)庫(kù)的某個(gè)表上。做這種系統(tǒng)論文里能畫(huà)出清晰的業(yè)務(wù)流程圖、用例圖、時(shí)序圖代碼里能體現(xiàn)多表關(guān)聯(lián)、事務(wù)控制、權(quán)限攔截比單純做一張圖書(shū)表有意義得多。1.2 SSM不是新技術(shù)但依然是畢業(yè)答辯里的穩(wěn)妥選擇很多同學(xué)會(huì)問(wèn)為什么不直接用SpringBoot如果學(xué)校允許用SpringBoot當(dāng)然更省事配置少、開(kāi)發(fā)快。但如果你所在學(xué)校課程體系里教的是SSM或者指導(dǎo)老師對(duì)框架底層比較看重那SSM反而是更穩(wěn)妥的選擇。原因很簡(jiǎn)單SSM需要你自己維護(hù)Spring的applicationContext.xml、SpringMVC的配置文件、MyBatis的Mapper映射這套配置過(guò)程能夠逼著你搞清楚Spring容器怎么啟動(dòng)、DispatcherServlet如何攔截請(qǐng)求、SqlSessionFactory是怎么構(gòu)建的。答辯時(shí)老師問(wèn)一句SpringMVC的執(zhí)行流程是什么你配置過(guò)、Debug過(guò)就能順著DispatcherServlet、HandlerMapping、Controller、ViewResolver一條線講下來(lái)。換成SpringBoot很多配置被自動(dòng)裝配隱藏掉了遇到同樣的問(wèn)題反而容易答不上來(lái)。用SSM實(shí)現(xiàn)房屋裝修管理系統(tǒng)代碼量會(huì)比SpringBoot大一些但是可控性很高。這個(gè)項(xiàng)目我測(cè)試過(guò)多次Tomcat 8.5加JDK 1.8跑得很穩(wěn)適合作為計(jì)算機(jī)畢業(yè)設(shè)計(jì)原創(chuàng)源碼去交付。1.3 源碼不是終點(diǎn)拿到手第一件事別急著運(yùn)行凡是帶贈(zèng)源碼字樣的畢設(shè)項(xiàng)目我都會(huì)提醒一句話源碼是用來(lái)學(xué)習(xí)不是用來(lái)直接交的。你把它下載下來(lái)以后第一步不是雙擊打開(kāi)數(shù)據(jù)庫(kù)腳本一頓執(zhí)行而是先花一晚上把目錄結(jié)構(gòu)、核心表設(shè)計(jì)、請(qǐng)求路由看明白。我看過(guò)太多同學(xué)答辯前一周才開(kāi)始看源碼結(jié)果連項(xiàng)目怎么啟動(dòng)都不知道更別提演示的時(shí)候突然報(bào)錯(cuò)。帶著源碼跑一遍只是會(huì)用能說(shuō)清楚每個(gè)表字段為什么這樣設(shè)計(jì)、每個(gè)接口為什么這樣寫(xiě)才是懂。這篇博文后面的內(nèi)容就是幫你從能跑走到能講。2. 需求分析不能拍腦袋裝修管理到底在管哪些事2.1 三角色與核心用例需求分析階段最容易犯的錯(cuò)誤是上來(lái)就畫(huà)頁(yè)面、寫(xiě)字段。正確的做法是先梳理角色和用例。這套系統(tǒng)里最核心的角色有三個(gè)業(yè)主客戶(hù)、裝修公司、系統(tǒng)管理員。角色核心操作典型訴求業(yè)主注冊(cè)登錄、發(fā)布裝修需求、查看報(bào)價(jià)、確認(rèn)合同、查看施工進(jìn)度、驗(yàn)收、評(píng)價(jià)找靠譜公司、控制預(yù)算、知道施工到哪一步裝修公司注冊(cè)登錄、查看需求、報(bào)價(jià)、接受合同、上報(bào)進(jìn)度、提交驗(yàn)收接到更多訂單、展示公司實(shí)力、及時(shí)同步施工情況系統(tǒng)管理員用戶(hù)管理、需求審核、公告發(fā)布、數(shù)據(jù)統(tǒng)計(jì)維護(hù)系統(tǒng)秩序、確保信息真實(shí)上網(wǎng)搜索或者看真實(shí)的裝修平臺(tái)會(huì)發(fā)現(xiàn)有些系統(tǒng)還會(huì)拆出設(shè)計(jì)師工長(zhǎng)監(jiān)理但畢設(shè)不建議拆太細(xì)。角色越多權(quán)限控制和頁(yè)面開(kāi)發(fā)工作量成倍上升。三個(gè)角色足夠形成業(yè)務(wù)差異也足夠在答辯時(shí)講清楚權(quán)限設(shè)計(jì)。2.2 一條完整的業(yè)務(wù)閉環(huán)把這套系統(tǒng)的業(yè)務(wù)閉環(huán)畫(huà)成流程應(yīng)該是這樣業(yè)主發(fā)布裝修需求填寫(xiě)房屋面積、戶(hù)型、裝修風(fēng)格、預(yù)算范圍、期望開(kāi)工時(shí)間等基本信息。需求庫(kù)里的裝修公司可以瀏覽這些需求有合適的就發(fā)起報(bào)價(jià)。業(yè)主看到多個(gè)報(bào)價(jià)后選擇合適的公司雙方自動(dòng)進(jìn)入合同簽訂環(huán)節(jié)。合同生成之后裝修公司會(huì)按施工階段持續(xù)上報(bào)進(jìn)度比如拆改階段、水電階段、泥瓦階段業(yè)主可以在系統(tǒng)里看到進(jìn)度百分比和文字描述。施工完成后裝修公司申請(qǐng)驗(yàn)收業(yè)主確認(rèn)通過(guò)后可以針對(duì)本次裝修寫(xiě)評(píng)價(jià)整條鏈路才算結(jié)束。你在答辯時(shí)如果能把這個(gè)閉環(huán)用一兩分鐘講出來(lái)老師立刻知道你不是在做一個(gè)零散的增刪改查系統(tǒng)。2.3 功能清單與判定標(biāo)準(zhǔn)需求分析之后要整理出一份功能清單。這里有個(gè)原則基礎(chǔ)功能要形成閉環(huán)加分功能量力而行?;A(chǔ)功能包括用戶(hù)注冊(cè)登錄、需求發(fā)布與列表查詢(xún)、報(bào)價(jià)管理、合同管理、施工進(jìn)度管理、驗(yàn)收管理、評(píng)價(jià)管理、后臺(tái)用戶(hù)管理。這些已經(jīng)能支撐一個(gè)完整的裝修業(yè)務(wù)流程。加分功能包括密碼加密存儲(chǔ)、攔截器權(quán)限校驗(yàn)、公告管理、需求多條件篩選、合同編號(hào)自動(dòng)生成、列表分頁(yè)顯示。如果時(shí)間和能力允許可以再加統(tǒng)計(jì)報(bào)表和文件上傳。我在給課程設(shè)計(jì)做驗(yàn)收時(shí)最看重的是狀態(tài)流轉(zhuǎn)是否閉環(huán)。很多項(xiàng)目看起來(lái)功能都有但需求發(fā)布完以后報(bào)價(jià)和合同之間沒(méi)有關(guān)聯(lián)進(jìn)度和合同之間也連不上這在老師眼里就是兩張獨(dú)立的表硬湊成一個(gè)系統(tǒng)。你寧可功能少一點(diǎn)也要保證主流程是通的。3. 數(shù)據(jù)庫(kù)表設(shè)計(jì)復(fù)盤(pán)如何用9張表?yè)纹鹫麄€(gè)系統(tǒng)3.1 用戶(hù)角色與公司信息的設(shè)計(jì)取舍數(shù)據(jù)庫(kù)設(shè)計(jì)是整個(gè)SSM房屋裝修管理系統(tǒng)的地基。我給的參考方案是9張核心表這在本項(xiàng)目源碼里可以對(duì)應(yīng)看到。第一組是用戶(hù)表建議命名為t_user。字段包含id、username、password、real_name、phone、role、avatar、create_time。其中role用整型區(qū)分角色比如0表示管理員、1表示業(yè)主、2表示裝修公司。這樣一張表完成登錄認(rèn)證簡(jiǎn)單直接。裝修公司不建議和用戶(hù)表合并成一張大表。雖然業(yè)務(wù)上裝修公司也是一個(gè)賬號(hào)但公司有公司名稱(chēng)、營(yíng)業(yè)執(zhí)照編號(hào)、地址、介紹、評(píng)分等信息和普通業(yè)主差異很大。如果全部塞進(jìn)t_user字段會(huì)很臃腫而且不符合單一職責(zé)的表設(shè)計(jì)原則。所以單獨(dú)建一張t_company表字段包括id、user_id、company_name、license_no、contact_person、address、intro、rating。user_id與t_user.id建立一對(duì)一關(guān)聯(lián)既保留了賬號(hào)登錄獨(dú)立性也能在裝修需求列表頁(yè)方便地顯示公司名稱(chēng)。3.2 業(yè)務(wù)主鏈需求、報(bào)價(jià)、合同第二組是業(yè)務(wù)主鏈上的三張表需求表t_demand、報(bào)價(jià)表t_quote、合同表t_contract。t_demand是業(yè)主發(fā)布需求的落點(diǎn)核心字段有id、user_id、house_area、house_layout、decoration_style、budget、description、province/city/address、status、create_time。status請(qǐng)一定用int建議定義成0待接單、1已報(bào)價(jià)、2已簽約、3施工中、4待驗(yàn)收、5已完成、6已關(guān)閉。t_quote是裝修公司對(duì)某條需求發(fā)起的報(bào)價(jià)核心字段id、demand_id、company_id、price、duration_days、plan_content、status、create_time。demand_id關(guān)聯(lián)需求表company_id關(guān)聯(lián)公司表。status可以定義成0待業(yè)主確認(rèn)、1已接受、2已拒絕。注意這里的設(shè)計(jì)邏輯一條需求可以被多家公司報(bào)價(jià)所以t_demand和t_quote是一對(duì)多關(guān)系。t_contract生成時(shí)機(jī)是業(yè)主接受了某條報(bào)價(jià)。核心字段contract_no、demand_id、quote_id、owner_id、company_id、total_price、start_date、end_date、status、create_time。從這張表開(kāi)始業(yè)務(wù)已經(jīng)從信息撮合進(jìn)入履約管理。contract_no可以用時(shí)間戳加隨機(jī)數(shù)生成答辯時(shí)也能說(shuō)這是業(yè)務(wù)編號(hào)的規(guī)范做法。3.3 輔助鏈路施工進(jìn)度、材料、驗(yàn)收、評(píng)價(jià)第三組是輔助鏈路t_progress、t_material、t_acceptance、t_comment。t_progress用于裝修公司按階段上報(bào)施工進(jìn)度。字段contract_id、stage、description、progress_percent、operator_id、create_time。其中stage可以約定為字符串比如拆改水電泥瓦木工油漆安裝竣工方便前端做時(shí)間線展示。一張合同下會(huì)有多條進(jìn)度記錄所以是一對(duì)多關(guān)系。t_material是材料清單表屬于錦上添花的功能。字段contract_id、material_name、quantity、price、create_time。有了這張表這套系統(tǒng)就比只報(bào)文字進(jìn)度的項(xiàng)目更有說(shuō)服力可以做一個(gè)簡(jiǎn)單的施工材料列表展示裝修過(guò)程的透明度。t_acceptance用于驗(yàn)收。字段contract_id、result、comment、check_time、owner_id。result可以用0待驗(yàn)收、1通過(guò)、2不通過(guò)。驗(yàn)收完成之后t_contract.status也應(yīng)該跟著變化這個(gè)要在Service層里用事務(wù)控制。t_comment是業(yè)主對(duì)裝修公司服務(wù)的評(píng)價(jià)。字段contract_id、user_id、company_id、score、content、create_time。這里也可以做一個(gè)小功能當(dāng)新的評(píng)價(jià)產(chǎn)生時(shí)更新t_company.rating讓列表頁(yè)的公司評(píng)分跟著變化。3.4 幾個(gè)容易踩的坑金額精度、外鍵約束、狀態(tài)字段數(shù)據(jù)庫(kù)設(shè)計(jì)里有三個(gè)常見(jiàn)坑這個(gè)項(xiàng)目里特別容易觸發(fā)。第一金額字段不要用double。裝修預(yù)算、報(bào)價(jià)金額、合同金額、材料單價(jià)全部用decimal(10,2)Java實(shí)體里用BigDecimal接收。用double計(jì)算金額輕則顯示0.30000000000000004重則在寫(xiě)入數(shù)據(jù)庫(kù)時(shí)出現(xiàn)精度誤差答辯時(shí)被問(wèn)到會(huì)很難看。第二不要過(guò)度使用外鍵。畢設(shè)系統(tǒng)里的外鍵約束會(huì)造成兩個(gè)麻煩刪除順序必須嚴(yán)格測(cè)試數(shù)據(jù)初始化經(jīng)常失敗聯(lián)表查詢(xún)時(shí)MyBatis的關(guān)聯(lián)映射也變得復(fù)雜。我的建議是表結(jié)構(gòu)里保留邏輯外鍵字段比如demand_id、company_id但物理外鍵可以不建。保證查詢(xún)時(shí)能關(guān)聯(lián)出數(shù)據(jù)即可這件事可以在答辯時(shí)說(shuō)成為了減少不必要的耦合使用應(yīng)用層事務(wù)保證一致性。第三狀態(tài)字段一定要加注釋。所有status字段建議在數(shù)據(jù)庫(kù)腳本里寫(xiě)成COMMENT 0待接單 1已報(bào)價(jià)...這樣的形式。你過(guò)兩天回來(lái)看代碼或者指導(dǎo)老師看數(shù)據(jù)庫(kù)能一眼明白每個(gè)數(shù)字的含義。代碼里也建議定義一個(gè)常量類(lèi)或者枚舉類(lèi)避免在各種Mapper XML里寫(xiě)裸數(shù)字。4. SSM工程落地項(xiàng)目結(jié)構(gòu)和核心代碼這樣寫(xiě)才不亂4.1 推薦的分層結(jié)構(gòu)和Maven配置拿到源碼以后先看目錄結(jié)構(gòu)。一個(gè)規(guī)范的SSM項(xiàng)目通常是Maven構(gòu)建的JavaWeb工程推薦這樣分層src/main/java/com/example/decoration ├── controller # 控制器層 ├── service # 業(yè)務(wù)接口 ├── service/impl # 業(yè)務(wù)實(shí)現(xiàn) ├── mapper # MyBatis Mapper接口 ├── entity # 實(shí)體類(lèi) ├── common # 統(tǒng)一返回結(jié)果、常量、工具類(lèi) └── interceptor # 登錄攔截器、權(quán)限攔截器 src/main/resources ├── mapper # MyBatis SQL映射XML ├── spring # applicationContext.xml、spring-mvc.xml └── db.properties # 數(shù)據(jù)庫(kù)連接配置 src/main/webapp ├── WEB-INF/web.xml └── jsp # 頁(yè)面文件這套分層的核心思路是Controller只負(fù)責(zé)接收請(qǐng)求、調(diào)用Service、返回結(jié)果不直接操作數(shù)據(jù)庫(kù)Service負(fù)責(zé)業(yè)務(wù)邏輯和事務(wù)Mapper只負(fù)責(zé)SQL。很多同學(xué)寫(xiě)畢設(shè)時(shí)喜歡在Controller里直接注入Mapper省事但答辯時(shí)老師問(wèn)你這個(gè)Service層有什么意義就會(huì)很尷尬。既然是畢業(yè)設(shè)計(jì)一定要把Controller-Service-Mapper三層分清楚。Maven的pom.xml里核心依賴(lài)就是spring-webmvc、mybatis、mybatis-spring、mysql-connector-java、druid、jstl、servlet-api、jackson。注意兩個(gè)點(diǎn)一是servlet-api要標(biāo)scopeprovided/scope否則Tomcat里會(huì)沖突二是MySQL驅(qū)動(dòng)版本要和自己本地的MySQL版本匹配5.7用5.1.49沒(méi)問(wèn)題8.x就要用8.0.33。4.2 SSM常用注解和登錄攔截器實(shí)現(xiàn)這套系統(tǒng)里會(huì)用到不少SSM常用注解答辯前必須能說(shuō)清楚Controller負(fù)責(zé)標(biāo)記控制器RequestMapping負(fù)責(zé)映射URLResponseBody負(fù)責(zé)把返回值轉(zhuǎn)成JSONService標(biāo)記業(yè)務(wù)層組件Autowired是依賴(lài)注入Transactional是聲明式事務(wù)。幾乎每個(gè)注解都是高頻考點(diǎn)。登錄攔截是本項(xiàng)目權(quán)限體系的核心。由于沒(méi)有引入Spring Security可以用SpringMVC的HandlerInterceptor來(lái)實(shí)現(xiàn)。public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); Object loginUser session.getAttribute(loginUser); if (loginUser null) { // 未登錄則跳轉(zhuǎn)到登錄頁(yè)也可以返回JSON response.sendRedirect(request.getContextPath() /login.jsp); return false; } return true; } }然后在spring-mvc.xml里注冊(cè)攔截器并排除登錄、注冊(cè)、靜態(tài)資源等路徑mvc:interceptors mvc:interceptor mvc:mapping path/**/ mvc:exclude-mapping path/login/ mvc:exclude-mapping path/register/ mvc:exclude-mapping path/static/**/ mvc:exclude-mapping path/css/**/ mvc:exclude-mapping path/js/**/ !-- 裝修需求列表和詳情可以允許游客查看 -- mvc:exclude-mapping path/demand/list/ mvc:exclude-mapping path/demand/detail/**/ bean classcom.example.decoration.interceptor.LoginInterceptor/ /mvc:interceptor /mvc:interceptors攔截器這部分建議一定自己寫(xiě)一遍。很多網(wǎng)上源碼的權(quán)限控制就是頁(yè)面隱藏按鈕根本沒(méi)有后端攔截演示時(shí)繞開(kāi)登錄直接訪問(wèn)后端接口照樣能查數(shù)據(jù)。加上這個(gè)攔截器功能和代碼質(zhì)量都會(huì)提升一個(gè)檔次。4.3 業(yè)主發(fā)布需求三層代碼如何聯(lián)動(dòng)以業(yè)主發(fā)布裝修需求這個(gè)核心動(dòng)作為例把三層代碼完整走一遍。Controller層Controller RequestMapping(/demand) public class DemandController { Autowired private DemandService demandService; PostMapping(/publish) ResponseBody public Result publish(RequestBody Demand demand, HttpSession session) { User user (User) session.getAttribute(loginUser); if (user null || user.getRole() ! 1) { return Result.error(請(qǐng)使用業(yè)主賬號(hào)登錄); } demand.setUserId(user.getId()); demand.setStatus(0); demandService.publish(demand); return Result.success(); } }Service接口和實(shí)現(xiàn)類(lèi)public interface DemandService { void publish(Demand demand); }Service public class DemandServiceImpl implements DemandService { Autowired private DemandMapper demandMapper; Override Transactional public void publish(Demand demand) { demand.setCreateTime(new Date()); demandMapper.insert(demand); } }MyBatis Mapper接口Repository public interface DemandMapper { int insert(Demand demand); }對(duì)應(yīng)的XMLinsert idinsert parameterTypecom.example.decoration.entity.Demand useGeneratedKeystrue keyPropertyid INSERT INTO t_demand (user_id, house_area, house_layout, decoration_style, budget, description, address, status, create_time) VALUES (#{userId}, #{houseArea}, #{houseLayout}, #{decorationStyle}, #{budget}, #{description}, #{address}, #{status}, #{createTime}) /insert注意幾個(gè)細(xì)節(jié)useGeneratedKeystrue和keyPropertyid保證了插入后能立刻拿到自增主鍵后續(xù)添加報(bào)價(jià)時(shí)可以直接使用ResponseBody配合統(tǒng)一的Result對(duì)象返回JSON比管理員返回字符串路徑更清晰。這套寫(xiě)法學(xué)到之后在其他頁(yè)面也一樣用。4.4 裝修公司上報(bào)施工進(jìn)度多條件更新和事務(wù)施工進(jìn)度上報(bào)是一個(gè)典型的多條件更新場(chǎng)景。裝修公司可能只更新進(jìn)度百分比也可能只更新描述文字甚至同時(shí)修改階段名稱(chēng)。如果用固定SQL每次都要把所有字段傳進(jìn)來(lái)很別扭。MyBatis的動(dòng)態(tài)SQL正好解決這個(gè)問(wèn)題。Controller層接收參數(shù)后組裝成一個(gè)Map或者直接使用Progress對(duì)象Service層調(diào)用Mapperupdate idupdateProgress parameterTypemap UPDATE t_progress set if teststage ! null and stage ! stage #{stage}, /if if testdescription ! null and description ! description #{description}, /if if testprogressPercent ! null progress_percent #{progressPercent}, /if /set WHERE id #{id} /update這里如果把更新進(jìn)度和更新合同狀態(tài)放在同一個(gè)Service方法里建議加上Transactional。比如當(dāng)施工進(jìn)度達(dá)到100%時(shí)自動(dòng)把合同狀態(tài)改為待驗(yàn)收。這個(gè)過(guò)程中有兩張表的數(shù)據(jù)要更新任何一步失敗都不能只更新一半否則數(shù)據(jù)就亂掉了。用Spring的聲明式事務(wù)只需要在方法上增加Transactional注解Spring會(huì)基于AOP幫你完成事務(wù)的開(kāi)閉和回滾。這也是答辯時(shí)值得重點(diǎn)講的技術(shù)點(diǎn)在SSM里事務(wù)默認(rèn)是針對(duì)運(yùn)行時(shí)異常的受檢異常不會(huì)觸發(fā)回滾。所以Service里不要隨意catch掉所有異常否則事務(wù)會(huì)失效。5. 把源碼跑起來(lái)部署步驟和五個(gè)我實(shí)測(cè)踩過(guò)的坑5.1 前置環(huán)境與初始化在開(kāi)始跑這套系統(tǒng)前建議準(zhǔn)備以下環(huán)境JDK 1.8、Maven 3.6、Tomcat 8.5或9.0、MySQL 5.7開(kāi)發(fā)工具可以用IDEA或Eclipse。這些版本組合我實(shí)測(cè)下來(lái)最穩(wěn)定。具體步驟大概是這樣用IDEA將源碼作為Maven工程導(dǎo)入等待依賴(lài)下載完成。在本機(jī)MySQL里創(chuàng)建數(shù)據(jù)庫(kù)名稱(chēng)建議用decoration_system編碼用utf8mb4。執(zhí)行源碼附帶的init.sql把建表和初始化數(shù)據(jù)一次導(dǎo)入。修改src/main/resources/db.properties里的數(shù)據(jù)庫(kù)地址、用戶(hù)名、密碼。配置Tomcat把項(xiàng)目deploy到Tomcat中啟動(dòng)。瀏覽器訪問(wèn)http://localhost:8080/看到登錄頁(yè)說(shuō)明啟動(dòng)成功。初始化數(shù)據(jù)里至少要有三個(gè)測(cè)試賬號(hào)管理員、業(yè)主、裝修公司。不然你在驗(yàn)收一個(gè)功能時(shí)還要臨時(shí)注冊(cè)兩個(gè)角色非常麻煩。5.2 常見(jiàn)坑一數(shù)據(jù)庫(kù)版本和驅(qū)動(dòng)不匹配這個(gè)坑出現(xiàn)頻率最高。學(xué)校機(jī)房很多是MySQL 5.5或5.7本地同學(xué)很多裝了MySQL 8.0。MySQL 8.0默認(rèn)認(rèn)證插件是caching_sha2_password老版本驅(qū)動(dòng)連接時(shí)會(huì)報(bào)Public Key Retrieval is not allowed。解決辦法有兩個(gè)一是統(tǒng)一用MySQL 5.7數(shù)據(jù)庫(kù)腳本幾乎不用改二是如果必須用MySQL 8.0要在db.properties里加allowPublicKeyRetrievaltrueuseSSLfalse并把驅(qū)動(dòng)換成com.mysql.cj.jdbc.Driver。我在源碼環(huán)境里默認(rèn)寫(xiě)的是MySQL 5.7配置如果你用8.0記得主動(dòng)改。5.3 常見(jiàn)坑二Mapper XML沒(méi)有被打包進(jìn)classesSSM項(xiàng)目里明明代碼看起來(lái)沒(méi)問(wèn)題啟動(dòng)后報(bào)Invalid bound statement (not found)大概率是Mapper XML沒(méi)有編譯到classes目錄下。Maven在打包時(shí)默認(rèn)只把src/main/resources下的文件輸出到classpath如果你的XML放在src/main/java里需要通過(guò)pom配置resources才能打包。最簡(jiǎn)單、也最建議的目錄結(jié)構(gòu)是Mapper接口放在src/main/java對(duì)應(yīng)的XML放在src/main/resources/mapper下兩者通過(guò)MyBatis的名稱(chēng)空間和接口全限定名關(guān)聯(lián)。然后在spring-context.xml里的SqlSessionFactoryBean配置中設(shè)置mapperLocations指向classpath:mapper/*.xml。bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ /bean5.4 常見(jiàn)坑三中文亂碼中文亂碼幾乎每個(gè)JavaWeb畢設(shè)項(xiàng)目都會(huì)遇到。亂碼原因一般有三個(gè)位置頁(yè)面本身編碼、請(qǐng)求編碼、數(shù)據(jù)庫(kù)連接編碼。JSP頁(yè)面用% page pageEncodingUTF-8 %。web.xml里配置CharacterEncodingFilter強(qiáng)制所有請(qǐng)求和響應(yīng)使用UTF-8。數(shù)據(jù)庫(kù)連接URL加characterEncodingutf8。數(shù)據(jù)庫(kù)表創(chuàng)建時(shí)統(tǒng)一用utf8mb4不要用默認(rèn)的latin1。很多同學(xué)只改了頁(yè)面沒(méi)有配置CharacterEncodingFilter結(jié)果POST請(qǐng)求里帶中文就會(huì)亂。這個(gè)問(wèn)題我排查過(guò)不少次配置一個(gè)全局過(guò)濾器基本能解決80%的亂碼問(wèn)題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 /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping5.5 常見(jiàn)坑四靜態(tài)資源被DispatcherServlet攔截SpringMVC的DispatcherServlet如果配置成/會(huì)默認(rèn)攔截所有請(qǐng)求包括css、js、圖片、html。如果不額外處理頁(yè)面就會(huì)變成裸體狀態(tài)沒(méi)有任何樣式。解決方式是在spring-mvc.xml里加入mvc:default-servlet-handler/ mvc:resources mapping/static/** location/static//然后把頁(yè)面的css和js統(tǒng)一放在webapp/static目錄下。這里要注意如果配置了自定義攔截器攔截/**靜態(tài)資源路徑一定要在exclude-mapping里排除否則登錄攔截器會(huì)把css請(qǐng)求也攔下來(lái)。5.6 常見(jiàn)坑五金額和日期字段的處理房屋裝修系統(tǒng)里到處是金額和時(shí)間處理不好就會(huì)出現(xiàn)兩類(lèi)問(wèn)題。一是金額精度問(wèn)題前面已經(jīng)提到用BigDecimal。二是在JSON返回給前端時(shí)日期會(huì)變成一串時(shí)間戳非常不友好。可以在SpringMVC配置里加入JodaTime或者fastjson的序列化配置簡(jiǎn)單一點(diǎn)的話可以在實(shí)體類(lèi)的日期字段上加JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8)。這個(gè)細(xì)節(jié)屬于沒(méi)寫(xiě)也不影響功能但寫(xiě)了會(huì)讓代碼更完整的類(lèi)型答辯時(shí)能給老師留下好印象。6. 答辯之前怎么把別人寫(xiě)的源碼講成自己的亮點(diǎn)6.1 講清楚業(yè)務(wù)閉環(huán)畢業(yè)答辯時(shí)間通常很緊張老師不可能聽(tīng)你逐個(gè)頁(yè)面念功能。建議用三分鐘時(shí)間只講一條主線業(yè)主發(fā)布需求裝修公司報(bào)價(jià)業(yè)主確認(rèn)簽約裝修公司更新進(jìn)度業(yè)主驗(yàn)收評(píng)價(jià)。這條線每走到一個(gè)節(jié)點(diǎn)指出對(duì)應(yīng)的表、對(duì)應(yīng)的狀態(tài)變化、對(duì)應(yīng)的接口老師立刻就知道你是真懂這個(gè)項(xiàng)目。比如講到需求發(fā)布你可以說(shuō)業(yè)主發(fā)布需求前端把表單數(shù)據(jù)提交給/demand/publishController層判斷登錄用戶(hù)角色后調(diào)用Service插入t_demand記錄初始狀態(tài)是0待接單。后面裝修公司登錄后查詢(xún)狀態(tài)為0的需求列表發(fā)起報(bào)價(jià)寫(xiě)入t_quote。每個(gè)業(yè)務(wù)動(dòng)作都直接驅(qū)動(dòng)狀態(tài)變化這就是狀態(tài)流轉(zhuǎn)。6.2 四類(lèi)高頻追問(wèn)和參考答案畢業(yè)設(shè)計(jì)中老師非常喜歡圍繞框架、數(shù)據(jù)庫(kù)、并發(fā)、安全來(lái)提問(wèn)。我總結(jié)過(guò)幾個(gè)高頻問(wèn)題你可以照著準(zhǔn)備。問(wèn)題核心作答思路SpringMVC的執(zhí)行流程用戶(hù)請(qǐng)求到達(dá)DispatcherServlet通過(guò)HandlerMapping找到對(duì)應(yīng)的Controller方法執(zhí)行后返回ModelAndView再經(jīng)過(guò)ViewResolver解析視圖。如果用了ResponseBody則直接由消息轉(zhuǎn)換器輸出JSONMyBatis和Hibernate的區(qū)別Hibernate是全自動(dòng)ORM把Java對(duì)象和數(shù)據(jù)庫(kù)表映射后不需要寫(xiě)SQLMyBatis是半自動(dòng)ORMSQL由開(kāi)發(fā)者自己控制和優(yōu)化。本項(xiàng)目多表聯(lián)查較多用MyBatis寫(xiě)動(dòng)態(tài)SQL更直觀項(xiàng)目里事務(wù)怎么控制在Spring配置文件中開(kāi)啟tx:annotation-driven在Service方法上使用Transactional保證多個(gè)表操作要么同時(shí)成功要么同時(shí)回滾如何防止SQL注入MyBatis的#{}底層使用PreparedStatement預(yù)編譯傳入的參數(shù)被當(dāng)作參數(shù)值不會(huì)拼接進(jìn)SQL如果用${}就要自己過(guò)濾。本項(xiàng)目統(tǒng)一使用#{}并在攔截器中校驗(yàn)登錄狀態(tài)還有一個(gè)常被問(wèn)到的為什么不用SpringBoot你只要從當(dāng)前學(xué)校課程體系以SSM為主SSM配置過(guò)程有助于理解框架運(yùn)行機(jī)制兩個(gè)角度去回答基本不會(huì)卡殼。6.3 低成本二次開(kāi)發(fā)建議源碼不是交完作業(yè)就結(jié)束了。如果你想在這個(gè)項(xiàng)目上加一些自己的東西以下三個(gè)方向成本都不高但是效果很明顯。一是加入ECharts統(tǒng)計(jì)報(bào)表。在后臺(tái)管理頁(yè)面統(tǒng)計(jì)不同裝修風(fēng)格的需求數(shù)量或者每月新增合同數(shù)量。這個(gè)只需要寫(xiě)一個(gè)聚合查詢(xún)SQL返回一個(gè)包含名稱(chēng)和數(shù)量的列表前端用ECharts畫(huà)柱狀圖或餅圖即可。它是典型的以項(xiàng)目數(shù)據(jù)為基礎(chǔ)的拓展功能論文里也好寫(xiě)。二是增加Excel導(dǎo)出。合同列表加一個(gè)導(dǎo)出Excel按鈕后端用EasyPOI或POI把查詢(xún)結(jié)果寫(xiě)入Excel前端點(diǎn)擊下載。這個(gè)功能能體現(xiàn)你對(duì)文件流和工具庫(kù)的掌握工作量不大但非常出效果。三是如果學(xué)校允許SpringBoot可以嘗試把這套系統(tǒng)改造成SpringBoot版本。把web.xml配置遷移成配置類(lèi)把spring的XML配置改成注解你會(huì)發(fā)現(xiàn)框架之間遷移的邏輯是相通的這個(gè)改造過(guò)程能讓你對(duì)SSM的理解更深一層。最后再分享一個(gè)實(shí)際經(jīng)驗(yàn)?zāi)玫竭@套SSM房屋裝修管理系統(tǒng)源碼之后我建議你第一周只干一件事把核心表之間的關(guān)系畫(huà)出來(lái)然后對(duì)著源碼把需求發(fā)布和報(bào)價(jià)確認(rèn)這兩個(gè)接口的完整調(diào)用鏈走一遍。走通了后面的進(jìn)度上報(bào)、驗(yàn)收評(píng)價(jià)、后臺(tái)管理都是一樣的套路。自己動(dòng)手改過(guò)、斷點(diǎn)打過(guò)的代碼答辯時(shí)才真正說(shuō)得出口。