csuct

           

          泛型dao 詳細剖析

          對于大多數(shù)開發(fā)人員,為系統(tǒng)中的每個 DAO 編寫幾乎相同的代碼到目前為止已經(jīng)成為一種習(xí)慣。雖然所有人都將這種重復(fù)標(biāo)識為 “代碼味道”,但我們大多數(shù)都已經(jīng)學(xué)會忍受它。其實有解決方案。可以使用許多 ORM 工具來避免代碼重復(fù)。例如,使用 Hibernate,您可以簡單地為所有的持久域?qū)ο笾苯邮褂脮挷僮鳌_@種方法的缺點是損失了類型安全。

          為什么您要為數(shù)據(jù)訪問代碼提供類型安全接口?我會爭辯說,當(dāng)它與現(xiàn)代 IDE 工具一起使用時,會減少編程錯誤并提高生產(chǎn)率。首先,類型安全接口清楚地指明哪些域?qū)ο缶哂锌捎玫某志么鎯ΑF浯危艘壮鲥e的類型強制轉(zhuǎn)換的需要(這是一個在查詢操作中比在 CRUD 中更常見的問題)。最后,它有效利用了今天大多數(shù) IDE 具備的自動完成特性。使用自動完成是記住什么查詢可用于特定域類的快捷方法。

          在本文中,我將為您展示如何避免再三地重復(fù) DAO 代碼,而仍保留類型安全接口的優(yōu)點。事實上,您需要為每個新 DAO 編寫的只是 Hibernate 映射文件、無格式舊 Java 接口以及 Spring 配置文件中的 10 行。

          DAO 實現(xiàn)

          DAO 模式對任何企業(yè) Java 開發(fā)人員來說都應(yīng)該很熟悉。但是模式的實現(xiàn)各不相同,所以我們來澄清一下本文提供的 DAO 實現(xiàn)背后的假設(shè):

          系統(tǒng)中的所有數(shù)據(jù)庫訪問都通過 DAO 進行以實現(xiàn)封裝。
          每個 DAO 實例負責(zé)一個主要域?qū)ο蠡驅(qū)嶓w。如果域?qū)ο缶哂歇毩⑸芷冢鼞?yīng)具有自己的 DAO。
          DAO 負責(zé)域?qū)ο蟮膭?chuàng)建、讀取(按主鍵)、更新和刪除(creations, reads, updates, and deletions,CRUD)。
          DAO 可允許基于除主鍵之外的標(biāo)準(zhǔn)進行查詢。我將之稱為查找器方法 或查找器。查找器的返回值通常是 DAO 負責(zé)的域?qū)ο蠹稀?
          DAO 不負責(zé)處理事務(wù)、會話或連接。這些不由 DAO 處理是為了實現(xiàn)靈活性。

           

          泛型 DAO 接口

          泛型 DAO 的基礎(chǔ)是其 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);
          }

           


          實現(xiàn)接口

          用 Hibernate 實現(xiàn)清單 1 中的接口十分簡單,如清單 2 所示。它只需調(diào)用底層 Hibernate 方法和添加強制類型轉(zhuǎn)換。Spring 負責(zé)會話和事務(wù)管理。(當(dāng)然,我假設(shè)這些函數(shù)已做了適當(dāng)?shù)脑O(shè)置,但該主題在 Hibernate 和 Springt 手冊中有詳細介紹。)


          清單 2. 第一個泛型 DAO 實現(xiàn)
          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 配置中,我創(chuàng)建了 GenericDaoHibernateImpl 的一個實例。必須告訴 GenericDaoHibernateImpl 的構(gòu)造函數(shù) DAO 實例將負責(zé)哪個域類。只有這樣,Hibernate 才能在運行時知道由 DAO 管理的對象類型。在清單 3 中,我將域類 Person 從示例應(yīng)用程序傳遞給構(gòu)造函數(shù),并將先前配置的 Hibernate 會話工廠設(shè)置為已實例化的 DAO 的參數(shù):


          清單 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

          我還沒有完成,但我所完成的確實已經(jīng)可以使用了。在清單 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);
          }
                  


          現(xiàn)在,我有一個能夠進行類型安全 CRUD 操作的泛型 DAO。讓子類 GenericDaoHibernateImpl 為每個域?qū)ο筇砑硬樵兡芰⒎浅:侠怼R驗楸疚牡哪康脑谟谡故救绾尾粸槊總€查詢編寫顯式的 Java 代碼來實現(xiàn)查詢,但是,我將使用其他兩個工具將查詢引入 DAO,也就是 Spring AOP 和 Hibernate 命名的查詢。

           


          Spring AOP introductions

          可以使用 Spring AOP 中的 introductions 將功能添加到現(xiàn)有對象,方法是將功能包裝在代理中,定義應(yīng)實現(xiàn)的接口,并將所有先前未支持的方法指派到單個處理程序。在我的 DAO 實現(xiàn)中,我使用 introductions 將許多查找器方法添加到現(xiàn)有泛型 DAO 類中。因為查找器方法是特定于每個域?qū)ο蟮模虼藢⑵鋺?yīng)用于泛型 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 類僅定義一個構(gòu)造函數(shù),該構(gòu)造函數(shù)接受域類作為其參數(shù)。因為該 bean 定義是抽象的,所以我將來可以無數(shù)次地重用該定義,并將構(gòu)造函數(shù)參數(shù)設(shè)置為合適的域類。

          最后,第三個也是最有趣的 bean 將 GenericDaoHibernateImpl 的 vanilla 實例包裝在代理中,賦予其執(zhí)行查找器方法的能力。該 bean 定義也是抽象的,不指定希望引入到 vanilla DAO 的接口。該接口對于每個具體的實例是不同的。注意,清單 5 顯示的整個配置僅定義一次。

           

          擴展 GenericDAO

          當(dāng)然,每個 DAO 的接口都基于 GenericDao 接口。我只需使該接口適應(yīng)特定的域類并擴展該接口以包括查找器方法。在清單 6 中,可以看到為特定目的擴展的 GenericDao 接口示例:


          清單 6. PersonDao 接口
          public interface PersonDao extends GenericDao<Person, Long> {
              List<Person> findByName(String name);
          }

           


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

          配置 PersonDao

          因為 Spring 配置依賴于先前定義的 “抽象” bean,因此它變得相當(dāng)簡潔。我需要指出 DAO 負責(zé)哪個域類,并且需要告訴 Springs 該 DAO 應(yīng)實現(xiàn)哪個接口(一些方法是直接使用,一些方法則是通過使用 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 的實現(xiàn)并不完整。調(diào)用 findByName() 會導(dǎo)致運行時異常。問題在于我還沒有實現(xiàn)調(diào)用 findByName() 所必需的查詢。剩下要做的就是指定查詢。為更正該問題,我使用了 Hibernate 命名查詢。

           

          Hibernate 命名查詢

          使用 Hibernate,可以在 Hibernate 映射文件 (hbm.xml) 中定義 HQL 查詢并為其命名。稍后可以通過簡單地引用給定名稱來在 Java 代碼中使用該查詢。該方法的優(yōu)點之一是能夠在部署時優(yōu)化查詢,而無需更改代碼。您一會將會看到,另一個優(yōu)點是無需編寫任何新 Java 實現(xiàn)代碼,就可以實現(xiàn) “完整的” 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 映射,該域類具有兩個屬性:name 和 weight。Person 是具有上述屬性的簡單 POJO。該文件還包含一個在數(shù)據(jù)庫中查找 Person 所有實例的查詢,其中 “name” 等于提供的參數(shù)。Hibernate 不為命名查詢提供任何真正的名稱空間功能。出于討論目的,我為所有查詢名稱都加了域類的短(非限定)名稱作為前綴。在現(xiàn)實世界中,使用包括包名稱的完全類名可能是更好的主意。


          逐步概述

          您已經(jīng)看到了為任何域?qū)ο髣?chuàng)建和配置新 DAO 所必需的全部步驟。三個簡單的步驟是:

          定義一個接口,它擴展 GenericDao 并包含所需的任何查找器方法。
          將每個查找器的命名查詢添加到域?qū)ο蟮?hbm.xml 映射文件。
          為 DAO 添加 10 行 Spring 配置文件。
          查看執(zhí)行查找器方法的代碼(只編寫了一次!)來結(jié)束我的討論。


          可重用的 DAO 類

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


          清單 10. FinderIntroductionAdvisor 的實現(xiàn)
          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 的實現(xiàn)中惟一缺少的是 executeFinder() 實現(xiàn)。該代碼查看調(diào)用的類和方法的名稱,并使用配置上的約定將其與 Hibernate 查詢的名稱相匹配。還可以使用 FinderNamingStrategy 來支持其他命名查詢的方法。默認實現(xiàn)查找叫做 “ClassName.methodName” 的查詢,其中 ClassName 是不帶包的短名稱。清單 11 完成了泛型類型安全 DAO 實現(xiàn):


          清單 11. executeFinder() 的實現(xiàn)
          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();
           }
           

           

          結(jié)束語

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

          幾乎本文的所有代碼都是可重用的。盡管您的 DAO 類可能包含此處沒有實現(xiàn)的查詢和操作類型(比如,批操作),但使用我所展示的技術(shù),您至少應(yīng)該能夠?qū)崿F(xiàn)其中的一部分。參閱 參考資料 了解其他泛型類型安全 DAO 類實現(xiàn)。

           

          本文來自CSDN博客:http://blog.csdn.net/jhaij/archive/2007/06/17/1655535.aspx

          posted on 2010-03-26 00:13 csuct 閱讀(823) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           

          導(dǎo)航

          統(tǒng)計

          常用鏈接

          留言簿

          隨筆檔案

          搜索

          最新評論

          主站蜘蛛池模板: 城口县| 渑池县| 山西省| 双辽市| 黄浦区| 历史| 永清县| 柳江县| 黄平县| 河曲县| 云霄县| 巨野县| 班玛县| 汉阴县| 伊宁市| 商水县| 兰考县| 红安县| 靖安县| 吉林市| 皮山县| 出国| 都匀市| 确山县| 高平市| 临城县| 资源县| 富裕县| 若尔盖县| 北海市| 巴彦县| 吴川市| 大埔区| 山东省| 合作市| 林甸县| 沭阳县| 古丈县| 临城县| 山西省| 吴桥县|