不要重複編寫DAO

來源:互聯網
上載者:User

由於 Java 5 泛型的採用,有關泛型型別安全 Data Access Object (DAO) 實現的想法變得切實可行。在本文中,系統架構師 Per Mellqvist 展示了基於 Hibernate 的泛型 DAO 實作類別。然後展示如何使用 Spring AOP introductions 將型別安全介面添加到類中以便於查詢執行。

對於大多數開發人員,為系統中的每個 DAO 編寫幾乎相同的代碼到目前為止已經成為一種習慣。雖然所有人都將這種重複標識為 “代碼味道”,但我們大多數都已經學會忍受它。其實有解決方案。可以使用許多 ORM 工具來避免代碼重複。例如,使用 Hibernate,您可以簡單地為所有的持久域對象直接使用會話操作。這種方法的缺點是損失了型別安全。

為什麼您要為資料存取碼提供型別安全介面?我會爭辯說,當它與現代 IDE 工具一起使用時,會減少編程錯誤並提高生產率。首先,型別安全介面清楚地指明哪些域對象具有可用的持久儲存。其次,它消除了易出錯的類型強制轉換的需要(這是一個在查詢操作中比在 CRUD 中更常見的問題)。最後,它有效利用了今天大多數 IDE 具備的自動完成特性。使用自動完成是記住什麼查詢可用於特定域類的快捷方法。

在本文中,我將為您展示如何避免再三地重複 DAO 代碼,而仍保留型別安全介面的優點。事實上,您需要為每個新 DAO 編寫的只是 Hibernate 對應檔、無格式舊 Java 介面以及 Spring 設定檔中的 10 行。

DAO 實現

DAO 模式對任何企業 Java 開發人員來說都應該很熟悉。但是模式的實現各不相同,所以我們來澄清一下本文提供的 DAO 實現背後的假設:

  • 系統中的所有資料庫訪問都通過 DAO 進行以實現封裝。
  • 每個 DAO 執行個體負責一個主要域對象或實體。如果域對象具有獨立生命週期,它應具有自己的 DAO。
  • DAO 負責域對象的建立、讀取(按主鍵)、更新和刪除(creations, reads, updates, and deletions,CRUD)。
  • DAO 可允許基於除主鍵之外的標準進行查詢。我將之稱為尋找器方法尋找器。尋找器的傳回值通常是 DAO 負責的域對象集合。
  • DAO 不負責處理事務、會話或串連。這些不由 DAO 處理是為了實現靈活性。



回頁首

泛型 DAO 介面

泛型 DAO 的基礎是其 CRUD 操作。下面的介面定義泛型 DAO 的方法:

清單 1. 泛型 DAO 介面

public interface GenericDao <T, PK extends Serializable> {    /** Persist the newInstance object into database */    PK create(T newInstance);    /** Retrieve an object that was previously persisted to the database using     *   the indicated id as primary key     */    T read(PK id);    /** Save changes made to a persistent object.  */    void update(T transientObject);    /** Remove an object from persistent storage in the database */    void delete(T persistentObject);}

實現介面

用 Hibernate 實現清單 1 中的介面十分簡單,如清單 2 所示。它只需調用底層 Hibernate 方法和添加強制類型轉換。Spring 負責會話和交易管理。(當然,我假設這些函數已做了適當的設定,但該主題在 Hibernate 和 Springt 手冊中有詳細介紹。)

清單 2. 第一個泛型 DAO 實現

