基于SSH2框架构建JavaEE应用程序(2)

基于SSH2框架构建JavaEE应用程序(2)

四、数据传输、数据模型与Dozer

数据传输是程序员实现各种功能时刻需要考虑的问题,从数据模型的建立,到数据模型的转换,从数据的合法性验证,到数据类型的转化,我们要时刻小心,精心设计与组织。数据模型与数据传输可简单可复杂,完全取决于设计者的经验与意图,当然,项目的规模也是我们应该考虑的因素,一个小型项目实在没必要将问题复杂化。


我们首先考虑数据从视图(View)传输到数据库(DB)的数据模型变化过程。用户从界面输入数据,此时,数据毫无结构可言,是零散的、无组织的,数据提交到控制器后,一方面考虑到OOP的严谨性,另一方面考虑到数据的封装,会将零散的无组织的数据封装成Value Object(简称VO)对象,VO就是JavaBean,并传送到业务类中,在数据模型比较复杂的情况下,业务类的方法参数和返回值都应该是VO或VO的集合,VO转换成PO(Persistence Object)后传送到DAO,DAO调用Hibernate API持久化数据。简单来说,业务类对外暴露的数据模型是VO,DAO对外暴露的数据模型是PO。


数据从数据库传递到视图层的数据模型变化恰恰相反,Hibernate将数据库中的记录转换成PO,PO传递给业务类后转换成VO,VO被传送到视图层进行显示处理。


以上转换过程如下图:



 

为什么同时需要PO和VO?前面说过,这不是必须的,如果项目比较小,直接使用PO就行了。但PO有如下缺点:


PO反应了数据库的物理模型,向外暴露PO存在一定的风险;

PO过于僵化,无法适应变化莫测的业务需求;

PO在持久化状态下与数据库同步,可能导致数据意外修改;

PO将导致程序缺乏健壮性。


而VO没有PO的缺点,相对而言,VO更加灵活,能适应各种需求的变化,下面是PO和VO的区别:


VO是用new关键字创建,由GC回收的。PO则是向数据库中添加新数据时创建,删除数据库中数据时削除的。并且它只能存活在一个数据库连接中,断开连接即被销毁;

VO是值对象,精确点讲它是业务对象,是存活在业务层的,是业务逻辑使用的,它存活的目的就是为数据提供一个生存的地方。PO则是有状态的,每个属性代表其当前的状态。它是物理数据的对象表示。使用它,可以使我们的程序与物理数据解耦,并且可以简化对象数据与物理数据之间的转换;

VO的属性是根据当前业务的不同而不同的,也就是说,它的每一个属性都一一对应当前业务逻辑所需要的数据的名称。PO的属性是跟数据库表的字段一一对应的;

PO一般只有一个,但对应的VO可能有多个。


我们举一个简单的例子来说明VO与PO的区别:比如要实现用户注册与登陆的功能,在物理模型中创建一个用户表,字段分别为用户ID(标识列)、用户名、密码、注册日期等等,定义PO时应该有这四个字段的映射属性。现在,我们来实现用户注册这一功能,为了接收用户输入的注册信息,必须定义VO,注册用户需要输入的信息有:用户名、密码1、密码2,这正好是VO的属性。而实现用户登陆功能时,用户需要输入的信息只有用户名和密码,所以,该VO的属性只有两个:用户名、密码。可以看出,PO侧重于物理模型,而VO则更关注用户的实际需求。如下图所示:



 


因为数据传输是双向的,所以VO和PO之间存在相互转换的问题,这会给编程带来麻烦,我们总是要通过getter方法取出数据,再通过setter方法给对方属性赋值,在属性很多的情况下,让人深感繁琐,而Dozer工具能简化这个问题。


Dozer(http://dozer.sourceforge.net/)是一个JavaBean映射工具,能实现对象属性值之间的相互赋值,Dozer支持简单类型映射、复合类型映射、双向映射以及递归映射,默认情况下,Dozer能实现类型相同、名字相同的属性之间的赋值,如果属性名不同,则必须在xml(dozerBeanMapping.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。


其中,Book和CookBook类分别定义如下:


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;

/**

 * VO和PO相互转换的类

 */

public class VoPoConverter {

/**

 * VO和PO之间相互转换,将源对象的同名属性复制目标对象中

 * 前提:源对象和目标对象都必须存在

 * @param src 源对象

 * @param desc 目标对象

 */

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

if(src == null) return;

Mapper mapper = new DozerBeanMapper();

mapper.map(src, desc);

}

/**

 * VO和PO之间相互转换,先创建对象,再将源对象的同名属性复制目标对象中

 * @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;

}

}


类名的意思虽然叫VO与PO转换器,实际上可以应用在任何场合。如果要配合xml配置文件,该类还需要做一些修改。


——作者:李赞红 ([email protected]),转载请保留版权!

你可能感兴趣的:(基于SSH2框架构建JavaEE应用程序(2))