DAO模式

DAO(Data Access Object)模式實際上是兩個模式的組合,即DataAccessor 模式和 Active Domain Object 模式。

其中DataAccessor模式實現了數據訪問和業務邏輯的分離,而Active Domain Object 模式實現了業務數據的對象化封裝,一般我們將這兩個模式組合使用。

Data Accessor模式實現了數據訪問和業務邏輯的分離:就是將數據訪問的實現機制加以封裝,與數據的使用代碼相分離,從外部來看,Data Accessor 提供了黑盒式的數據存取接口。

Domain Object模式實現了面向領域的業務數據對象化封裝。

比如說Person對象,就是對現實業務領域的一個抽象,擁有相關屬性get/set的JavaBean。

 

Target

DAO模式通過對業務層提供數據抽象層接口,實現了以下目標:

1. 數據存儲邏輯的分離

通過對數據訪問邏輯進行抽象,爲上層機構提供抽象化的數據訪問接口。業務層無需關心具體的select, insert, update操作,這樣,一方面避免了業務代碼中混雜JDBC調用語句,使得業務落實實現更加清晰,另一方面,由於數據訪問接口和數據訪問實現分離,也使得開發人員的專業劃分成爲可能。某些精通數據庫操作技術的開發人員可以根據接口提供數據庫訪問的最優化實現,而精通業務的開發人員則可以拋開數據的繁瑣細節,專注於業務邏輯編碼。

2. 數據訪問底層實現的分離

DAO模式通過將數據訪問計劃分爲抽象層和實現層,從而分離了數據使用和數據訪問的實現細節。這意味着業務層與數據訪問的底層細節無關,也就是說,我們可以在保持上層機構不變得情況下,通過切換底層實現來修改數據訪問的具體機制,常見的一個例子就是,我們可以通過僅僅替換數據訪問曾實現,將我們的系統部署在不同的數據庫平臺之上。

3. 資源管理和調度的分離

在數據庫操作中,資源的管理和調度是一個非常值得關注的主題。大多數系統的性能瓶頸往往並非集中於業務邏輯處理本身。在系統涉及的各種資源調度過程中,往往存在着最大的性能黑洞,而數據庫作爲業務系統中最重要的系統資源,自然也成爲關注的焦點。DAO模式將數據訪問邏輯從業務邏輯中脫離開來,使得在數據訪問層實現統一的資源調度成爲可能,通過數據庫連接池以及各種緩存機制(Statement Cache,Data Cache等,緩存的使用是高性能系統實現的一個關鍵所在)的配合使用,往往可以保持上層系統不變的情況下,大幅度提升系統性能。

4.數據抽象

在直接基於JDBC調用的代碼中,程序員面對的數據往往是原始的RecordSet數據集,誠然這樣的數據集可以提供足夠的信息,但對於業務邏輯開發過程而言,如此瑣碎和缺乏寓意的字段型數據實在令人厭倦。

DAO 模式通過對底層數據的封裝,爲業務曾提供一個面向對象的接口,使得業務邏輯開發員可以面向業務中的實體進行編碼。通過引入DAO模式,業務邏輯更加清晰,且富於形象性和描述性,這將爲日後的維護帶來極大的便利。試想,在業務曾通過Customer.getName方法獲得客戶姓名,相對於直接通過SQL語句訪問數據庫表並從ResultSet中獲得某個字符型字段而言,哪種方式更加易於業務邏輯的形象化和簡潔化?


Demo

空洞地談些理論固然沒有什麼價值,我們需要看到的是通過對應用設計模式之後,我們的代碼到底有怎樣的改觀,進而才能對設計帶來的優劣有所感悟。下面讓我們來看看代碼:

public BigDecimal calcAmount(String customerID, BigDecimal amount) {
        Customer customer = CustomerDAO.getCustomer(customerID);
        Promotion promotion = PromotionDAO.getPromotion(customer.getLevel());
        Customer.setSumAmount(customer.getSumAmount().add(amount));
        CustomerDAO.save(customer);
        return amount.multiply(promotion.getRatio());
}

這樣的代碼相信已經足夠明晰,即使對於缺乏數據庫技術基礎的讀者也可以輕鬆閱讀。

從上面這段代碼中,我們可以看到,通過DAO模式對各個數據庫對象進行封裝,我們對業務層屏蔽了數據庫訪問的底層實現,業務層僅包含與本領域相關的邏輯對象和算法,這樣對於業務邏輯開發/維護人員而言,面對的是一個簡潔明快的邏輯實現結構。業務層的開發和維護將變得更加簡單。

 

Improve

上面的例子中我們通過DAO模式實現了業務路基與數據邏輯的分離。對於專項開發(爲特定客戶環境指定的特定業務系統)而言,這樣的分離設計差不多已經可以實現開發過程中業務層面與數據層面的相對獨立,並且在實現複雜性與結構清晰性上達到較好的平衡。

