學(xué)習(xí)筆記

          Simple is beautiful.

          導(dǎo)航

          <2007年4月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          293012345

          統(tǒng)計(jì)

          公告

          ...

          常用鏈接

          留言簿(1)

          隨筆分類(2)

          隨筆檔案(56)

          Weblog

          搜索

          最新評(píng)論

          評(píng)論排行榜

          [ZT]Spring數(shù)據(jù)訪問(wèn)對(duì)象(DAO)框架

           摘要: J2EE應(yīng)用程序中的業(yè)務(wù)組件通常使用JDBC API訪問(wèn)和更改關(guān)系數(shù)據(jù)庫(kù)中的持久數(shù)據(jù)。這經(jīng)常導(dǎo)致持久性代碼與業(yè)務(wù)邏輯發(fā)生混合,這是一種不好的習(xí)慣。數(shù)據(jù)訪問(wèn)對(duì)象(DAO)設(shè)計(jì)模式通過(guò)把持久性邏輯分成若干數(shù)據(jù)訪問(wèn)類來(lái)解決這一問(wèn)題。

            本文是一篇關(guān)于DAO設(shè)計(jì)模式的入門文章,突出講述了它的優(yōu)點(diǎn)和不足之處。另外,本文還介紹了Spring 2.0 JDBC/DAO框架并示范了它如何妥善地解決傳統(tǒng)DAO設(shè)計(jì)中的缺陷。

          傳統(tǒng)的DAO設(shè)計(jì)

            數(shù)據(jù)訪問(wèn)對(duì)象(DAO)是一個(gè)集成層設(shè)計(jì)模式,如Core J2EE Design Pattern 圖書(shū)所歸納。它將持久性存儲(chǔ)訪問(wèn)和操作代碼封裝到一個(gè)單獨(dú)的層中。本文的上下文中所提到的持久存儲(chǔ)器是一個(gè)RDBMS。

            這一模式在業(yè)務(wù)邏輯層和持久存儲(chǔ)層之間引入了一個(gè)抽象層,如圖1所示。業(yè)務(wù)對(duì)象通過(guò)數(shù)據(jù)訪問(wèn)對(duì)象來(lái)訪問(wèn)RDBMS(數(shù)據(jù)源)。抽象層改善了應(yīng)用程序代碼并引入了靈活性。理論上,當(dāng)數(shù)據(jù)源改變時(shí),比如更換數(shù)據(jù)庫(kù)供應(yīng)商或是數(shù)據(jù)庫(kù)的類型時(shí),僅需改變數(shù)據(jù)訪問(wèn)對(duì)象,從而把對(duì)業(yè)務(wù)對(duì)象的影響降到最低。

          SPRING數(shù)據(jù)訪問(wèn)對(duì)象(DAO)框架入門圖-1

            圖1. 應(yīng)用程序結(jié)構(gòu),包括DAO之前和之后的部分

            講解了DAO設(shè)計(jì)模式的基礎(chǔ)知識(shí),下面將編寫(xiě)一些代碼。下面的例子來(lái)自于一個(gè)公司域模型。簡(jiǎn)而言之,這家公司有幾位員工工作在不同的部門,如銷售部、市場(chǎng)部以及人力資源部。為了簡(jiǎn)單起見(jiàn),我們將集中討論一個(gè)稱作“雇員”的實(shí)體。

          針對(duì)接口編程

            DAO設(shè)計(jì)模式帶來(lái)的靈活性首先要?dú)w功于一個(gè)對(duì)象設(shè)計(jì)的最佳實(shí)踐:針對(duì)接口編程(P2I)。這一原則規(guī)定實(shí)體必須實(shí)現(xiàn)一個(gè)供調(diào)用程序而不是實(shí)體自身使用的接口。因此,可以輕松替換成不同的實(shí)現(xiàn)而對(duì)客戶端代碼只產(chǎn)生很小的影響。

            我們將據(jù)此使用findBySalaryRange()行為定義Employee DAO接口,IEmployeeDAO。業(yè)務(wù)組件將通過(guò)這個(gè)接口與DAO交互:

          import java.util.Map;
          public interface IEmployeeDAO {
          //SQL String that will be executed
          public String FIND_BY_SAL_RNG = "SELECT EMP_NO, EMP_NAME, "
          + "SALARY FROM EMP WHERE SALARY >= ? AND SALARY <= ?";
          //Returns the list of employees who fall into the given salary
          //range. The input parameter is the immutable map object
          //obtained from the HttpServletRequest. This is an early
          //refactoring based on "Introduce Parameter Object"
          public List findBySalaryRange(Map salaryMap);
          }
          

          提供DAO實(shí)現(xiàn)類

            接口已經(jīng)定義,現(xiàn)在必須提供Employee DAO的具體實(shí)現(xiàn),EmployeeDAOImpl:

          import java.sql.Connection;
          import java.sql.PreparedStatement;
          import java.sql.ResultSet;
          import java.util.List;
          import java.util.ArrayList;
          import java.util.Map;
          import com.bea.dev2dev.to.EmployeeTO;
          public class EmployeeDAOImpl implements IEmployeeDAO{
          public List findBySalaryRange(Map salaryMap)
          {
          Connection conn = null;
          PreparedStatement pstmt = null;
          ResultSet rs = null;
          List empList = new ArrayList();
          //Transfer Object for inter-tier data transfer
          EmployeeTO tempEmpTO = null;
          try{
          //DBUtil - helper classes that retrieve connection from pool
          conn = DBUtil.getConnection();
          pstmt = conn.prepareStatement(FIND_BY_SAL_RNG);
          pstmt.setDouble(1, Double.valueOf( (String)
          salaryMap.get("MIN_SALARY") );
          pstmt.setDouble(2, Double.valueOf( (String)
          salaryMap.get("MIN_SALARY") );
          rs = pstmt.executeQuery();
          int tmpEmpNo = 0;
          String tmpEmpName = "";
          double tmpSalary = 0.0D;
          while (rs.next()){
          tmpEmpNo = rs.getInt("EMP_NO");
          tmpEmpName = rs.getString("EMP_NAME");
          tmpSalary = rs.getDouble("SALARY");
          tempEmpTO = new EmployeeTO(tmpEmpNo,
          tmpEmpName,
          tmpSalary);
          empList.add(tempEmpTO);
          }//end while
          }//end try
          catch (SQLException sqle){
          throw new DBException(sqle);
          }//end catch
          finally{
          try{
          if (rs != null){
          rs.close();
          }
          }
          catch (SQLException sqle){
          throw new DBException(sqle);
          }
          try{
          if (pstmt != null){
          pstmt.close();
          }
          }
          catch (SQLException sqle){
          throw new DBException(sqle);
          }
          try{
          if (conn != null){
          conn.close();
          }
          }
          catch (SQLException sqle){
          throw new DBException(sqle);
          }
          }//end of finally block
          return empList;
          }//end method findBySalaryRange
          }
          

            上面的清單說(shuō)明了DAO方法的一些要點(diǎn):

          • 它們封裝了所有與JDBC API的交互。如果使用像Kodo或者Hibernate的O/R映射方案,則DAO類可以將這些產(chǎn)品的私有API打包。
          • 它們將檢索到的數(shù)據(jù)打包到一個(gè)與JDBC API無(wú)關(guān)的傳輸對(duì)象中,然后將其返回給業(yè)務(wù)層作進(jìn)一步處理。
          • 它們實(shí)質(zhì)上是無(wú)狀態(tài)的。唯一的目的是訪問(wèn)并更改業(yè)務(wù)對(duì)象的持久數(shù)據(jù)。
          • 在這個(gè)過(guò)程中,它們像SQLException一樣捕獲任何底層JDBC API或數(shù)據(jù)庫(kù)報(bào)告的錯(cuò)誤(例如,數(shù)據(jù)庫(kù)不可用、錯(cuò)誤的SQL句法)。DAO對(duì)象再次使用一個(gè)與JDBC無(wú)關(guān)的自定義運(yùn)行時(shí)異常類DBException,通知業(yè)務(wù)對(duì)象這些錯(cuò)誤。
          • 它們像Connection和PreparedStatement對(duì)象那樣,將數(shù)據(jù)庫(kù)資源釋放回池中,并在使用完ResultSet游標(biāo)之后,將其所占用的內(nèi)存釋放。

            因此,DAO層將底層的數(shù)據(jù)訪問(wèn)API抽象化,為業(yè)務(wù)層提供了一致的數(shù)據(jù)訪問(wèn)API。

          構(gòu)建DAO工廠

            DAO工廠是典型的工廠設(shè)計(jì)模式實(shí)現(xiàn),用于為業(yè)務(wù)對(duì)象創(chuàng)建和提供具體的DAO實(shí)現(xiàn)。業(yè)務(wù)對(duì)象使用DAO接口,而不用了解實(shí)現(xiàn)類的具體情況。DAO工廠帶來(lái)的依賴反轉(zhuǎn)(dependency inversion)提供了極大的靈活性。只要DAO接口建立的約定未改變,那么很容易改變DAO實(shí)現(xiàn)(例如,從straight JDBC實(shí)現(xiàn)到基于Kodo的O/R映射),同時(shí)又不影響客戶的業(yè)務(wù)對(duì)象:

          public class DAOFactory {
          private static DAOFactory daoFac;
          static{
          daoFac = new DAOFactory();
          }
          private DAOFactory(){}
          public DAOFactory getInstance(){
          return daoFac;
          }
          public IEmployeeDAO getEmployeeDAO(){
          return new EmployeeDAOImpl();
          }
          }
          

          與業(yè)務(wù)組件的協(xié)作

            現(xiàn)在該了解DAO怎樣適應(yīng)更復(fù)雜的情形。如前幾節(jié)所述,DAO與業(yè)務(wù)層組件協(xié)作獲取和更改持久業(yè)務(wù)數(shù)據(jù)。下面的清單展示了業(yè)務(wù)服務(wù)組件及其與DAO層的交互:

          public class EmployeeBusinessServiceImpl implements
          IEmployeeBusinessService {
          public List getEmployeesWithinSalaryRange(Map salaryMap){
          IEmployeeDAO empDAO = DAOFactory.getInstance()
          .getEmployeeDAO();
          List empList = empDAO.findBySalaryRange(salaryMap);
          return empList;
          }
          }
          

            交互過(guò)程十分簡(jiǎn)潔,完全不依賴于任何持久性接口(包括JDBC)。

          問(wèn)題

            DAO設(shè)計(jì)模式也有缺點(diǎn):

          • 代碼重復(fù):從EmployeeDAOImpl清單可以清楚地看到,對(duì)于基于JDBC的傳統(tǒng)數(shù)據(jù)庫(kù)訪問(wèn),代碼重復(fù)(如上面的粗體字所示)是一個(gè)主要的問(wèn)題。一遍又一遍地寫(xiě)著同樣的代碼,明顯違背了基本的面向?qū)ο笤O(shè)計(jì)的代碼重用原則。它將對(duì)項(xiàng)目成本、時(shí)間安排和工作產(chǎn)生明顯的副面影響。
          • 耦合:DAO代碼與JDBC接口和核心collection耦合得非常緊密。從每個(gè)DAO類的導(dǎo)入聲明的數(shù)量可以明顯地看出這種耦合。
          • 資源耗損:依據(jù)EmployeeDAOImpl類的設(shè)計(jì),所有DAO方法必須釋放對(duì)所獲得的連接、聲明、結(jié)果集等數(shù)據(jù)庫(kù)資源的控制。這是危險(xiǎn)的主張,因?yàn)橐粋€(gè)編程新手可能很容易漏掉那些約束。結(jié)果造成資源耗盡,導(dǎo)致系統(tǒng)停機(jī)。
          • 錯(cuò)誤處理:JDBC 驅(qū)動(dòng)程序通過(guò)拋出SQLException來(lái)報(bào)告所有的錯(cuò)誤情況。SQLException是檢查到的異常,所以開(kāi)發(fā)人員被迫去處理它,即使不可能從這類導(dǎo)致代碼混亂的大多數(shù)異常中恢復(fù)過(guò)來(lái)。而且,從SQLException對(duì)象獲得的錯(cuò)誤代碼和消息特定于數(shù)據(jù)庫(kù)廠商,所以不可能寫(xiě)出可移植的DAO錯(cuò)誤發(fā)送代碼。
          • 脆弱的代碼:在基于JDBC的DAO中,兩個(gè)常用的任務(wù)是設(shè)置聲明對(duì)象的綁定變量和使用結(jié)果集檢索數(shù)據(jù)。如果SQL where子句中的列數(shù)目或者位置更改了,就不得不對(duì)代碼執(zhí)行更改、測(cè)試、重新部署這個(gè)嚴(yán)格的循環(huán)過(guò)程。

            讓我們看看如何能夠減少這些問(wèn)題并保留DAO的大多數(shù)優(yōu)點(diǎn)。

          進(jìn)入Spring DAO

            先識(shí)別代碼中發(fā)生變化的部分,然后將這一部分代碼分離出來(lái)或者封裝起來(lái),就能解決以上所列出的問(wèn)題。Spring的設(shè)計(jì)者們已經(jīng)完全做到了這一點(diǎn),他們發(fā)布了一個(gè)超級(jí)簡(jiǎn)潔、健壯的、高度可伸縮的JDBC框架。固定部分(像檢索連接、準(zhǔn)備聲明對(duì)象、執(zhí)行查詢和釋放數(shù)據(jù)庫(kù)資源)已經(jīng)被一次性地寫(xiě)好,所以該框架的一部分內(nèi)容有助于消除在傳統(tǒng)的基于JDBC的DAO中出現(xiàn)的缺點(diǎn)。

            圖2顯示的是Spring JDBC框架的主要組成部分。業(yè)務(wù)服務(wù)對(duì)象通過(guò)適當(dāng)?shù)慕涌诶^續(xù)使用DAO實(shí)現(xiàn)類。JdbcDaoSupport是JDBC數(shù)據(jù)訪問(wèn)對(duì)象的超類。它與特定的數(shù)據(jù)源相關(guān)聯(lián)。Spring Inversion of Control (IOC)容器或BeanFactory負(fù)責(zé)獲得相應(yīng)數(shù)據(jù)源的配置詳細(xì)信息,并將其與JdbcDaoSupport相關(guān)聯(lián)。這個(gè)類最重要的功能就是使子類可以使用JdbcTemplate對(duì)象。

          SPRING數(shù)據(jù)訪問(wèn)對(duì)象(DAO)框架入門圖-2

            圖2. Spring JDBC框架的主要組件

            JdbcTemplate是Spring JDBC框架中最重要的類。引用文獻(xiàn)中的話:“它簡(jiǎn)化了JDBC的使用,有助于避免常見(jiàn)的錯(cuò)誤。它執(zhí)行核心JDBC工作流,保留應(yīng)用代碼以提供SQL和提取結(jié)果。”這個(gè)類通過(guò)執(zhí)行下面的樣板任務(wù)來(lái)幫助分離JDBC DAO代碼的靜態(tài)部分:

          • 從數(shù)據(jù)源檢索連接。
          • 準(zhǔn)備合適的聲明對(duì)象。
          • 執(zhí)行SQL CRUD操作。
          • 遍歷結(jié)果集,然后將結(jié)果填入標(biāo)準(zhǔn)的collection對(duì)象。
          • 處理SQLException異常并將其轉(zhuǎn)換成更加特定于錯(cuò)誤的異常層次結(jié)構(gòu)。

          利用Spring DAO重新編寫(xiě)

            既然已基本理解了Spring JDBC框架,現(xiàn)在要重新編寫(xiě)已有的代碼。下面將逐步講述如何解決前幾節(jié)中提到的問(wèn)題。

          第一步:修改DAO實(shí)現(xiàn)類- 現(xiàn)在從JdbcDaoSupport擴(kuò)展出EmployeeDAOImpl以獲得JdbcTemplate。

          import org.springframework.jdbc.core.support.JdbcDaoSupport;
          import org.springframework.jdbc.core.JdbcTemplate;
          public class EmployeeDAOImpl extends JdbcDaoSupport
          implements IEmployeeDAO{
          public List findBySalaryRange(Map salaryMap){
          Double dblParams [] = {Double.valueOf((String)
          salaryMap.get("MIN_SALARY"))
          ,Double.valueOf((String)
          salaryMap.get("MAX_SALARY"))
          };
          //The getJdbcTemplate method of JdbcDaoSupport returns an
          //instance of JdbcTemplate initialized with a datasource by the
          //Spring Bean Factory
          JdbcTemplate daoTmplt = this.getJdbcTemplate();
          return daoTmplt.queryForList(FIND_BY_SAL_RNG,dblParams);
          }
          }
          

            在上面的清單中,傳入?yún)?shù)映射中的值存儲(chǔ)在雙字節(jié)數(shù)組中,順序與SQL字符串中的位置參數(shù)相同。queryForList()方法以包含Map(用列名作為鍵,一項(xiàng)對(duì)應(yīng)一列)的List(一項(xiàng)對(duì)應(yīng)一行)的方式返回查詢結(jié)果。稍后我會(huì)說(shuō)明如何返回傳輸對(duì)象列表。

            從簡(jiǎn)化的代碼可以明顯看出,JdbcTemplate鼓勵(lì)重用,這大大削減了DAO實(shí)現(xiàn)中的代碼。JDBC和collection包之間的緊密耦合已經(jīng)消除。由于JdbcTemplate方法可確保在使用數(shù)據(jù)庫(kù)資源后將其按正確的次序釋放,所以JDBC的資源耗損不再是一個(gè)問(wèn)題。

            另外,使用Spring DAO時(shí),不必處理異常。JdbcTemplate類會(huì)處理SQLException,并根據(jù)SQL錯(cuò)誤代碼或錯(cuò)誤狀態(tài)將其轉(zhuǎn)換成特定于Spring異常的層次結(jié)構(gòu)。例如,試圖向主鍵列插入重復(fù)值時(shí),將引發(fā)DataIntegrityViolationException。然而,如果無(wú)法從這一錯(cuò)誤中恢復(fù),就無(wú)需處理該異常。因?yàn)镾pring DAO的根異常類DataAccessException是運(yùn)行時(shí)異常類,所以可以這樣做。值得注意的是Spring DAO異常獨(dú)立于數(shù)據(jù)訪問(wèn)實(shí)現(xiàn)。如果實(shí)現(xiàn)是由O/R映射解決方案提供,就會(huì)拋出同樣的異常。

          第二步:修改業(yè)務(wù)服務(wù)- 現(xiàn)在業(yè)務(wù)服務(wù)實(shí)現(xiàn)了一個(gè)新方法setDao(),Spring容器使用該方法傳遞DAO實(shí)現(xiàn)類的引用。該過(guò)程稱為“設(shè)置方法注入(setter injection)”,通過(guò)第三步中的配置文件告知Spring容器該過(guò)程。注意,不再需要使用DAOFactory,因?yàn)镾pring BeanFactory提供了這項(xiàng)功能:

          public class EmployeeBusinessServiceImpl
          implements IEmployeeBusinessService {
          IEmployeeDAO empDAO;
          public List getEmployeesWithinSalaryRange(Map salaryMap){
          List empList = empDAO.findBySalaryRange(salaryMap);
          return empList;
          }
          public void setDao(IEmployeeDAO empDAO){
          this.empDAO = empDAO;
          }
          }
          

            請(qǐng)注意P2I的靈活性;即使極大地改動(dòng)DAO實(shí)現(xiàn),業(yè)務(wù)服務(wù)實(shí)現(xiàn)也只需少量更改。這是由于業(yè)務(wù)服務(wù)現(xiàn)在由Spring容器進(jìn)行管理。

          第三步:配置Bean Factory- Spring bean factory需要一個(gè)配置文件進(jìn)行初始化并啟動(dòng)Spring框架。這個(gè)配置文件包含所有業(yè)務(wù)服務(wù)和帶Spring bean容器的DAO實(shí)現(xiàn)類。除此之外,它還包含用于初始化數(shù)據(jù)源和JdbcDaoSupport的信息:

          <?xml version="1.0" encoding="UTF-8"?>
          <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
          "http://www.springframework.org/dtd/spring-beans.dtd">
          <beans>
          <!-- Configure Datasource -->
          <bean id="FIREBIRD_DATASOURCE"
          class="org.springframework.jndi.JndiObjectFactoryBean">
          <property name="jndiEnvironment">
          <props>
          <prop key="java.naming.factory.initial">
          weblogic.jndi.WLInitialContextFactory
          </prop>
          <prop key="java.naming.provider.url">
          t3://localhost:7001
          </prop>
          </props>
          </property>
          <property name="jndiName">
          <value>
          jdbc/DBPool
          </value>
          </property>
          </bean>
          <!-- Configure DAO -->
          <bean id="EMP_DAO" class="com.bea.dev2dev.dao.EmployeeDAOImpl">
          <property name="dataSource">
          <ref bean="FIREBIRD_DATASOURCE"></ref>
          </property>
          </bean>
          <!-- Configure Business Service -->
          <bean id="EMP_BUSINESS"
          class="com.bea.dev2dev.sampleapp.business.EmployeeBusinessServiceImpl">
          <property name="dao">
          <ref bean="EMP_DAO"></ref>
          </property>
          </bean>
          </beans>
          

            這個(gè)Spring bean容器通過(guò)調(diào)用JdbcDaoSupport提供的setDataSource()方法,設(shè)置包含DAO實(shí)現(xiàn)的數(shù)據(jù)源對(duì)象。

          第四步:測(cè)試- 最后是編寫(xiě)JUnit測(cè)試類。依照Spring的方式,需要在容器外部進(jìn)行測(cè)試。然而,從第三步中的配置文件可以清楚地看到,我們一直在使用WebLogic Server連接池。

          package com.bea.dev2dev.business;
          import java.util.*;
          import junit.framework.*;
          import org.springframework.context.ApplicationContext;
          import org.springframework.context.support.FileSystemXmlApplicationContext;
          public class EmployeeBusinessServiceImplTest extends TestCase {
          private IEmployeeBusinessService empBusiness;
          private Map salaryMap;
          List expResult;
          protected void setUp() throws Exception {
          initSpringFramework();
          initSalaryMap();
          initExpectedResult();
          }
          private void initExpectedResult() {
          expResult = new ArrayList();
          Map tempMap = new HashMap();
          tempMap.put("EMP_NO",new Integer(1));
          tempMap.put("EMP_NAME","John");
          tempMap.put("SALARY",new Double(46.11));
          expResult.add(tempMap);
          }
          private void initSalaryMap() {
          salaryMap = new HashMap();
          salaryMap.put("MIN_SALARY","1");
          salaryMap.put("MAX_SALARY","50");
          }
          private void initSpringFramework() {
          ApplicationContext ac = new FileSystemXmlApplicationContext
          ("C:/SpringConfig/Spring-Config.xml");
          empBusiness =
          (IEmployeeBusinessService)ac.getBean("EMP_BUSINESS");
          }
          protected void tearDown() throws Exception {
          }
          /**
          * Test of getEmployeesWithinSalaryRange method,
          * of class
          * com.bea.dev2dev.business.EmployeeBusinessServiceImpl.
          */
          public void testGetEmployeesWithinSalaryRange() {
          List result = empBusiness.getEmployeesWithinSalaryRange
          (salaryMap);
          assertEquals(expResult, result);
          }
          }
          

          使用綁定變量

            到目前為止,我們搜索了工資介于最低值和最高值之間的雇員。假設(shè)在某種情形下,業(yè)務(wù)用戶想要顛倒這一范圍。DAO代碼很脆弱,將不得不通過(guò)更改來(lái)滿足要求的變化。這個(gè)問(wèn)題在于使用了靜態(tài)的位置綁定變量(用“?”表示)。Spring DAO通過(guò)支持命名的綁定變量來(lái)挽救這個(gè)情況。修改的IEmployeeDAO清單引入了命名的綁定變量(用“:<some name>”表示)。注意查詢中的變化,如下所示:

          import java.util.Map;
          public interface IEmployeeDAO {
          //SQL String that will be executed
          public String FIND_BY_SAL_RNG = "SELECT EMP_NO, EMP_NAME, "
          + "SALARY FROM EMP WHERE SALARY >= :max AND SALARY <= :min";
          //Returns the list of employees falling into the given salary range
          //The input parameter is the immutable map object obtained from
          //the HttpServletRequest. This is an early refactoring based on
          //- "Introduce Parameter Object"
          public List findBySalaryRange(Map salaryMap);
          }
          

            多數(shù)JDBC驅(qū)動(dòng)程序僅支持位置綁定變量。所以,Spring DAO在運(yùn)行時(shí)將這個(gè)查詢轉(zhuǎn)換成位置綁定、基于變量的查詢,并且設(shè)置正確的綁定變量。現(xiàn)在,為了完成這些任務(wù),需要使用NamedParameterJdbcDaoSupport類和NamedParameterJdbcTemplate類,以代替JdbcDaoSupport和JdbcTemplate。下面就是修改后的DAO實(shí)現(xiàn)類:

          import org.springframework.jdbc.core.namedparam.NamedParameterJdbcDaoSupport;
          import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
          public class EmployeeDAOImpl extends NamedParameterJdbcDaoSupport
          implements IEmployeeDAO{
          public List findBySalaryRange(Map salaryMap){
          NamedParameterJdbcTemplate tmplt =
          this.getNamedParameterJdbcTemplate();
          return tmplt.queryForList(IEmployeeDAO.FIND_BY_SAL_RNG
          ,salaryMap);
          }
          }
          

            NamedParameterJdbcDaoSupport的getNamedParameterJdbcTemplate()方法返回一個(gè) NamedParameterJdbcTemplate實(shí)例,該實(shí)例由數(shù)據(jù)源句柄進(jìn)行了預(yù)初始化。Spring Beanfactory執(zhí)行初始化任務(wù),從配置文件獲得所有的詳細(xì)信息。在執(zhí)行時(shí),一旦將命名的參數(shù)替換成位置占位符, NamedParameterJdbcTemplate就將操作委托給JdbcTemplate。可見(jiàn),使用命名的參數(shù)使得DAO方法不受底層SQL聲明任何更改的影響。

            最后,如果數(shù)據(jù)庫(kù)不支持自動(dòng)類型轉(zhuǎn)換,需要如下所示,對(duì)JUnit測(cè)試類中的initSalaryMap()方法稍做修改。

          private void initSalaryMap() {
          salaryMap = new HashMap();
          salaryMap.put("MIN_SALARY",new Double(1));
          salaryMap.put("MAX_SALARY",new Double(50));
          }
          

          Spring DAO回調(diào)函數(shù)

            至此,已經(jīng)說(shuō)明為了解決傳統(tǒng)DAO設(shè)計(jì)中存在的問(wèn)題,如何封裝和概括JdbcTemplate類中JDBC代碼的靜態(tài)部分。現(xiàn)在了解一下有關(guān)變量的問(wèn)題,如設(shè)置綁定變量、結(jié)果集遍歷等。雖然Spring DAO已經(jīng)擁有這些問(wèn)題的一般化解決方案,但在某些基于SQL的情況下,可能仍需要設(shè)置綁定變量。

            在嘗試向Spring DAO轉(zhuǎn)換的過(guò)程中,介紹了由于業(yè)務(wù)服務(wù)及其客戶機(jī)之間的約定遭到破壞而導(dǎo)致的隱蔽運(yùn)行時(shí)錯(cuò)誤。這個(gè)錯(cuò)誤的來(lái)源可以追溯到原始的DAO。 dbcTemplate.queryForList()方法不再返回EmployeeTO實(shí)例列表。而是返回一個(gè)map表(每個(gè)map是結(jié)果集的一行)。

            如您目前所知,JdbcTemplate基于模板方法設(shè)計(jì)模式,該模式利用JDBC API定義SQL執(zhí)行工作流。必須改變這個(gè)工作流以修復(fù)被破壞的約定。第一個(gè)選擇是在子類中更改或擴(kuò)展工作流。您可以遍歷 JdbcTemplate.queryForList()返回的列表,用EmployeeTO實(shí)例替換map對(duì)象。然而,這會(huì)導(dǎo)致我們一直竭力避免的靜態(tài)代碼與動(dòng)態(tài)代碼的混合。第二個(gè)選擇是將代碼插入JdbcTemplate提供的各種工作流修改鉤子(hook)。明智的做法是在一個(gè)不同的類中封裝傳輸對(duì)象填充代碼,然后通過(guò)鉤子鏈接它。填充邏輯的任何修改將不會(huì)改變DAO。

            編寫(xiě)一個(gè)類,使其實(shí)現(xiàn)在Spring框架特定的接口中定義的方法,就可以實(shí)現(xiàn)第二個(gè)選擇。這些方法稱為回調(diào)函數(shù),通過(guò)JdbcTemplate向框架注冊(cè)。當(dāng)發(fā)生相應(yīng)的事件(例如,遍歷結(jié)果集并填充獨(dú)立于框架的傳輸對(duì)象)時(shí),框架將調(diào)用這些方法。

          第一步:傳輸對(duì)象

            下面是您可能感興趣的傳輸對(duì)象。注意,以下所示的傳輸對(duì)象是固定的:

          package com.bea.dev2dev.to;
          public final class EmployeeTO implements Serializable{
          private int empNo;
          private String empName;
          private double salary;
          /** Creates a new instance of EmployeeTO */
          public EmployeeTO(int empNo,String empName,double salary) {
          this.empNo = empNo;
          this.empName = empName;
          this.salary = salary;
          }
          public String getEmpName() {
          return this.empName;
          }
          public int getEmpNo() {
          return this.empNo;
          }
          public double getSalary() {
          return this.salary;
          }
          public boolean equals(EmployeeTO empTO){
          return empTO.empNo == this.empNo;
          }
          }
          

          第二步:實(shí)現(xiàn)回調(diào)接口

            實(shí)現(xiàn)RowMapper接口,填充來(lái)自結(jié)果集的傳輸對(duì)象。下面是一個(gè)例子:

          package com.bea.dev2dev.dao.mapper;
          import com.bea.dev2dev.to.EmployeeTO;
          import java.sql.ResultSet;
          import java.sql.SQLException;
          import org.springframework.jdbc.core.RowMapper;
          public class EmployeeTOMapper implements RowMapper{
          public Object mapRow(ResultSet rs, int rowNum)
          throws SQLException{
          int empNo = rs.getInt(1);
          String empName = rs.getString(2);
          double salary = rs.getDouble(3);
          EmployeeTO empTo = new EmployeeTO(empNo,empName,salary);
          return empTo;
          }
          }
          

            注意實(shí)現(xiàn)類不應(yīng)該對(duì)提供的ResultSet對(duì)象調(diào)用next()方法。這由框架負(fù)責(zé),該類只要從結(jié)果集的當(dāng)前行提取值就行。回調(diào)實(shí)現(xiàn)拋出的任何SQLException也由Spring框架處理。

          第三步:插入回調(diào)接口

            執(zhí)行SQL查詢時(shí),JdbcTemplate利用默認(rèn)的RowMapper實(shí)現(xiàn)產(chǎn)生map列表。現(xiàn)在需要注冊(cè)自定義回調(diào)實(shí)現(xiàn)來(lái)修改 JdbcTemplate的這一行為。注意現(xiàn)在用的是NamedParameterJdbcTemplate的query()方法,而不是 queryForList()方法:

          public class EmployeeDAOImpl extends NamedParameterJdbcDaoSupport
          implements IEmployeeDAO{
          public List findBySalaryRange(Map salaryMap){
          NamedParameterJdbcTemplate daoTmplt =
          getNamedParameterJdbcTemplate();
          return daoTmplt.query(IEmployeeDAO.FIND_BY_SAL_RNG, salaryMap,
          new EmployeeTOMapper());
          }
          }
          

            Spring DAO框架對(duì)執(zhí)行查詢后返回的結(jié)果進(jìn)行遍歷。它在遍歷的每一步調(diào)用EmployeeTOMapper類實(shí)現(xiàn)的mapRow()方法,使用EmployeeTO傳輸對(duì)象填充最終結(jié)果的每一行。

          第四步:修改后的JUnit類

            現(xiàn)在要根據(jù)返回的傳輸對(duì)象測(cè)試這些結(jié)果。為此要對(duì)測(cè)試方法進(jìn)行修改。

          public class EmployeeBusinessServiceImplTest extends TestCase {
          private IEmployeeBusinessService empBusiness;
          private Map salaryMap;
          List expResult;
          // all methods not shown in the listing remain the
          // same as in the previous example
          private void initExpectedResult() {
          expResult = new ArrayList();
          EmployeeTO to = new EmployeeTO(2,"John",46.11);
          expResult.add(to);
          }
          /**
          * Test of getEmployeesWithinSalaryRange method, of
          * class com.bea.dev2dev.business.
          * EmployeeBusinessServiceImpl
          */
          public void testGetEmployeesWithinSalaryRange() {
          List result = empBusiness.
          getEmployeesWithinSalaryRange(salaryMap);
          assertEquals(expResult, result);
          }
          public void assertEquals(List expResult, List result){
          EmployeeTO expTO = (EmployeeTO) expResult.get(0);
          EmployeeTO actualTO = (EmployeeTO) result.get(0);
          if(!expTO.equals(actualTO)){
          throw new RuntimeException("** Test Failed **");
          }
          }
          }
          

          優(yōu)勢(shì)

            Spring JDBC框架的優(yōu)點(diǎn)很清楚。我們獲益很多,并將DAO方法簡(jiǎn)化到只有幾行代碼。代碼不再脆弱,這要感謝該框架對(duì)命名的參數(shù)綁定變量的“開(kāi)箱即用”支持,以及在映射程序中將傳輸對(duì)象填充邏輯分離。

            Spring JDBC的優(yōu)點(diǎn)應(yīng)該促使您向這一框架移植現(xiàn)有的代碼。希望本文在這一方面能有所幫助。它會(huì)幫助您獲得一些重構(gòu)工具和知識(shí)。例如,如果您沒(méi)有采用P2I Extract Interface,那么可以使用重構(gòu),從現(xiàn)有的DAO實(shí)現(xiàn)類創(chuàng)建接口。除此之外,查看本文的參考資料可以得到更多指導(dǎo)。

          下載

            可以下載本文用到的源代碼。

          結(jié)束語(yǔ)

            在此篇文章中,我講述了數(shù)據(jù)訪問(wèn)對(duì)象(DAO)設(shè)計(jì)模式的基礎(chǔ)知識(shí),并從正反兩方面進(jìn)行了討論。引入Spring DAO或JDBC框架來(lái)克服傳統(tǒng)DAO的不足。然后,根據(jù)Spring框架提供的“開(kāi)箱即用”命名參數(shù)支持對(duì)脆弱的DAO代碼進(jìn)行了改進(jìn)。最后,回調(diào)功能展示了如何在指定點(diǎn)修改框架行為。

          參考資料

           作者簡(jiǎn)介
          Dhrubojyoti Kayal 是Capgemini Consulting的高級(jí)顧問(wèn)。在利用企業(yè)Java技術(shù)開(kāi)發(fā)和設(shè)計(jì)應(yīng)用程序和產(chǎn)品方面,擁有5年以上的經(jīng)驗(yàn)。

          posted on 2007-04-14 09:11 Ecko 閱讀(385) 評(píng)論(0)  編輯  收藏


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


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 宜昌市| 罗田县| 沐川县| 罗定市| 新密市| 韶关市| 浦县| 久治县| 万全县| 鄂州市| 岳阳市| 黄石市| 渭南市| 丹寨县| 稻城县| 罗江县| 榆中县| 梓潼县| 罗源县| 平山县| 班戈县| 资阳市| 樟树市| 乌鲁木齐县| 宣威市| 方城县| 平陆县| 桂林市| 嘉荫县| 成安县| 象州县| 青海省| 望奎县| 浦城县| 大方县| 望都县| 鄂尔多斯市| 永年县| 莎车县| 通道| 昭苏县|