2009-09-04 122 views
32

數據訪問對象(DAO)是一種常見的設計模式,並由Sun推薦。但Java DAO最早的例子直接與關係數據庫交互 - 實質上,它們正在進行對象關係映射(ORM)。現在,我看到了像JDO和Hibernate這樣成熟的ORM框架之上的DAO,我想知道這是否是一個好主意。爲什麼將DAO層放在持久層(如JDO或Hibernate)上

我正在開發一個使用JDO作爲持久層的Web服務,並且正在考慮是否引入DAO。

public class Book { 
    // Book description in various languages, indexed by ISO language codes 
    private Map<String,BookDescription> descriptions; 
} 

JDO是足夠聰明的這個映射到「書」和「BOOKDESCRIPTIONS」表之間的外鍵約束:用含有地圖的其他對象的特定類打交道時,我可以預見到的問題。它透明地加載BookDescription對象(使用延遲加載,我相信),並在Book對象持久時持久化它們。如果我要引入一個「數據訪問層」並編寫像BookDao這樣的類,並將所有JDO代碼封裝在其中,那麼這個JDO的透明加載的子對象是否會繞過數據訪問層呢?爲了保持一致性,不應該通過一些BookDescriptionDao對象(或BookDao.loadDescription方法)加載並保存所有的BookDescription對象嗎?然而,以這種方式進行重構會使模型操作變得複雜。

所以我的問題是,在業務層直接調用JDO(或Hibernate,或任何你喜歡的ORM)有什麼問題?它的語法已經非常簡潔,並且與數據存儲無關。將數據封裝在數據訪問對象中的優點是什麼?

+2

謝謝你的答案爲止。我可以看到,在某些情況下,DAO模式可以解決*即時*需求,例如,當您需要專門的代碼來進行對象檢索,錯誤處理等時。但在其他情況下,這更像是理論上的爭論(一個人的「可維護性「是另一個人的」過早抽象「),沒有明確的答案。 – 2009-09-05 14:11:12

+0

爲了給這個問題提供一些背景知識,我對DAO的興趣最初是作爲解決即時問題的手段,即將依賴關係注入到JDO加載的對象中。但是我從那以後發現了我認爲更好的解決方案:JDO的addInstanceLifecycleListener()方法。 – 2009-09-05 14:20:17

+3

幾個月過去了...最後,我最終在JDO之上引入了一個數據訪問層,以便封裝安全方面(限制當前用戶可見或可編輯的實體)。 – 2010-05-19 08:35:52

回答

9

這取決於你的圖層的目標是什麼。你把一個抽象概念提供給另一組語義。通常還有其他層面來簡化諸如未來維護的發展等。但他們可以有其他用途。

例如,ORM代碼上的DAO(或持久性處理)層提供專門的恢復和錯誤處理功能,您不希望污染業務邏輯。

12

您提出一些觀點。但我仍然使用了DAO層,這裏的原因:

  1. 數據庫訪問是調用遠程系統。在所有這些情況下(還有web服務,ajax等),交互的粒度都需要足夠大。許多微小的電話會導致性能下降這種性能的必要性通常需要系統或層的不同視圖(這裏是Dao層)。

  2. 有時候,你的持久化操作只是加載/保存/刪除一個對象。一個獨特的Dao(或一個超類;考慮泛型)可以對此負責,所以您不必一次又一次地編寫這些方法。
    但是,您經常也有特定需求,例如運行ORM未自動創建的特定請求。在那裏,你用特定的Dao方法編寫你的特定需求(通常可以重複使用)。
    在同一層有常規和特定的需求允許重用(例如,攔截可以確保數據庫連接在需要時打開/提交)。

+0

關於第1點:DAO不是必需的,根本不需要。關於第2點:這也不需要DAO;我已經編寫了數百個特定的查詢,而不使用DAO類,並且查詢方法是可重用的。 – 2010-09-24 00:37:52

6

當使用像JDO或JPA這樣的ORM工具時,DAO是反模式。在這種情況下,創建一個「數據訪問層」是完全沒有必要的,只會增加代碼和代碼的複雜性,使開發和維護變得更加困難。

基於我以前的經驗,我建議使用簡單的靜態外觀,如Persistence,爲持久相關操作提供易於使用的高級API。

然後,您可以使用靜態導入來輕鬆訪問那些有用的方法。例如,您可以使用以下代碼:


    List<Book> cheapBooks = 
     find("select b from Book where b.price < ?", lowPriceForBooks); 
    ... 
    Book b = new Book(...); 
    persist(b); 
    ... 
    Book existingBook = load(Book.class, bookId); 
    remove(existingBook); 
    ... 

上面的代碼儘可能簡單和容易,並且可以很容易地進行單元測試。

+4