public class GenericDaoHibernateImpl <T, PK extends Serializable>    implements GenericDao<T, PK>, FinderExecutor {    private Class<T> type;    public GenericDaoHibernateImpl(Class<T> type) {        this.type = type;    }    public PK create(T o) {        return (PK) getSession().save(o);    }    public T read(PK id) {        return (T) getSession().get(type, id);    }    public void update(T o) {        getSession().update(o);    }    public void delete(T o) {        getSession().delete(o);    }    // Not showing implementations of getSession() and setSessionFactory()            }

Spring 配置

最後,在 Spring 配置中,我建立了 GenericDaoHibernateImpl 的一個執行個體。必須告訴 GenericDaoHibernateImpl 的建構函式 DAO 執行個體將負責哪個域類。只有這樣,Hibernate 才能在運行時知道由 DAO 管理的物件類型。在清單 3 中,我將域類 Person 從應用程式範例傳遞給建構函式,並將先前配置的 Hibernate 會話工廠設定為已執行個體化的 DAO 的參數:

清單 3. 配置 DAO

<bean id="personDao" class="genericdao.impl.GenericDaoHibernateImpl">        <constructor-arg>            <value>genericdaotest.domain.Person</value>        </constructor-arg>        <property name="sessionFactory">            <ref bean="sessionFactory"/>        </property></bean>        


回頁首

可用的泛型 DAO

我還沒有完成,但我所完成的確實已經可以使用了。在清單 4 中,可以看到原封不動使用該泛型 DAO 的樣本:

清單 4. 使用 DAO

public void someMethodCreatingAPerson() {    ...    GenericDao dao = (GenericDao)     beanFactory.getBean("personDao"); // This should normally be injected    Person p = new Person("Per", 90);    dao.create(p);}        

現在,我有一個能夠進行型別安全 CRUD 操作的泛型 DAO。讓子類 GenericDaoHibernateImpl 為每個域對象添加查詢能力將非常合理。因為本文的目的在於展示如何不為每個查詢編寫顯式的 Java 代碼來實現查詢,但是,我將使用其他兩個工具將查詢引入 DAO,也就是 Spring AOP 和 Hibernate 命名的查詢。



回頁首

Spring AOP introductions

可以使用 Spring AOP 中的 introductions 將功能添加到現有對象,方法是將功能封裝在代理中,定義應實現的介面,並將所有先前未支援的方法指派到單個處理常式。在我的 DAO 實現中,我使用 introductions 將許多尋找器方法添加到現有泛型 DAO 類中。因為尋找器方法是特定於每個域對象的,因此將其應用於泛型 DAO 的類型化介面。

Spring 配置如清單 5 所示:

清單 5. FinderIntroductionAdvisor 的 Spring 配置

<bean id="finderIntroductionAdvisor" class="genericdao.impl.FinderIntroductionAdvisor"/><bean id="abstractDaoTarget"        class="genericdao.impl.GenericDaoHibernateImpl" abstract="true">        <property name="sessionFactory">            <ref bean="sessionFactory"/>        </property></bean><bean id="abstractDao"        class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true">        <property name="interceptorNames">            <list>                <value>finderIntroductionAdvisor</value>            </list>        </property></bean>        

在清單 5 的設定檔中,我定義了三個 Spring bean。第一個 bean 是 FinderIntroductionAdvisor,它處理引入到 DAO 的所有方法,這些方法在 GenericDaoHibernateImpl 類中不可用。我稍後將詳細介紹 Advisor bean。

第二個 bean 是 “抽象的”。在 Spring 中,這意味著該 bean 可在其他 bean 定義中被重用,但不被執行個體化。除了抽象特性之外,該 bean 定義只指出我想要 GenericDaoHibernateImpl 的執行個體以及該執行個體需要對 SessionFactory 的引用。注意,GenericDaoHibernateImpl 類僅定義一個建構函式,該建構函式接受域類作為其參數。因為該 bean 定義是抽象的,所以我將來可以無數次地重用該定義,並將建構函式參數設定為合適的域類。

最後,第三個也是最有趣的 bean 將 GenericDaoHibernateImpl 的 vanilla 執行個體封裝在代理中,賦予其執行尋找器方法的能力。該 bean 定義也是抽象的,不指定希望引入到 vanilla DAO 的介面。該介面對於每個具體的執行個體是不同的。注意,清單 5 顯示的整個配置僅定義一次。



回頁首

擴充 GenericDAO

當然,每個 DAO 的介面都基於 GenericDao 介面。我只需使該介面適應特定的域類並擴充該介面以包括尋找器方法。在清單 6 中,可以看到為特定目的擴充的 GenericDao 介面樣本:

清單 6. PersonDao 介面

public interface PersonDao extends GenericDao<Person, Long> {    List<Person> findByName(String name);}

很明顯,清單 6 中定義的方法旨在按名稱尋找 Person。必需的 Java 實現代碼全部是泛型代碼,在添加更多 DAO 時不需要任何更新。

配置 PersonDao

因為 Spring 配置依賴於先前定義的 “抽象” bean,因此它變得相當簡潔。我需要指出 DAO 負責哪個域類,並且需要告訴 Springs 該 DAO 應實現哪個介面(一些方法是直接使用,一些方法則是通過使用 introductions 來使用)。清單 7 展示了 PersonDAO 的 Spring 設定檔:

清單 7. PersonDao 的 Spring 配置

<bean id="personDao" parent="abstractDao">    <property name="proxyInterfaces">        <value>genericdaotest.dao.PersonDao</value>    </property>    <property name="target">        <bean parent="abstractDaoTarget">            <constructor-arg>                <value>genericdaotest.domain.Person</value>            </constructor-arg>        </bean>    </property></bean>        

在清單 8 中,可以看到使用了這個更新後的 DAO 版本:

清單 8. 使用型別安全介面

public void someMethodCreatingAPerson() {    ...    PersonDao dao = (PersonDao)     beanFactory.getBean("personDao"); // This should normally be injected    Person p = new Person("Per", 90);    dao.create(p);    List<Person> result = dao.findByName("Per"); // Runtime exception}        

雖然清單 8 中的代碼是使用型別安全 PersonDao 介面的正確方法,但 DAO 的實現並不完整。調用 findByName() 會導致運行時異常。問題在於我還沒有實現調用 findByName() 所必需的查詢。剩下要做的就是指定查詢。為更正該問題,我使用了 Hibernate 命名查詢。



回頁首

Hibernate 命名查詢

使用 Hibernate,可以在 Hibernate 對應檔 (hbm.xml) 中定義 HQL 查詢並為其命名。稍後可以通過簡單地引用給定名稱來在 Java 代碼中使用該查詢。該方法的優點之一是能夠在部署時最佳化查詢,而無需更改代碼。您一會將會看到,另一個優點是無需編寫任何新 Java 實現代碼,就可以實現 “完整的” DAO。清單 9 是帶有命名查詢的對應檔的樣本:

清單 9. 帶有命名查詢的對應檔

 <hibernate-mapping package="genericdaotest.domain">     <class name="Person">         <id name="id">             <generator class="native"/>         </id>         <property name="name" />         <property name="weight" />     </class>     <query name="Person.findByName">         <![CDATA[select p from Person p where p.name = ? ]]>     </query> </hibernate-mapping>        

清單 9 定義了域類 Person 的 Hibernate 映射,該域類具有兩個屬性:nameweightPerson 是具有上述屬性的簡單 POJO。該檔案還包含一個在資料庫中尋找 Person 所有執行個體的查詢,其中 “name” 等於提供的參數。Hibernate 不為命名查詢提供任何真正的名稱空間功能。出於討論目的,我為所有查詢名稱都加了域類的短(非限定)名稱作為首碼。在現實世界中,使用包括包名稱的完全類名可能是更好的主意。



回頁首

逐步概述

您已經看到了為任何域對象建立和配置新 DAO 所必需的全部步驟。三個簡單的步驟是:

  1. 定義一個介面,它擴充 GenericDao 並包含所需的任何尋找器方法。
  2. 將每個尋找器的命名查詢添加到域對象的 hbm.xml 對應檔。
  3. 為 DAO 添加 10 行 Spring 設定檔。

查看執行尋找器方法的代碼(只編寫了一次!)來結束我的討論。



回頁首

可重用的 DAO 類

使用的 Spring advisor 和 interceptor 很簡單,事實上它們的工作是反向參考 GenericDaoHibernateImplClass。方法名以 “find” 打頭的所有調用都傳遞給 DAO 和單個方法 executeFinder()

清單 10. FinderIntroductionAdvisor 的實現

public class FinderIntroductionAdvisor extends DefaultIntroductionAdvisor {    public FinderIntroductionAdvisor() {        super(new FinderIntroductionInterceptor());    }}public class FinderIntroductionInterceptor implements IntroductionInterceptor {    public Object invoke(MethodInvocation methodInvocation) throws Throwable {        FinderExecutor genericDao = (FinderExecutor) methodInvocation.getThis();        String methodName = methodInvocation.getMethod().getName();        if (methodName.startsWith("find")) {            Object[] arguments = methodInvocation.getArguments();            return genericDao.executeFinder(methodInvocation.getMethod(), arguments);        } else {            return methodInvocation.proceed();        }    }    public boolean implementsInterface(Class intf) {        return intf.isInterface() && FinderExecutor.class.isAssignableFrom(intf);    }}

executeFinder() 方法

清單 10 的實現中惟一缺少的是 executeFinder() 實現。該代碼查看調用的類和方法的名稱,並使用配置上的約定將其與 Hibernate 查詢的名稱相匹配。還可以使用 FinderNamingStrategy 來支援其他命名查詢的方法。預設實現尋找叫做 “ClassName.methodName” 的查詢,其中 ClassName 是不帶包的簡短名稱。清單 11 完成了泛型型別安全 DAO 實現:

清單 11. executeFinder() 的實現

public List<T> executeFinder(Method method, final Object[] queryArgs) {     final String queryName = queryNameFromMethod(method);     final Query namedQuery = getSession().getNamedQuery(queryName);     String[] namedParameters = namedQuery.getNamedParameters();     for(int i = 0; i < queryArgs.length; i++) {             Object arg = queryArgs[i];             Type argType =  namedQuery.setParameter(i, arg);      }      return (List<T>) namedQuery.list(); } public String queryNameFromMethod(Method finderMethod) {     return type.getSimpleName() + "." + finderMethod.getName(); }


回頁首

結束語

在 Java 5 之前,該語言不支援編寫既型別安全 泛型的代碼,您必須只能選擇其中之一。在本文中,您已經看到一個結合使用 Java 5 泛型與 Spring 和 Hibernate(以及 AOP)等工具來提高生產率的樣本。泛型型別安全 DAO 類相當容易編寫 —— 您只需要單個介面、一些命名查詢和為 Spring 配置添加的 10 行代碼 —— 而且可以極大地減少錯誤並節省時間。

幾乎本文的所有代碼都是可重用的。儘管您的 DAO 類可能包含此處沒有實現的查詢和操作類型(比如,批操作),但使用我所展示的技術,您至少應該能夠實現其中的一部分。參閱 參考資料 瞭解其他泛型型別安全 DAO 類實現。

致謝

自 Java 語言中出現泛型以來,單個泛型型別安全 DAO 的概念已經成為主題。我曾在 JavaOne 2004 中與 Don Smith 簡要討論了泛型 DAO 的靈活性。本文使用的 DAO 實作類別旨在作為樣本實現,實際上還存在其他實現。例如,Christian Bauer 已經發布了帶有 CRUD 操作和標準搜尋的實現,Eric Burke 也在該領域做出了工作。我確信將會有更多的實現出現。我要額外感謝 Christian,他目睹了我編寫泛型型別安全 DAO 的第一次嘗試並提出改進建議。最後,我要感謝 Ramnivas Laddad 的無價協助,他審閱了本文。

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.