java開發(fā)MVC三層架構(gòu)上再加一層Manager層原理詳解
MVC三層架構(gòu)
我們在剛剛成為程序員的時(shí)候,就會被前輩們 “教育” 說系統(tǒng)的設(shè)計(jì)要遵循 MVC(Model-View-Controller)架構(gòu)。它將整體的系統(tǒng)分成了 Model(模型),View(視圖)和 Controller(控制器)三個(gè)層次,也就是將用戶視圖和業(yè)務(wù)處理隔離開,并且通過控制器連接起來,很好地實(shí)現(xiàn)了表現(xiàn)和邏輯的解耦,是一種標(biāo)準(zhǔn)的軟件分層架構(gòu)。
MVC分層架構(gòu)是架構(gòu)上最簡單的一種分層方式。為了遵循這種分層架構(gòu)我們在構(gòu)建項(xiàng)目時(shí)往往會建立這樣三個(gè)目錄:controller、service 和 dao,它們分別對應(yīng)了表現(xiàn)層、邏輯層還有數(shù)據(jù)訪問層。
每層的作用如下:
Controller層:主要是對訪問控制進(jìn)行轉(zhuǎn)發(fā),各類基本參數(shù)校驗(yàn),或者不復(fù)用的業(yè)務(wù)簡單處理。
Service層:主要是處理業(yè)務(wù)邏輯和事務(wù)
Dao層:負(fù)責(zé)與底層數(shù)據(jù)庫MySQL,Oracle等進(jìn)行數(shù)據(jù)交互
可是隨著我們的業(yè)務(wù)邏輯越來復(fù)雜,代碼寫的越來越多,這種簡單的三層架構(gòu)的問題也越來越明顯。
MVC架構(gòu)弊端
傳統(tǒng)的MVC分層有以下幾個(gè)很明顯的問題:
Service層代碼臃腫
Service層很容易出現(xiàn)大事務(wù),事務(wù)嵌套,導(dǎo)致問題很多,而且極難排查
dao層參雜業(yè)務(wù)邏輯
dao層sql語句復(fù)雜,關(guān)聯(lián)查詢比較多
為了解決這個(gè)問題,我們參考《alibaba java開發(fā)手冊》,在Service層之下再獨(dú)立出一個(gè)通用業(yè)務(wù)處理層(Manager層)
在這個(gè)分層架構(gòu)中主要增加了 Manager 層,它與 Service 層的關(guān)系是:Manager 層提供原子的服務(wù)接口,Service 層負(fù)責(zé)依據(jù)業(yè)務(wù)邏輯來編排原子接口。
Manager層的特征
在《alibaba java開發(fā)手冊》中是這樣描述Manager層的:
Manager 層:通用業(yè)務(wù)處理層,它有如下特征:
對第三方平臺封裝的層,預(yù)處理返回結(jié)果及轉(zhuǎn)化異常信息,適配上層接口;對 Service 層通用能力的下沉,如緩存方案、中間件通用處理;與 DAO 層交互,對多個(gè) DAO 的組合復(fù)用。
在實(shí)際開發(fā)中我們可以這樣使用Manager層
復(fù)雜業(yè)務(wù),service提供數(shù)據(jù)給Manager層,負(fù)責(zé)業(yè)務(wù)編排,然后把事務(wù)下沉到Manager層,Manager層不允許相互調(diào)用,不會出現(xiàn)事務(wù)嵌套。
專注于不帶業(yè)務(wù)sql語言,也可以在manager層進(jìn)行通用業(yè)務(wù)的dao層封裝。
避免復(fù)雜的join查詢,數(shù)據(jù)庫壓力比java大很多,所以要嚴(yán)格控制好sql,所以可以在manager層進(jìn)行拆分,比如復(fù)雜查詢。
當(dāng)然對于簡單的業(yè)務(wù),可以不使用Manager層。
Manager層使用案例
這里我們舉個(gè)例子說明一下Manager層的使用場景:
假設(shè)你有一個(gè)用戶系統(tǒng),他有一個(gè)獲取用戶信息的接口,它調(diào)用邏輯Service層的 getUser
方法,getUser
方法又和 User DB
交互獲取數(shù)據(jù)。如下圖左邊展示部分。
這時(shí),產(chǎn)品提出一個(gè)需求,在 APP 中展示用戶信息的時(shí)候,如果用戶不存在,那么要自動給用戶創(chuàng)建一個(gè)用戶。同時(shí),要做一個(gè) HTML5 的頁面,HTML5 頁面要保留之前的邏輯,也就是不需要創(chuàng)建用戶。
此時(shí)按照傳統(tǒng)的三層架構(gòu),邏輯層的邊界就變得不清晰,表現(xiàn)層也承擔(dān)了一部分的業(yè)務(wù)邏輯,因?yàn)槲覀兺鶗诒憩F(xiàn)層Controller中增加業(yè)務(wù)邏輯處理,將獲取用戶和創(chuàng)建用戶接口編排起來。
而添加Manager層以后,Manager 層提供創(chuàng)建用戶和獲取用戶信息的接口,而 Service 層負(fù)責(zé)將這兩個(gè)接口組裝起來。這樣就把原先散布在表現(xiàn)層的業(yè)務(wù)邏輯都統(tǒng)一到了 Service 層,每一層的邊界就非常清晰了。
接下來我們看一段實(shí)際代碼說明一下Service層與Manager層如何進(jìn)行區(qū)分?
@Transactional(rollbackFor = Throwable.class) public Result<String> upOrDown(Long departmentId, Long swapId) { // 驗(yàn)證 1 DepartmentEntity departmentEntity = departmentDao.selectById(departmentId); if (departmentEntity == null) { return Result.error("部門xxx不存在"); } // 驗(yàn)證 2 DepartmentEntity swapEntity = departmentDao.selectById(swapId); if (swapEntity == null) { return Result.error("部門xxx不存在"); } // 驗(yàn)證 3 Long count = employeeDao.countByDepartmentId(departmentId); if (count != null && count > 0) { return Result.error("員工不存在"); } // 操作數(shù)據(jù)庫 4 Long departmentSort = departmentEntity.getSort(); departmentEntity.setSort(swapEntity.getSort()); departmentDao.updateById(departmentEntity); swapEntity.setSort(departmentSort); departmentDao.updateById(swapEntity); return Result.OK("success"); }
上面代碼在我們在我們采用三層架構(gòu)時(shí)經(jīng)常會遇到,那么它有什么問題呢?
上面的代碼是典型的長事務(wù)問題(類似的還有調(diào)用第三方接口),前三步都是使用 connection 進(jìn)行驗(yàn)證操作,但是由于方法上有@Transactional 注解,所以這三個(gè)驗(yàn)證都是使用的同一個(gè) connection。
若對于復(fù)雜業(yè)務(wù)、復(fù)雜的驗(yàn)證邏輯,會導(dǎo)致整個(gè)驗(yàn)證過程始終占用該 connection 連接,占用時(shí)間可能會很長,直至方法結(jié)束,connection 才會交還給數(shù)據(jù)庫連接池。
對于復(fù)雜業(yè)務(wù)的不可預(yù)計(jì)的情況,長時(shí)間占用同一個(gè) connection 連接不是好的事情,應(yīng)該盡量縮短占用時(shí)間。
說明:對于@Transactional 注解,當(dāng) spring 遇到該注解時(shí),會自動從數(shù)據(jù)庫連接池中獲取 connection,并開啟事務(wù)然后綁定到 ThreadLocal 上,如果業(yè)務(wù)并沒有進(jìn)入到最終的 操作數(shù)據(jù)庫環(huán)節(jié),那么就沒有必要獲取連接并開啟事務(wù),應(yīng)該直接將 connection 返回給數(shù)據(jù)庫連接池,供其他使用。
所以我們在加入Manager層以后可以這樣寫:
DepartmentService.java public Result<String> upOrDown(Long departmentId, Long swapId) { // 驗(yàn)證 1 DepartmentEntity departmentEntity = departmentDao.selectById(departmentId); if (departmentEntity == null) { return Result.error("部門xxx不存在"); } // 驗(yàn)證 2 DepartmentEntity swapEntity = departmentDao.selectById(swapId); if (swapEntity == null) { return Result.error("部門xxx不存在"); } // 驗(yàn)證 3 Long count = employeeDao.countByDepartmentId(departmentId); if (count != null && count > 0) { return Result.error("員工不存在"); } // 操作數(shù)據(jù)庫 4 departmentManager.upOrDown(departmentSort,swapEntity); return Result.OK("success"); }
DepartmentManager.java @Transactional(rollbackFor = Throwable.class) public void upOrDown(DepartmentEntity departmentEntity ,DepartmentEntity swapEntity){ Long departmentSort = departmentEntity.getSort(); departmentEntity.setSort(swapEntity.getSort()); departmentDao.updateById(departmentEntity); swapEntity.setSort(departmentSort); departmentDao.updateById(swapEntity); }
將數(shù)據(jù)在 service 層準(zhǔn)備好,然后傳遞給 manager 層,由 manager 層添加 @Transactional
事務(wù)注解進(jìn)行數(shù)據(jù)庫操作。
以上就是MVC三層架構(gòu)上再加一層Manager層原理詳解的詳細(xì)內(nèi)容,更多關(guān)于MVC架構(gòu)Manager層原理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java?map為什么不能遍歷的同時(shí)進(jìn)行增刪操作
這篇文章主要介紹了Java?map為什么不能遍歷的同時(shí)進(jìn)行增刪操作,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-07-07淺析RxJava處理復(fù)雜表單驗(yàn)證問題的方法
這篇文章主要介紹了RxJava處理復(fù)雜表單驗(yàn)證問題的相關(guān)資料,非常不錯具有參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06JAVA發(fā)送HTTP請求的多種方式詳細(xì)總結(jié)
目前做項(xiàng)目中有一個(gè)需求是這樣的,需要通過Java發(fā)送url請求,查看該url是否有效,這時(shí)我們可以通過獲取狀態(tài)碼來判斷,下面這篇文章主要給大家介紹了關(guān)于JAVA發(fā)送HTTP請求的多種方式總結(jié)的相關(guān)資料,需要的朋友可以參考下2023-01-01Java中將File轉(zhuǎn)化為MultipartFile的操作
這篇文章主要介紹了Java中將File轉(zhuǎn)化為MultipartFile的操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-10-10java后端實(shí)現(xiàn)信息分頁查詢的示例代碼
在一個(gè)頁面展示大量的用戶信息不便于觀看,因此就需要采用分頁展示的方法,本文就來為大家介紹一下java后端如何實(shí)現(xiàn)信息分頁查詢,需要的小伙伴可以參考下2023-11-11Java實(shí)現(xiàn)獲取客戶端真實(shí)IP方法小結(jié)
本文給大家匯總介紹了2種使用java實(shí)現(xiàn)獲取客戶端真實(shí)IP的方法,主要用于獲取使用了代理訪問的來訪者的IP,有需要的小伙伴可以參考下。2016-03-03