風人園

          弱水三千,只取一瓢,便能解渴;佛法無邊,奉行一法,便能得益。
          隨筆 - 99, 文章 - 181, 評論 - 56, 引用 - 0
          數據加載中……

          Don’t repeat the DAO!

          譯者:Nicholas @ Nirvana Studio
          原文地址:http://www-128.ibm.com/developerworks/java/library/j-genericdao.html

          使用Hibernate和Spring AOP購建一個范型類型安全的DAO
          2006年五月12日

          在采用了Java 5的范型之后,要實現一個基于范型類型安全的數據訪問對象(DAO)就變得切實可行了。在這篇文章里,系統架構師Per Mellqvist展示了一個基于Hibernate的范型DAO實現。然后將介紹如何使用Spring AOP的introduction為一個類增加一個類型安全的接口以便于執行查詢。

          對于大多數開發者來說,在系統中為每一個DAO編寫幾乎一樣的代碼已經成為了一種習慣。同時大家也都認可這種重復就是“代碼的味道”,我們中的大多數已經習慣如此。當然也有另外的辦法。你可以使用很多ORM工具來避免代碼的重復編寫。舉個例子,用Hibernate,你可以簡單的使用session操作直接控制你的持久化領域對象。這種方式的負面影響就是丟失了類型安全。

          為什么你的數據訪問代碼需要一個類型安全的接口?我認為它減少了編程錯誤,提高了生產率,尤其是在使用現代高級IDE的時候。首先,一個類型安全的接口清晰的制定了哪些領域對象具有持久化功能。其次,它消除了類型轉換帶來的潛在問題。最后,它平衡了IDE的自動完成功能。使用自動完成功能是最快的方式來記住對于適當的領域類哪些查詢是可用的。

          在這篇文章里,我將展示給大家如何避免一次次地重復編寫DAO代碼,但同時還收益于類型安全的接口。事實上,所有內需要編寫的是為新的DAO編寫一個Hibernate映射文件,一個POJO的Java接口,并且10行Spring配置文件。

          DAO實現

          DAO模式對于任何Java開發人員來說都是耳熟能詳的。這個模式的實現相當多,所以讓我們仔細推敲一下我這篇文章里面對于DAO實現的一些假設:

          • 所有系統中的數據庫訪問都是通過DAO來完成封裝
          • 每一個DAO實例對一個主要的領域對象或者實體負責。如果一個領域對象具有獨立的生命周期,那么它需要具有自己的DAO。
          • DAO具有CRUD操作
          • DAO可以允許基于criteria方式的查詢而不僅僅是通過主鍵查詢。我將這些成為finder方法或者finders。這個finder的返回值通常是DAO所負責的領域對象的集合。

          范型DAO接口

          范型DAO的基礎就是CRUD操作。下面的接口定義了范型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實現上面的接口是非常簡單的。也就是調用一下Hibernate的方法和增加一些類型轉換。Spring負責session和transaction管理。

          						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);
              }
          ?
              publicvoid update(T o){
                  getSession().update(o);
              }
          ?
              publicvoid delete(T o){
                  getSession().delete(o);
              }
          ?
              // Not showing implementations of getSession() and setSessionFactory()}

          Spring 配置

          最后,Spring配置,我創建了一個GenericDaoHibernateImpl的實例。GenericDaoHibernateImpl的構造器必須被告知領域對象的類型,這樣DAO實例才能為之負責。這個同樣需要Hibernate運行時知道這個對象的類型。下面的代碼中,我將領域類Person傳遞給構造器并且將Hibernate的session工廠作為一個參數用來實例化DAO:

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

          可用的范型DAO

          我還沒有全部完成,但我現在已經有了一個可供作的代碼。下面的代碼展示了范型DAO如何使用:

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

          這時候,我有一個范型DAO有能力進行類型安全的CRUD操作。同時也有理由編寫GenericDaoHibernateImpl的子類來為每個領域對象增加查詢功能。但是這篇文章的主旨在于展示如何完成這項功能而不是為每個查詢編寫明確的代碼,然而,我將會使用多個工具來介紹DAO的查詢,這就是Spring AOP和Hibernate命名查詢。

          Spring AOP介紹

          你可以使用Spring AOP提供的introduction功能將一個現存的對象包裝到一個代理里面來增加新的功能,定義它需要實現的新接口,并且將之前所有不支持的方法委派到一個處理機。在我的DAO實現里面,我用introduction將一定數量的finder方法增加到現存的范型DAO類里面。因為finder方法針對特定的領域對象,所以它們被應用到表明接口的范型DAO中。

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

          在上面的配置中,我定義了三個Spring bean,第一個bean,FinderIntroductionAdvisor,處理那些introduce到DAO中但是不屬于GenericDaoHibernateImpl類的方法。一會我再介紹Advisor bean的詳細情況。

          第二個bean定義為“abstract”。在Spring中,這個bean可以被其他bean重用但是它自己不會被實例化。不同于抽象屬性,bean的定義簡單的指出了我需要一個GenericDaoHibernateImpl的實例同時需要一個SessionFactory的引用。注意GenericDaoHibernateImpl類只定義了一個構造器接受領域類作為參數。因為這個bean是抽象的,我可以無限次的重用并且設定合適的領域類。

          最后,第三個,也是最有意思的是bean將GenericDaoHibernateImpl的實例包裝進了一個代理,給予了它執行finder方法的能力。這個bean定義同樣是抽象的并且沒有指定任何接口。這個接口不同于任何具體的實例。

          擴展通用DAO

          每個DAO的接口,都是基于GenericDAO接口的。我需要將為特定的領域類適配接口并且將其擴展包含我的finder方法。

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

          上面的代碼清晰的展示了通過用戶名查找Person對象列表。所需的Java實現類不需要包含任何的更新操作,因為這些已經包含在了通用DAO里。

          配置PersonDao

          因為Spring配置依賴之前的那些抽象bean,所以它變得很緊湊。我需要指定DAO負責的領域類,并且我需要告訴Spring我這個DAO需要實現的接口。

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

          你可以這樣使用:

          						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}

          上面的代碼是使用類型安全接口PersonDao的一種正確途徑,但是DAO的實現并沒有完成。當調用findByName()的時候導致了一個運行時異常。這個問題是我還沒有findByName()。剩下的工作就是指定查詢語句。要完成這個,我使用Hibernate命名查詢。

          Hibernate命名查詢

          使用Hibernate,你可以定義任何HQL查詢在映射文件里,并且給它一個名字。你可以在之后的代碼里面方便的通過名字引用這個查詢。這么做的一個優點就是能夠在部署的時候調節查詢而不需要改變代碼。正如你一會將看到的,另一個好處就是實現一個“完整”的DAO而不需要編寫任何Java實現代碼。

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

          上面的代碼定義了領域類Person的Hibernate映射文件,有兩個屬性:name和weight。Person是一個具有上面屬性的簡單的POJO。這個文件同時包含了一個查詢,通過提供的name屬性從數據庫查找Person實例。Hibernate為命名查詢提供了不真實的命名空間功能。為了便于討論,我將所有的查詢名字的前綴變成領域類的的名稱。在現實場景中,使用完整的類名,包含包名,是一個更好的主意。

          總覽

          你已經看到了為任何領域對象創建并配置DAO的所需步驟了。這三個簡單的步驟就是:

          1. 定義一個接口繼承GenericDao并且包含任何所需的finder方法
          2. 在映射文件中為每個領域類的finder方法增加一個命名查詢。
          3. 為DAO增加10行Spring配置

          可重用的DAO類

          Spring advisor和interceptor的功能比較瑣碎,事實上他們的工作都引用回了GenericDaoHibernateImpl類。所有帶有“find”開頭的方法都被傳遞給DAO的單一方法executeFinder()。

          						public
          						class FinderIntroductionAdvisor extends DefaultIntroductionAdvisor {public FinderIntroductionAdvisor(){super(new FinderIntroductionInterceptor());
              }}
          ?
          publicclass FinderIntroductionInterceptor implements IntroductionInterceptor {
          ?
              publicObject invoke(MethodInvocation methodInvocation)throwsThrowable{
          ?
                  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();
                  }}
          ?
              publicboolean implementsInterface(Class intf){return intf.isInterface() && FinderExecutor.class.isAssignableFrom(intf);
              }}

          executeFinder() 方法

          上面的代碼唯一缺的就是executeFinder的實現。這個代碼觀察被調用的類的名字和方法,并且將他們與Hibernate的查詢名相匹配。你可以使用一個FinderNamingStrategy來激活其他方式的命名查詢。默認的實現查找一個名為“ClassName.methodName”的查詢,ClassName是除包名之外的類名。

          						public List<T> executeFinder(Method method, finalObject[] queryArgs){finalString queryName = queryNameFromMethod(method);
               finalQuery 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();
          }
          ?
          publicString queryNameFromMethod(Method finderMethod){return type.getSimpleName() + "." + finderMethod.getName();
          }

          總結

          在Java 5之前,Java語言并不支持代碼同時具有類型安全和范性的特性;你不得不二者選一。在這篇文章里,你可以看到使用Java 5范型支持并且結合Spring和Hibernate(和AOP)一起來提高生產力。一個范型類型安全的DAO類非常容易編寫,所有你需要做的就是一個接口,一些命名查詢,并且10行Spring配置,并且可以極大的減少錯誤,同時節省時間。

          posted on 2007-03-05 11:51 風人園 閱讀(201) 評論(0)  編輯  收藏 所屬分類: DAO

          主站蜘蛛池模板: 红安县| 昌宁县| 丰都县| 石城县| 灵川县| 休宁县| 梧州市| 佳木斯市| 宜城市| 沧源| 文安县| 丰台区| 南木林县| 白河县| 湟源县| 香港 | 定南县| 宝清县| 鄂伦春自治旗| 东乡| 华蓥市| 漳浦县| 乌兰察布市| 左云县| 兴文县| 萝北县| 峨边| 靖安县| 迭部县| 新郑市| 西贡区| 东平县| 拜城县| 陈巴尔虎旗| 上犹县| 汝南县| 河源市| 五指山市| 甘孜| 曲麻莱县| 涿鹿县|