1. 程式人生 > 實用技巧 >S臺專案記錄-1

S臺專案記錄-1

1.VO與資料實體的使用問題

  最終方案:對於同一個實體資訊的訪問,大致的思路是,引數VO,返回呼叫方的VO以及資料實體。呼叫方將引數VO傳遞到Controller, Contorller呼叫具體的servcie來獲取資料模型,最後將資料模型例項轉化成返回值VO.做兩次解耦的原因如下,1.對於引數VO和資料模型的解耦,可以減少不必要資料屬性的傳遞,資料實體的屬性資訊也不用全部暴露給呼叫者。2.對於返回資訊與資料模型的解耦,便於做許可權控制,防止把多餘的資訊暴露給呼叫方。

*在分頁查詢時,增加對應的列表資訊VO,該VO專門用於列表資訊的展示。

2. VO的分類,今天自己思考了VO的分類,最後覺得對於基礎操作CRUD可以按照按操作許可權去分。例如,對於實體entity,可以建立對應entityUpdateSaveVO,對應於儲存和更新操作,因為往往更新和儲存操作可編輯的欄位對於使用者來說是比較一致的(如果差異很大建議分成兩個獨立VO)。另外,對於檢視單個實體詳情操作,可以單獨建立entityDetailVO,這個VO裡包含所有可以被呼叫方檢視的資訊。

結合1中所說,對於實體entity,一般來說常用的VO有

-entityUpdateSaveVO:包含使用者可編輯的欄位

-entityDetailVO:包含使用者可檢視的欄位

-entityListVO:包含列表資訊欄位(往往是entity的一少部分)

-entityListQueryParamVO:包含分頁查詢條件欄位,根據條件分頁查詢滿足條件的entity

3.核心服務與具體業務擴充套件遇到的問題

  在我們這個專案中,核心服務是指標對抽象核心業務實體提供的一些最基本的服務,目的就是可以讓不同的具體業務都能使用這些核心服務,從而實現低成本的業務擴充套件和高複用的服務呼叫。當然這期間肯定會遇到各種中問題,這一小節專門記錄。

  3.1核心業務術語與具體業務不一致的問題

    提供的核心業務服務相當於一套抽象的業務描述,在編寫這些服務的時候我們會自定義一套業務術語(或者取自使用最多的具體業務),所以對應的資料實體,展示層實體的類名,屬性名對應到具體業務中可能都會有變化。例如,在核心業務服務中,我們把一個業務實體一個屬性叫做 coreField1,但是在具體的業務中,同樣的實體屬性,它的名稱可能是businessField1。那麼,對於各個模型類中的屬性名,我們都需要做相應的轉化。具體轉化方式有如下幾種:

  1)呼叫核心服務後,new出具體業務模型類,然後getter/setter來轉化。此種情況,只適合與類屬性很少的情況,如果類的數量以及類屬性的數量很多的話,就得在所有呼叫核心服務的地方寫一大堆getter,setter.

  2) 具體業務也使用和核心業務相同的資料模型,即類名屬性名都相同,從而可以使用BeanUtil實現轉化。這種做法雖然方便,但是會降低程式碼的可讀性,即程式設計師在具體業務中編寫程式碼時,都要在腦袋裡做一次轉化,這樣很容易導致術語理解混淆不清晰。另外,後續的程式碼維護會出現同樣的問題。

  3)註解的方式。使用自定義註解,在具體業務類屬性上標註出核心業務類對應屬性的名字,然後自己編寫兩個類相互轉化的方法。

  4)繼承的方式。具體業務模型類,繼承核心的模型類。然後在自定義屬性的getter/setter呼叫父類屬性的getter/setter。優點:簡單易實現。缺點:要修改核心業務資料模型的欄位屬性(private -> protected)。

  5)組合的方式。在具體業務模型類中宣告一個核心業務類。然後再具體屬性getter/setter時使用核心業務屬性。本來個人認為這是最好的方式,所以給出實現說明如下。向比於3)這種方式簡單直白,並且不會有很高的轉化成本,但是寫完發現有個很嚴重的問題,就是每個相關的模型類都需要手動的去寫這些getter/setter,類多欄位多的時候真的會懷疑人生,而且這些

  

 1  //具體業務模型類
 2  class BusinessEntity {
 3 
 4     //對應核心業務模型類
 5     private CoreEntity coreEntity;
 6     //具體業務屬性    
 7     private String BusinessField1;
 8     
 9     public String getBusinessField1{
10        return coreEntity.getCoreField1;  
11      } 
12 
13     public void setBusinessField1(String businessField1){
14          this.coreEntity.setCoreField1 = businessField1; 
15    }
16  }

4.多表連線條件查詢--Mybatis Plus的深坑

  4.1 其實單純說是Mybatis Plus(MP)的坑也不全面。事情是這樣的,多表連線查詢(如一對多關係)其實mybatis有很清楚的配置,就是在結果對映<ResultMap>裡面配置好<Collection>或者<Association>就行了。但是這種方式,不適合可以根據條件去篩選多端實體的情況。比如,一個班級有多個學生,那麼可能會有如下定義:

1 class ClassOne {
2    private String classId;
3    private String className;
4    //該班級裡的學生
5    private List<Student> studentList;
6 }

如果只是根據ClassOne的本生的屬性去去分頁查班級資訊和學生資訊,那MP可以完美支援,只要配置好Collection屬性,然後在select班級語句的後面加上對應的模糊查詢判斷即可。但是要根據學生的資訊,來查詢班級的情況,那就不好使了,比如要查詢‘李’姓學生的所在班級情況,如果把查詢條件通過<Collection>標籤中的column屬性帶到查詢學生的語句中去(比如,column(classId=classId,李=studentName)我也是在網上看到的,神奇的寫法),條件是可以帶過去的,但是如果所有班級都沒有李姓同學,那麼MP仍然會返回所有的班級實體,只是所有的studentList均為null。顯然這種情況不是我們需要的,我們需要的是整體結果返回為null.所以這種情況還是老老實實寫left join然後在最後加上所有可能的判斷條件吧。