然而,對於一個產品化的業務系統而言,目前的設計卻仍稍顯不足。相對專項原發的軟件項目而言,軟件產品往往需要在不同客戶環境下及時部署。一個典型情況就是常見的論壇系統,一個商業論壇系統可能會部署在廠前上萬個不同的客戶環境中。誠然,由於java良好的跨平臺支持,我們在操作系統之間大可輕易遷移,但在另外一個層面,數據庫層,卻仍然面臨着平臺遷移的窘境。客戶可能已經購買了Oracle, MySQL, Sybase 或者其他類型的數據庫。這就意味着我們的產品必須能部署在這些平臺上,才能滿足客戶的需求。

 

對於我們現有的設計而言,爲了滿足不同客戶的需求,我們可以實現針對不同類型數據庫的Data Accessor, 並根據客戶實際部署環境,通過類文件的靜態替換來實現。顯然,這樣的實現方式在面對大量客戶和複雜的部署環境時,將大大增加部署和維護工作的難度和複雜性。回憶一下“開閉原則”(Open-Close Principle) –對擴展開放,對修改封閉。我們應該採取適當的設計,將此類因素帶來的變動(類的靜態替換)屏蔽在系統之外。

爲了實現跨數據庫平臺移植,或者展開來說,爲了支持不同數據訪問機制之間的可配置切換,我們需要在目前的DAO層引入Factory模式和Proxy模式。

 

這裏所謂的不同數據訪問機制,包括了不同數據庫本身的訪問實現,同時也包括了對於同一數據庫德不同訪問機制的兼容。例如我們的系統部署在小客戶環境中,可能採用了基於JDBC的實現,而在企業環境中部署時,可能採用CMP作爲數據訪問的底層實現,以獲得服務器集羣上的性能優勢(CMP具體怎樣還有待商榷,這裏暫且將其作爲一個理由)。

 

Factory

由於需要針對不同的數據庫訪問機制分別提供各自版本的Data Accessor實現,自然我們會想通過 Java Interface 定義一個調用接口,然後對這個調用接口實現不同數據庫的 Data Accessor。通過以接口作爲調用界面和實現規範,我們就可以避免代碼只能給對具體實現的依賴。

對於例子中的CustomerDAO而言,我們可以抽象出DAO接口,然後不同的數據庫Accessor來實現這個接口,通過DAOFactory來根據配置文件自動分配。

public interface CustomerDAO {
    public Customer getCustomer(String customerID);
    public void save(Customer customer);
}
public class CustomerDAOImpOracle implements CustomerDAO {}
public class CustomerDAOImpMysql implements CustomerDAO {}
 

然後在xml配置文件中定義好映射關係,在獲取實例的時候就會自動加載:

CustomerDAO custDAO = (CustomerDAO)DAOFactory.getDAO(CustomerDAO.class); 

<sectionname="DAO_MYSQL">

    <entrykey="com.jscai.www.dao.CustomerDAO"

        value="com.jscai.www.dao.imp.mysql.CustomerDAOImpMysql"/>

    <entrykey="com.jscai.www.dao.PersonDAO"

        value="com.jscai.www.dao.imp.mysql.PersonDAOImpMysql"/>

</section>

<sectionname="DAO_ORACLE">

    <entrykey="com.jscai.www.dao.CustomerDAO"

        value="com.jscai.www.dao.imp.oracle.CustomerDAOImpOracle"/>

    <entrykey="com.jscai.www.dao.PersonDAO"

        value="com.jscai.www.dao.imp.oracle.PersonDAOImpOracle"/>

</section>

 

         通過上面的代碼我們可以看到,通過接口我們將具體的DAO實現從代碼中分離。也就是說,業務層通過接口調用底層實現,具體的DAO實現類不會出現在我們的業務代碼中。

         而且如果需要切換底層實現,只要切換一個配置項就可以了。

         如果Hibernate和Spring集成後,這個DAOFactory可以通過Spring的BeanFactory來替換,通過DI來把相應的DAO實現傳入。如果需要切換底層實現,只需要換一個配置文件。

         另外,在我們的DAOFactory中還可以做一些優化,比如說DAO可以做成Singleton的模式,反正DAO中沒有特有的屬性,可以全局共享一個instance。

 

Proxy

         加上DAOFactory之後,代碼靈活性提高了很多,但是似乎出現了一些Bad Smell,相對於改造前的calcAmount方法,這段代碼裏混雜了一些數據訪問層的內容,如DAOFactory.getDAO方法的調用,這樣影響業務邏輯代碼的可讀性。

         calcAmount方法應該只有業務邏輯代碼,而關於DAO的獲取是不同層次的操作,影響了代碼整潔性和邏輯的純粹性。我們可以加入一箇中間層 (Proxy) 來消除。

public class CustomerDAOProxy {
    private static CustomerDAO dao;
    public static CustomerDAO getDAO() {
        if (dao ==null) {
            dao = (CustomerDAO) DAOFactory.getDAO(CustomerDAO.class);
        }
        returndao;
    }
    public static Customer getCustomer(String customerID) {
        returngetDAO().getCustomer(customerID);
    }
    public static void save(Customer customer) {
        getDAO().save(customer);
    }
}

         這樣我們的業務邏輯層又能恢復簡潔性和可讀性了:

Customer customer = CustomerDAOProxy.getCustomer(customerID);


 

發佈了44 篇原創文章 · 獲贊 11 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章