Java海闊天空

          編程是我的生活,但生活不僅僅是編程。

          基于SSH2框架構建JavaEE應用程序(2)

          四、數據傳輸、數據模型與Dozer

          數據傳輸是程序員實現各種功能時刻需要考慮的問題,從數據模型的建立,到數據模型的轉換,從數據的合法性驗證,到數據類型的轉化,我們要時刻小心,精心設計與組織。數據模型與數據傳輸可簡單可復雜,完全取決于設計者的經驗與意圖,當然,項目的規模也是我們應該考慮的因素,一個小型項目實在沒必要將問題復雜化。


          我們首先考慮數據從視圖(View)傳輸到數據庫(DB)的數據模型變化過程。用戶從界面輸入數據,此時,數據毫無結構可言,是零散的、無組織的,數據提交到控制器后,一方面考慮到OOP的嚴謹性,另一方面考慮到數據的封裝,會將零散的無組織的數據封裝成Value Object(簡稱VO)對象,VO就是JavaBean,并傳送到業務類中,在數據模型比較復雜的情況下,業務類的方法參數和返回值都應該是VOVO的集合,VO轉換成POPersistence Object)后傳送到DAODAO調用Hibernate API持久化數據。簡單來說,業務類對外暴露的數據模型是VODAO對外暴露的數據模型是PO


          數據從數據庫傳遞到視圖層的數據模型變化恰恰相反,Hibernate將數據庫中的記錄轉換成POPO傳遞給業務類后轉換成VOVO被傳送到視圖層進行顯示處理。


          以上轉換過程如下圖:



           

          為什么同時需要POVO?前面說過,這不是必須的,如果項目比較小,直接使用PO就行了。但PO有如下缺點:


          PO反應了數據庫的物理模型,向外暴露PO存在一定的風險;

          PO過于僵化,無法適應變化莫測的業務需求;

          PO在持久化狀態下與數據庫同步,可能導致數據意外修改;

          PO將導致程序缺乏健壯性。


          VO沒有PO的缺點,相對而言,VO更加靈活,能適應各種需求的變化,下面是POVO的區別:


          VO是用new關鍵字創建,由GC回收的。PO則是向數據庫中添加新數據時創建,刪除數據庫中數據時削除的。并且它只能存活在一個數據庫連接中,斷開連接即被銷毀;

          VO是值對象,精確點講它是業務對象,是存活在業務層的,是業務邏輯使用的,它存活的目的就是為數據提供一個生存的地方。PO則是有狀態的,每個屬性代表其當前的狀態。它是物理數據的對象表示。使用它,可以使我們的程序與物理數據解耦,并且可以簡化對象數據與物理數據之間的轉換;

          VO的屬性是根據當前業務的不同而不同的,也就是說,它的每一個屬性都一一對應當前業務邏輯所需要的數據的名稱。PO的屬性是跟數據庫表的字段一一對應的;

          PO一般只有一個,但對應的VO可能有多個。


          我們舉一個簡單的例子來說明VOPO的區別:比如要實現用戶注冊與登陸的功能,在物理模型中創建一個用戶表,字段分別為用戶ID(標識列)、用戶名、密碼、注冊日期等等,定義PO時應該有這四個字段的映射屬性。現在,我們來實現用戶注冊這一功能,為了接收用戶輸入的注冊信息,必須定義VO,注冊用戶需要輸入的信息有:用戶名、密碼1、密碼2,這正好是VO的屬性。而實現用戶登陸功能時,用戶需要輸入的信息只有用戶名和密碼,所以,該VO的屬性只有兩個:用戶名、密碼。可以看出,PO側重于物理模型,而VO則更關注用戶的實際需求。如下圖所示:



           


          因為數據傳輸是雙向的,所以VOPO之間存在相互轉換的問題,這會給編程帶來麻煩,我們總是要通過getter方法取出數據,再通過setter方法給對方屬性賦值,在屬性很多的情況下,讓人深感繁瑣,而Dozer工具能簡化這個問題。


          Dozerhttp://dozer.sourceforge.net/)是一個JavaBean映射工具,能實現對象屬性值之間的相互賦值,Dozer支持簡單類型映射、復合類型映射、雙向映射以及遞歸映射,默認情況下,Dozer能實現類型相同、名字相同的屬性之間的賦值,如果屬性名不同,則必須在xmldozerBeanMapping.xml)配置文件中指定,如果不必為JavaBean的每個屬性賦值,也可以在xml中指定。Xml的定義可以參考http://dozer.sourceforge.net/dtd/dozerbeanmapping.dtd,典型的結構如下:


          <?xml version="1.0" encoding="UTF-8"?>

          <!DOCTYPE mappings PUBLIC "-//DOZER//DTD MAPPINGS//EN"

          "http://dozer.sourceforge.net/dtd/dozerbeanmapping.dtd">

          <mappings>

          <configuration>

          <stop-on-errors>false</stop-on-errors>

          <date-format>MM/dd/yyyy HH:mm</date-format>

          <wildcard>true</wildcard>

          </configuration>

          <mapping>

          <class-a>com.denny_blue.dozerdemo.Book</class-a>

          <class-b>com.denny_blue.dozerdemo.CookBook</class-b>

          <field>

          <a>name</a>

          <b>bookName</b>

          </field>

          <field>

          <a>author</a>

          <b>author</b>

          </field>

          </mapping>

          </mappings>


          <class-a>指定所要復制的源對象,<class-b>復制的目標對象,<a>源對象的屬性名, <b>目標對象的屬性名。wildcard默認為true,在此時默認對所有屬性進行map,如果為false,則只對在xml文件中配置的屬性進行map


          其中,BookCookBook類分別定義如下:


          public class Book{

          public Book(){

            

          public void setAuthor(String author) {

          this.author = author; 

          }

          public String getAuthor() {

          return (this.author); 

          }

          public void setName(String name){

          this.name=name;

          public String getName(){

          return this.name;

          }

          }

          public class CookBook {

          private String bookName;

          private String author;

          public CookBook(){}

          public String getBookName() {

          return (this.bookName); 

          }

          public void setBookName(String bookName) {

          this.bookName = bookName; 

          }

          public String getAuthor() {

          return (this.author); 

          }

          public void setAuthor(String author) {

          this.author = author; 

          }

          }


          以下是測試代碼:


          Book book1=new Book();

          book1.setAuthor("dennis");

          book1.setName("dozer demo");

          DozerBeanMapper mapper=new DozerBeanMapper();

          book2=(Book)mapper.map(book1,com.denny_blue.dozerdemo.Book.class);

          CookBook cookBook=new CookBook();

          List myMappingFiles = new ArrayList();

          myMappingFiles.add("dozerBeanMapping.xml");

          mapper.setMappingFiles(myMappingFiles);

          cookBook=(CookBook)mapper.map(book1,CookBook.class);

          System.out.println("cookBook's name:"+   cookBook.getBookName()+"     cookBook's author:"+

            cookBook.getAuthor());

          }


          通過mapper.setMappingFiles()設置映射文件,可以添加多個配置文件,也可以把所有的映射寫在一個配置文件里面。這里介紹的只是最基本的使用方法,為了實現Dozer的模塊化應用,我專門寫了一個VoPoConverter類簡化Dozer的調用。


          package com.aptech.util;

          import java.util.ArrayList;

          import java.util.List;

          import org.dozer.DozerBeanMapper;

          import org.dozer.Mapper;

          /**

           * VOPO相互轉換的類

           */

          public class VoPoConverter {

          /**

           * VOPO之間相互轉換,將源對象的同名屬性復制目標對象中

           * 前提:源對象和目標對象都必須存在

           * @param src 源對象

           * @param desc 目標對象

           */

          public static void copyProperties(Object src, Object desc){

          if(src == null) return;

          Mapper mapper = new DozerBeanMapper();

          mapper.map(src, desc);

          }

          /**

           * VOPO之間相互轉換,先創建對象,再將源對象的同名屬性復制目標對象中

           * @param <T> 目標類型

           * @param src 源對象

           * @param descType 目標類型

           * @return

           */

          public static <T> T copyProperties(Object src, Class<T> descType){

          if(src == null) return null;

          Mapper mapper = new DozerBeanMapper();

          return mapper.map(src, descType);

          }

          /**

           * 將源集合轉換為目標集合,注意:目標集合是新建的

           * @param <T>

           * @param srcList 源集合

           * @param descType 目標集合中元素的類型

           * @return

           */

          public static <T> List<T> copyList(List srcList, Class<T> descType){

          if(srcList == null) return null;

          List<T> descList = new ArrayList<T>();

          for(Object obj : srcList){

          T t = VoPoConverter.copyProperties(obj, descType);

          descList.add(t);

          }

          return descList;

          }

          }


          類名的意思雖然叫VOPO轉換器,實際上可以應用在任何場合。如果要配合xml配置文件,該類還需要做一些修改。


          ——作者:李贊紅 (lifenote@21cn.com),轉載請保留版權!

          posted on 2011-02-21 11:43 李贊紅 閱讀(3058) 評論(3)  編輯  收藏

          評論

          # re: 基于SSH2框架構建JavaEE應用程序(2)[未登錄] 2011-02-21 14:05 littleJava

          基于SSH2框架構建JavaEE應用程序 1 2 兩部分寫的很精彩。關于po vo的區別我以前也有疑問,但是從來沒有像樓主寫的這么細致,謝謝!后面關于Dozer的介紹可以單獨寫一篇文章,寫在這里就有些喧賓奪主了。  回復  更多評論   

          # re: 基于SSH2框架構建JavaEE應用程序(2) 2011-02-21 16:04 窩窩影視

          不錯 就是看著有點亂!  回復  更多評論   

          # re: 基于SSH2框架構建JavaEE應用程序(2) 2011-02-21 17:31 jackerxff

          好像就是推廣Dozer的,實際完全沒有必要這樣,使PO映射表,VO映射視圖,PO同樣可以new ,PO的映射同樣也可以相當的靈活:查詢使用視圖VO,添加更新使用PO(添加的PO就是new的,更新使用id先get出來,更新后的對象(也是new的)利用反射將值注入get出來的對象中,再update),刪除只需一個id即可,無論PO還是VO都可以比較靈活通過@Transient提供的離線字段。。。。就先簡單說點
            回復  更多評論   


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


          網站導航:
           

          導航

          <2011年2月>
          303112345
          6789101112
          13141516171819
          20212223242526
          272812345
          6789101112

          統計

          常用鏈接

          留言簿(12)

          隨筆檔案(28)

          相冊

          技術友情博客

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 石台县| 绩溪县| 武鸣县| 红安县| 恭城| 潼南县| 南部县| 泸水县| 安化县| 峡江县| 济阳县| 岫岩| 曲松县| 建湖县| 新巴尔虎右旗| 湖州市| 尼勒克县| 云阳县| 天台县| 大关县| 承德县| 休宁县| 布拖县| 札达县| 买车| 绥滨县| 玉山县| 曲阜市| 临夏市| 浑源县| 台北市| 额敏县| 巨鹿县| 涞水县| 广平县| 砚山县| 太康县| 河曲县| 曲靖市| 宣城市| 新竹市|