4.2另一個MP和github pageHelper分頁的神坑,就是在寫left join的時候,在選擇條件裡寫的select t1.fieldA, t2.fieldA fromt1 left join t2 on t1.id = t2.id.會報“未明確定義列”的錯誤。但是這句語句在PL/SQL裡面是可以執行的。後面檢查發現,pageHelper分頁會巢狀一層sql在select語句外面,從而導致t1, t2識別符號失效,從而導致最終的sql裡面出現兩個fieldA,然後觸發了“未明確定義列”的錯誤。所以解決方法也很簡單,起別名就好了,把select語句改成select t1.fieldAas t1Field, t2.fieldA as t2Field fromt1 left join t2 on t1.id = t2.id.問題就解決了,當然對應實體屬性對映關係也要記得改一下!

4.3 MP和pageHelper的坑又來了, 問題大概是這樣的, 因為有一對多關聯查詢,實體類對應關係大概就是如下

1 class Entity1{
2  private String field1;
3  private List<Entity2> entity2List;
4 }

在查詢的配置裡配置了<Collection>但是來關聯list2,但是用left join的方式在分頁查詢的時候查出3條記錄,但是如果其中有兩個id相同的Entity1型別的關聯記錄,則MP會自動將其合併成兩條記錄,從而導致最終返回的記錄數為2而不是3,從而導致分頁混亂。因為時間問題,這裡還是沿用兩個框架(MP和PageHelper),解決方法是,查兩次,第一次根據條件查詢出Entity1實體資訊與其關聯的Entity2的id資訊,然後再根據id資訊單獨查詢出所有滿足條件的entity2例項,並塞到對應的entity2List中。當然,可能會有人說可以現查出滿足條件的Entity1,然後再查關聯的Entity2,可以這樣做的場景是,查詢條件不包括任何與Entity2實體相關的欄位,否則最終查出的記錄數可能還是無法滿足實際頁數需求(手頭的專案就是要根據Entity1和Entity2兩個實體的條件來查詢的)。這個方案雖然可行,但是肯定還有待優化。

*從遇到這個問題,我也想過,可能一對多關聯分頁查詢不太適合用這種List來封裝多端物件的情況(或者說當如果是一對多分頁查詢,就不太適合把Entity2中的內容也作為查詢條件),如果查詢條件只針對Entity1那是沒問題的,但是如果同樣涉及到Entity2,那麼,可能直接如下組合會比較方便進行關聯分頁查詢,當然,這樣的話,當關聯多個Entity2時會造成Entity1的資料冗餘,但是也確實有些業務分頁介面是冗餘顯示的,這個就得看具體需求了。不過這種組合方式只需要查一次庫,所以效率肯定會更高。

1 class Entity1{
2 private String field1;
3 private Entity2 entity;
4 }

4.4Mybatis Plus對於xml檔案,會將值為false的boolean型別的變數自動轉義成空字元,所以儘量不要傳遞到boolean型別的值到xml檔案當中,更好的方案是用有意義的字串。對於null值也一樣,本次專案中有的值是讓前端傳過來null表示查詢全部,但是這並不是一個好的實踐。

7)經驗教訓:需求不明確物件的關係時,不要寫程式碼。事情是這樣的,專案中有兩個實體物件的關係不明確,不知道是一對一,一對多還是多對多,後來,負責人說先按一對多來寫,結果,寫完一堆多之後,確定了是多對多關係,這真是日了狗了。後來加表,該關聯,真是沒事找事,以後一定記住,不清楚需求的就不要寫!!

8)專案經驗,初始階段可以根據UI把字典值確定好,不要等到前後臺對接的時候再確定。(會被催)

9)沙雕Maven, 自己打了一個jar包放到公司的Maven庫上,然後引入Pom檔案就是找不到,結果,把本地庫中對應的引入資料夾除開jar之外的檔案都刪除才解決問題。

10)當查詢某個時間段的資料記錄時(具體到天),需要注意的是,對於時間的結束範圍,需要將其設定成當天的23:59:59,如果不設定,XML檔案傳入生成的sql預設是當天的零點,顯然是不正確的。

11)業務編碼的順序,最好是insert -> update -> queryDetail -> queryByPage一般來說實現難度會是遞增的,然後新建儲存的時候會對業務和業務實體有一個更好的瞭解,在之後寫業務性比較強的查詢程式碼時,會有一個更好的理解,可以避免很多麻煩。

12)設計資料庫表時,對於字典值屬性,對照UI設計,或者根據經驗,如果欄位會需要支援模糊查詢的話,最好把字典值的翻譯也作為欄位存在資料表中,否則,模糊查詢會很麻煩。<經驗>

13) Oracle字元相關

  varchar2 最多存4000位元組,1.當字符集為GBK時(2位元組對應一個字元),可以存2000漢字;2.當字符集為UTF-8時,最多存1333個漢字,此時,varchar2(1333 char) 等價於 varchar(4000 char)

14) 關於批量刪除介面,如果需要實現批量刪除,注意處理刪除失敗時的邏輯;如果刪除中發生異常導致部分實體刪除失敗, 一般處理可以分為兩種,1.,操作失敗,刪除結果為操作前的結果。2.操作部分失敗,以刪除成功的實體被刪除,未被刪除的實體資訊返回給使用者。

15)對於任何設計,或者實現方案,應當綜合考慮風險,風險出現的概率以及後果,來加以比較抉擇。