我很高興看到您將DAO稱爲反模式!但是......不是你的靜態持久性外觀在概念上與DAO很相似嗎?我沒有看到將單行JDO方法抽象爲單行靜態方法的好處,而且抽象是「漏」的,因爲它需要使用底層ORM的查詢語言。 – 2009-09-05 00:37:36

+1

它不同,因爲在概念上,Facade是一個更大,更復雜的API的簡化前端; Hibernate,JPA和JDO API就是這種情況。 這些方法並不是真正的單線。他們還必須從ThreadLocal(在Web應用程序中)打開/獲取適當的工作單元對象(Hibernate Session,JPA EntityManager);可能會有一些異常處理代碼;等等。 我不介意查詢語言被暴露,因爲真正的目標是簡化客戶端代碼,不允許可移植性。但我建議避免HQL,現在使用標準的JPA QL(或JDO QL)。 – 2009-09-05 12:15:01

+0

另請注意,DAO的目標是封裝「數據訪問」代碼。這對於JDBC來說是有意義的,但對於自身封裝真實數據訪問代碼的高級別ORM API則不是。這就是爲什麼我說DAO在使用JDO或JPA時是反模式的原因。 – 2009-09-05 12:18:58

0

它實際上比所有這些答案都要簡單得多。這些模式都是關於圖層的。你不想循環引用你的圖層,只能知道上面的東西。你希望你的UICode能夠引用任何和所有的服務,你的服務代碼能夠引用任何和所有的DAO。

  1. DAO
  2. 服務
  3. UICODE

與從頂部到底部傳遞的的POJO。

+0

但是,這與使用DAO有什麼關係呢?分層和DAO是獨立的概念,儘管DAO通常放在專用層中。 – 2009-09-04 12:54:46

5

一個字:交易

就拿情況下,我必須在一個事務中執行兩個數據更新操作。這些行動一起形成一個合理的工作單位。我的業務邏輯想要根據這個工作單位來表達自己,並且它不想用事務邊界來打擾自己。

所以我寫了一個DAO。使用Spring事務藉此僞代碼和休眠:

編輯刪除HQL這是得罪@Roger這麼多,但是這是不相關的點

@Transactional 
public void doUnitOfWork() { 
    // some persistence operation here 
    // some other persistence operation here 
} 

我的商業邏輯調用doUnitOfWork() ,它開始一個事務,執行兩個持久化操作,然後提交。它既不知道也不關心交易,或者執行什麼操作。此外,如果DAO使用doUnitOfWork()方法實現接口,則業務邏輯可以對接口進行編碼,使得單元測試更容易。

通常,我總是將我的數據訪問操作包裝在DAO中,並在其周圍敲擊一個接口。

+1

DAO不應該有與交易相關的代碼,除非常見交易分界規則不適用的特殊情況。 (當然,業務邏輯也不應該包含這樣的代碼。) – 2009-09-04 12:58:52

+0

我深表同意。 – skaffman 2009-09-04 14:14:49

+0

像「getHibernateTemplate()。執行代碼(」一些HQL在這裏「);」是可怕的。它是冗長的,並且公開了使用Hibernate的事實(這應該只是一個實現細節,現在我們有JPA)。 僅僅爲了測試而創建額外的接口是一種過時的做法。我可以在沒有它們的情況下單元測試任何類型的Java代碼,並使用簡短而優雅的JUnit/TestNG測試。 – 2009-09-04 16:31:51

3

我相信大多數DAO是由人們爲了某種(歷史)原因而添加的。你是對的,因爲它們是在ORM日之前執行CRUD操作所需的SQL粘合劑的便捷封裝。如今,透明的堅持,現在他們的角色基本上是多餘的。

什麼現在是合適的庫和服務的概念:

庫: 存儲的ORM特定代碼實現查詢方法的集合類(如Hibernate或者JDO)

通常你可以創建一個抽象基類倉庫,然後提供一個特定於ORM的實現,您可以在其中實現特定於您的ORM的代碼中的所有查詢方法。這種方法的好處是您可以創建一個MockRepository實現來幫助測試您的應用程序而不使用數據庫。

服務: 一個類,它存儲可以編排對象模型(通常是ORM獨立代碼)的不重要更改/添加的方法集合。

這有助於保持您的應用程序在很大程度上與ORM無關 - 將應用程序移植到另一個ORM實際上只涉及實施新的ORM特定存儲庫類。

+1

