- 相關(guān)推薦
J2EE應用下基于A(yíng)OP的抓取策略
如何通過(guò)最精簡(jiǎn)的SQL查詢(xún)獲取所需的數據。很多時(shí)候這可不是輕而易舉的事情。默認情況下,O/R Mapping工具會(huì )按需加載數據,除非你改變了其默認設置。延遲加載行為保證了依賴(lài)的數據只有在真正請求時(shí)才會(huì )被加載進(jìn)來(lái),這樣就可以避免創(chuàng )建無(wú)謂的對象。有時(shí)我們的業(yè)務(wù)并不會(huì )使用到依賴(lài)的那些組件,這時(shí)延遲加載就派上用場(chǎng)了,同時(shí)也無(wú)需加載那些用不上的組件了。
典型情況下,我們的業(yè)務(wù)很清楚需要哪些數據。但由于使用了延遲加載,在執行大量Select查詢(xún)時(shí)數據庫的性能會(huì )降低,因為業(yè)務(wù)所需的數據并不是一下子獲得的。這樣,對于那些需要支持大量請求的應用來(lái)說(shuō)可能會(huì )產(chǎn)生瓶頸(可伸縮性問(wèn)題)。來(lái)看個(gè)例子吧,假設某個(gè)業(yè)務(wù)流程想要得到一個(gè)Person及其Address信息。我們將Address組件配置成延遲加載,這樣要想得到所需的數據就需要更多的SQL查詢(xún),也就是說(shuō)首先查詢(xún)Person,然后再查詢(xún)Address。這增加了數據庫與應用之間的通信成本。解決辦法就是在一個(gè)單獨的查詢(xún)中將 Person和Address都得到,因為我們知道這兩個(gè)組件都是業(yè)務(wù)流程所需的。如果在DAO/Repository及底層Service開(kāi)發(fā)特定于業(yè)務(wù)的Fetching-API,對于那些擁有不同數據集的相同領(lǐng)域對象來(lái)說(shuō),我們就得編寫(xiě)不同的API進(jìn)行抓取并組裝了。這么做會(huì )使Repository及底層Service過(guò)于膨脹,最終變成維護的夢(mèng)魘。延遲抓取的另一個(gè)問(wèn)題就是在獲取到請求的數據前要一直打開(kāi)數據庫連接,否則應用就會(huì )拋出一個(gè)延遲加載異常。說(shuō)明:如果在查詢(xún)中使用預先抓取來(lái)獲取二級緩存中的數據時(shí),我們將無(wú)法解決上面提出的問(wèn)題。對于Hibernate來(lái)說(shuō),如果我們使用預先抓取來(lái)獲取二級緩存中的數據,那么它將從數據庫而不是緩存中去獲取數據,哪怕是二級緩存中已經(jīng)存在該數據。這就說(shuō)明Hibernate也沒(méi)有解決這個(gè)問(wèn)題,從而表明我們不應該在查詢(xún)中通過(guò)預先抓取來(lái)獲得二級緩存中的對象。對于那些可以讓我們調節查詢(xún)以獲取緩存對象的O/R Mapping工具來(lái)說(shuō),如果緩存中有對象就會(huì )從緩存中獲取,否則采取預先抓取的方式。這就解決了上面提到的事務(wù)/DB連接問(wèn)題,因為在查詢(xún)的執行過(guò)程中會(huì )同時(shí)獲取緩存中的數據而不是按需讀取(也就是延遲加載)。通過(guò)下面的示例代碼來(lái)了解一下延遲加載所面對的問(wèn)題及解決辦法?紤]如下場(chǎng)景:某領(lǐng)域中有3個(gè)實(shí)體,分別是Employee、Department及Dependent。這三個(gè)實(shí)體之間的關(guān)系如下:Employee有0或多個(gè)dependents。
Department有0或多個(gè)employees。
Employee屬于0或1個(gè)department。
我們要執行三個(gè)操作:獲取employee的詳細信息。
獲取employee及其dependent的詳細信息。
獲取employee及其department的詳細信息。
以上三個(gè)操作需要獲取并呈現不同的數據。使用延遲加載有如下弊端:如果對實(shí)體employee所關(guān)聯(lián)的dependent和department這兩個(gè)實(shí)體使用延遲加載,那么在操作2和3中就會(huì )生成更多的SQL查詢(xún)語(yǔ)句。
在多個(gè)查詢(xún)語(yǔ)句的執行過(guò)程中需要保持數據庫連接,否則會(huì )拋出一個(gè)延遲加載異常,這將導致數據出現問(wèn)題。
但另一方面,使用預先抓取也存在如下弊端:對employee所對應的dependents和department采取預先抓取會(huì )產(chǎn)生不必要的數據。
無(wú)法在特定的場(chǎng)景下對查詢(xún)進(jìn)行調優(yōu)。
在Repository/DAO或底層服務(wù)中使用特定于操作的API可以解決上述問(wèn)題,但卻會(huì )導致如下問(wèn)題:代碼膨脹——不管是Service還是Repository/DAO類(lèi)都無(wú)法幸免。
維護的夢(mèng)魘——不管是Service還是Repository/DAO層,只要有新的操作都需要增加新的API。
代碼重復——有時(shí)底層服務(wù)需要在獲取的實(shí)體上增加某些業(yè)務(wù)邏輯,與之類(lèi)似,還要在數據返回前檢查DAO/Repository層的查詢(xún)響應以驗證數據可用性。
為了解決上面這些問(wèn)題,Repository/DAO層需要根據不同的業(yè)務(wù)情況執行不同的查詢(xún)來(lái)獲取實(shí)體。就像Aspect類(lèi)所定義的那樣,我們可以根據特定的操作使用不同的抓取機制來(lái)覆蓋Repository/DAO類(lèi)所定義的默認抓取模式。所有的抓取模式類(lèi)都實(shí)現了相同的接口。
Repository類(lèi)使用了上述的抓取模式來(lái)執行查詢(xún),如下代碼所示:public Employee findEmployeeById(int employeeId) {
List employee = hibernateTemplate.find(fetchingStrategy.queryEmployeeById(),
new Integer(employeeId));
if(employee.size() == 0)
return null;
return (Employee)employee.get(0);
}
Repository類(lèi)中的employee的抓取策略需要根據實(shí)際情況進(jìn)行調整。我們決定將Repository層的抓取策略調整到 Repository和Service層外,放在一個(gè)Aspect類(lèi)中,這樣當需要增加新的業(yè)務(wù)邏輯時(shí)只需修改Aspect類(lèi)并增加一個(gè)針對于 Repository的抓取策略實(shí)現即可。這里我們使用了面向方面的編程(Aspect Oriented Programming)以根據業(yè)務(wù)的不同使用不同的抓取策略。什么是面向方面的編程?面向方面的編程(AOP)可以通過(guò)模塊化的形式實(shí)現實(shí)際應用中的橫切關(guān)注點(diǎn),如日志、追蹤、動(dòng)態(tài)分析、錯誤處理、服務(wù)水平協(xié)議、策略增強、池化、緩存、并發(fā)控制、安全、事務(wù)管理以及業(yè)務(wù)規則等等。對這些關(guān)注點(diǎn)的傳統實(shí)現方式需要我們將這些實(shí)現融合到模塊的核心關(guān)注點(diǎn)中。憑借AOP,我們可以在一個(gè)叫做方面(aspect)的獨立模塊中實(shí)現這些關(guān)注點(diǎn)。模塊化的結果就是設計簡(jiǎn)化、易于理解、質(zhì)量提升、開(kāi)發(fā)時(shí)間降低以及對系統需求變更的快速響應。接下來(lái)讀者朋友們可以參考 Ramnivas Laddad所著(zhù)的《AspectJ in Action》一書(shū)以詳細了解AspectJ的概念以及編程方式,還可以了解一下AspectJ的開(kāi)發(fā)工具。Aspect在抓取策略實(shí)現上扮演著(zhù)重要角色。抓取策略是個(gè)業(yè)務(wù)層的橫切關(guān)注點(diǎn),它會(huì )隨著(zhù)業(yè)務(wù)的變化而變化。Aspect對于特定的業(yè)務(wù)邏輯下使用何種抓取策略起到了至關(guān)重要的作用。這里我們將對抓取策略的管理放在了底層服務(wù)和Respository層之外。任何新的業(yè)務(wù)都可能需要不同的抓取策略,這樣我們就無(wú)需修改底層服務(wù)或是Respository層的API就能應用新的抓取策略了。FetchingStrategyAspect.aj/**
Identify the getEmployeeWithDepartmentDetails flow where you need to change the fetching
strategy at repository level
*/
pointcut empWithDepartmentDetail(): call(* EmployeeRepository.findEmployeeById(int))
&& cflow(execution(* EmployeeDetailsService.getEmployeeWithDepartmentDetails(int)));
/**
When you are at the specified poincut before continuing further update the fetchingStrategy in
EmployeeRepositoryImpl to EmployeeWithDepartmentFetchingStrategy
*/
before(EmployeeRepositoryImpl r): empWithDepartmentDetail() && target(r) {
r.fetchingStrategy = new EmployeeWithDepartmentFetchingStrategy();
}
/**
Identify the getEmployeeWithDependentDetails flow where you need to change the fetching
staratergy at repository level
*/
pointcut empWithDependentDetail(): call(* EmployeeRepository.findEmployeeById(int))
&& cflow(execution(* EmployeeDetailsService.getEmployeeWithDependentDetails(int)));
/**
When you are at the specified poincut before continuing further update the fetchingStrategy in
EmployeeRepositoryImpl to EmployeeWithDependentFetchingStrategy
*/
before(EmployeeRepositoryImpl r): empWithDependentDetail() && target(r) {
r.fetchingStrategy = new EmployeeWithDependentFetchingStrategy();
}
這樣,Repository到底要執行何種查詢(xún)就不是由Service和Repository層所決定了,而是由外面的Aspect決定,縱使增加了新的業(yè)務(wù)也無(wú)需修改底層服務(wù)和Repository層。決定執行何種查詢(xún)的邏輯就成為一個(gè)橫切關(guān)注點(diǎn)了,它被放在A(yíng)spect中。Aspect會(huì )根據業(yè)務(wù)規則的不同在Service層調用Repository層的API之前將抓取策略注入到Repository中。這樣我們就可以使用相同的Service和 Repository層API來(lái)滿(mǎn)足各種不同的業(yè)務(wù)規則了。來(lái)看個(gè)具體示例吧,該示例會(huì )同時(shí)抓取一個(gè)employee的Department和Dependent的詳細信息。我們需要對業(yè)務(wù)層進(jìn)行一些變更,增加一個(gè)方法:getEmployeeWithDepartmentAndDependentsDetails(int employeeId)。實(shí)現新的抓取策略類(lèi)EmployeeWithDepartmentAndDependentFetchingStaratergy,后者又實(shí)現了EmployeeFetchingStrategy并重寫(xiě)了queryEmployeeById方法,該方法會(huì )返回優(yōu)化后的查詢(xún),可以在一個(gè)SQL語(yǔ)句中獲取所需數據。由Aspect將上述的抓取策略注入到相關(guān)的業(yè)務(wù)中,如下所示:pointcut empWithDependentAndDepartmentDetail(): call(* EmployeeRepository.findEmployeeById(int))
&& cflow(execution(* EmployeeDetailsService.getEmployeeWithDepartmentAndDependentsDetails(int)));
before(EmployeeRepositoryImpl r): empWithDependentAndDepartmentDetail() && target(r) {
r.fetchingStrategy = new EmployeeWithDepartmentAndDependentFetchingStaratergy();
}
如你所見(jiàn),我們并沒(méi)有修改底層業(yè)務(wù)與Repository層而是使用Aspect和一個(gè)新的FetchingStrategy實(shí)現就完成了上述新增的業(yè)務(wù),F在我們來(lái)談?wù)勱P(guān)于二級緩存的查詢(xún)優(yōu)化問(wèn)題。在上面的示例代碼中,我們對department實(shí)體進(jìn)行一些修改并配置在二級緩存中。如果對 department實(shí)體采取預先抓取,那么對于同樣的department實(shí)例,縱使它位于二級緩存中,每次也都需要查詢(xún)數據庫。如果不在查詢(xún)中獲取 department實(shí)體,那么業(yè)務(wù)層就需要參與到事務(wù)當中,因為我們并沒(méi)有將department實(shí)體緩存起來(lái)而是通過(guò)延遲加載的方式得到它。這樣,事務(wù)聲明就從底層移到了業(yè)務(wù)層,雖然我們知道該業(yè)務(wù)需要哪些數據,但O/R Mapping工具卻沒(méi)有提供相應的機制來(lái)解決上面遇到的問(wèn)題,即預先抓取緩存中的數據。對于那些沒(méi)有緩存的數據來(lái)說(shuō)這種方式?jīng)]什么問(wèn)題,但對于緩存數據來(lái)說(shuō),這就依賴(lài)于O/R Mapping工具了,因為只有它才能解決緩存數據問(wèn)題。該示例附帶的源代碼詳細解釋了抓取策略。該zip文件含有一個(gè)工程示例,闡述了上面談到的所有場(chǎng)景。你可以使用任何IDE或是使用aspectj編譯器從命令行執行代碼。在執行前請確保jdbc.properties文件與你機器上的信息一致并創(chuàng )建示例應用所需的表。你可以使用Eclipse IDE以及AJDT插件運行代碼,請按照下面的步驟進(jìn)行:解壓縮下載好的代碼并將工程導入到Eclipse中。
配置Resources/dbscript目錄下的jdbc.properties文件中的數據庫信息。
完成上面的步驟后請執行resources\dbscript\tables.sql腳本,這將創(chuàng )建該示例應用所需的表。
以AspectJ/Java應用的方式運行Main.java文件來(lái)創(chuàng )建默認數據并測試上面的抓取策略實(shí)現。
【J2EE應用下基于A(yíng)OP的抓取策略】相關(guān)文章:
J2EE控制策略03-09
基于信息化下的品牌管理提升策略研究03-05
高性能J2EE應用的技巧03-22
J2EE應用的十個(gè)技巧03-26
構建高性能J2EE應用的技巧03-20
J2EE應用服務(wù)器03-29
廣告設計應用策略03-13
Java動(dòng)態(tài)代理實(shí)現AOP的方法03-16
J2EE應用服務(wù)器介紹03-20