感謝您提出這個問題。表面上很難看出Repository模式與DAO實際上有何不同,但顯然在目的上存在一些差異(參見例如http://warren.mayocchi.com/2006/07/27/repository-or-dao/ ) – 2011-01-31 07:54:33

3

我想對於ORM管理的數據層,模式「每個實體的DAO類」絕對是多餘的。相反,DAO層應該由一組適用於任意實體類的一攬子CRUD方法集以及對數據執行更復雜操作的大量方法組成。如果功能足夠大,那麼應該根據域標準將DAO層分成多個類,這使得該方法更類似於面向服務的體系結構。

+1

我同意 - 除非在3056年挖掘我們文明遺蹟的古生物學家,否則絕不能再次看到「每個實體的DAO類別」。 另一個類似的概念是'DTO'(數據傳輸對象)。由於大多數ORM提供了連接/分離,因此沒有理由不能使用分離的實體對象進行數據傳輸。沒有必要爲每個實體類編寫一個額外的類來「傳輸它」(例如,對象編組,從JSON等序列化到JSON等等)。有些人認爲DTO可以讓你遠離模型變化,但大多數模型變化都需要DTO來更新! – Volksman 2014-04-05 21:28:43

10

隨着時間的推移,DAO失去了意義。

在J2EE日益流行的模式中,DAO是一個類,您可以同時滿足多個數據源 - 一個供應商的數據庫,另一個數據庫的數據庫,一個文件 - 並提供一個地方包裝查詢以進行數據通信。

有很多重用的空間,所以一個特定實體的DAO對象可能會擴展一個抽象DAO,它包含可重用的東西,DAO本身實現了一個DAO接口。

後J2EE/EJB,DataMapper和DataSource模式(或簡單的系統,ActiveRecord)變得流行以執行相同的角色。然而,DAO成爲涉及持久性的任何對象的流行詞。

今天,'DAO'這個詞已經可悲地成爲「一個使我能夠與我的數據庫進行交流的類」的同義詞。

使用ORM/JPA,真正的J2EE時代DAO的大部分基本原理都是開箱即用的。

對於後一種DataSource模式,JPA的EntityManager類似於DataSource,但通常通過PersistenceUnit XML定義提供,並通過IoC實例化。

曾經住在DAO或Mapper中的CRUD方法現在只能使用Repository模式提供一次。 AbstractDAO不需要 - ORM產品足夠聰明,可以接受Object()並知道它在哪裏持久化。

+2

+1感謝您編制這一系列替代模式(DAO,DataSource,ActiveRecord,Repository)。讓我想知道下一件大事將會是什麼...... :) – 2012-04-09 22:02:29

+0

如果我能正確地適應它,你的想法是DAO與現代ORM產品在DAO模式的定義方面是錯誤的。除此之外,您建議將Repository模式與ORM工具結合起來,這對於查找操作來說聽起來夠公平。但是,如果需要更新與Repository模式無關的操作,則必須有一個額外的對象,如抽象DAO(或任何你稱之爲複雜的外觀等) – 2014-04-02 06:53:01

2

所有這些介紹層的目的是使維護簡單易行。

  1. 數據訪問層
  2. 業務層
  3. 表示層

的第一層(數據訪問層)的目的是處理與數據庫的邏輯和防止業務層從知道任何數據庫細節。
數據訪問層使用POJO或EJB(DAO)來實現IoC,而POJOEJB使用Hibernate或ORM映射來實際處理數據庫層。
因此,如果你想要你的業務邏輯不應該關心哪些數據庫正在被使用,訪問和更新,你希望DAO處理這個問題
DAO可以支持更改不同表的邏輯以支持通過進行一些hibernate調用來進行操作。
實質上,您正在實現數據訪問層中的分層方法,即在DAO和Hibernate的兩層中再次分解其功能。

1

如果您使用ORM:享受其透明持久性支持!不要使用DAO來包裝ORM API。正如這裏所說的那樣,DAO在ORM之前。 ORM引入了OODBMS的概念,如透明持久性和可達性持久性。你必須利用這一點,因爲它會讓你的生活更輕鬆,代碼更漂亮。 假設您是建模部門和員工......一個用例可能是創建一個新部門,創建一個新員工並將該員工添加到部門中......您會做什麼?

//start persistence context 
... 
Department dept1 = new Department("department1"); 
dept1.addEmployee(new Employee("José", 10503f)); 

em.persist(dept1); 
... 
//close persistence context 

部門,員工和他們的關係現在持續存在。

假設您現在必須將現有員工添加到現有部門中,您會做什麼?很簡單:

//start persistence context 
... 
Department aDepart = hibernateSession.load(Department.class, dId); 
Employee anEmployee = hibernateSession.load(Employee.class, eId); 

aDepart.addEmployee(anEmployee);  
... 
//close persistence context 

非常簡單,這要歸功於Hibernate(像其他ORM)實現的透明持久性和可達性持久性。根本沒有DAO。

只需對您的域模型進行編碼,並像您在內存中一樣思考。通過良好的映射策略,ORM將透明地堅持你的記憶。

更多的例子在這裏: http://www.copypasteisforword.com/notes/hibernate-transparent-persistence http://www.copypasteisforword.com/notes/hibernate-transparent-persistence-ii