10.2 域DTO
域模型是指从业务模型中抽取出来的对象模型,比如商品、仓库。在J2EE中,最常见的域模型就是可持久化对象,比如Hibernate中的PO、EJB中的实体Bean。
在分布式系统中,域模型完全位于服务器端。根据持久化对象可否直接传递到客户端,域对象可以分为两种类型:一种是服务器端的持久化对象不可以直接传递到客户端,比如EJB中的实体Bean是不能被传递到客户端的;一种是持久化对象可以直接传递到客户端,比如Hibernate中的PO变为detached object以后就可以传递到客户端。
EJB中的实体Bean不能直接传递到客户端,而且实体Bean不是一个简单的JavaBean,所以也不能通过深度克隆(deep clone)创造一个新的可传递Bean的方式产生DTO。针对这种情况,必须编写一个简单的JavaBean来作为DTO。
下面是一个系统用户的实体Bean的代码:
abstract public class SystemUserBean implements EntityBean
{
EntityContext entityContext;
public java.lang.String ejbCreate(java.lang.String userId)
throws CreateException
{
setUserId(userId);
return null;
}
public void
ejbPostCreate(java.lang.String userId)
throws
CreateException
{
}
public void ejbRemove() throws RemoveException
{
}
public abstract void setUserId(java.lang.String userId);
public abstract void setName(java.lang.String name);
public abstract void setPassword(java.lang.String password);
public abstract void setRole(java.lang.Integer role);
public abstract java.lang.String getUserId();
public abstract java.lang.String getName();
public abstract java.lang.String getPassword();
public abstract java.lang.Integer getRole();
public void ejbLoad()
{
}
public void ejbStore()
{
}
public void ejbActivate()
{
}
public void ejbPassivate()
{
}
public void unsetEntityContext()
{
this.entityContext = null;
}
public void setEntityContext(EntityContext entityContext)
{
this.entityContext = entityContext;
}
}
根据需要我们设计了如下的DTO:
public class SystemUserDto implements Serializable
{
private String userId;
private String name;
private String password;
private Integer role;
public void setUserId(String userId)
{
this.userId = userId;
}
public String getUserId()
{
return userId;
}
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
public void setPassword(String password)
{
this.password = password;
}
public String getPassword()
{
return password;
}
public void setRole(Integer role)
{
this.role = role;
}
public Integer getRole()
{
return role;
}
}
为了实现DTO的生成,这里还需要一个将实体Bean转换为一个DTO的工具,我们称其为DTOAssembler:
public class SystemUserDtoAssembler
{
public static SystemUserDto createDto(SystemUser systemUser)
{
SystemUserDto systemUserDto = new SystemUserDto();
if (systemUser != null)
{
systemUserDto.setUserId(systemUser.getUserId());
systemUserDto.setName(systemUser.getName());
systemUserDto.setPassword(systemUser.getPassword());
systemUserDto.setRole(systemUser.getRole());
}
return systemUserDto;
}
public static SystemUserDto[] createDtos(Collection systemUsers)
{
List list = new ArrayList();
if (systemUsers != null)
{
Iterator iterator = systemUsers.iterator();
while (iterator.hasNext())
{
list.add(createDto((SystemUser) iterator.next()));
}
}
SystemUserDto[] returnArray = new SystemUserDto[list.size()];
return (SystemUserDto[]) list.toArray(returnArray);
}
}
为一个实体Bean产生DTO是非常麻烦的事情,所以像JBuilder这样的IDE都提供了根据实体Bean直接生成DTO类和DTOAssembler的代码生成器。
相对于重量级的实体Bean来说,使用Hibernate的开发人员则轻松多了,因为Hibernate中的PO就是一个普通的JavaBean对象,而且PO可以随时脱离Hibernate被传递到客户端,不用进行复杂的DTO和DTOAssembler的开发。不过缺点也是有的,当一个PO脱离Hibernate以后如果客户端访问其并没有在服务器端加载的属性的时候就会抛出惰性加载的异常,而如果对PO不采用惰性加载的话则会导致Hibernate将此PO直接或者间接关联的对象都取出来的问题,在有的情况下这是灾难性的。在案例系统中是使用DTOGenerator的方式来解决这种问题的。
无论是哪种方式,客户端都不能直接访问服务器端的域模型,但是客户端却希望能和域模型进行协作,因此需要一种机制来允许客户端像操纵域模型一样操作DTO,这样客户端可以对DTO进行读取、更新的操作,就好像对域模型做了同样的操作一样。客户端对DTO进行新增、修改、删除等操作,然后将修改后的DTO传回服务器端由服务器对其进行处理。对于实体Bean来讲,如果要处理从客户端传递过来的DTO,就必须编写一个DTODisassembler来将DTO解析为实体Bean:
public class SystemUserDtoDisassembler
{
public static SystemUser fromDto(SystemUserDto aDto)
throws ServiceLocatorException, CreateException,
FinderException
{
SystemUser systemUser = null;
ServiceLocator serviceLoc = ServiceLocator.getInstance();
SystemUserHome systemUserHome = (SystemUserHome) serviceLoc
.getEjbLocalHome("SystemUserHome");
boolean bFind = false;
try
{
systemUser = systemUserHome.findByPrimaryKey(aDto.getPkId());
bFind = (systemUser != null);
} catch (FinderException fe)
{
bFind = false;
}
if (bFind != true)
systemUser = systemUserHome.create(aDto.getPkId());
systemUser.setName(aDto.getName());
systemUser.setPassword(aDto.getPassword());
systemUser.setRole(aDto.getRole());
return systemUser;
}
}
Hibernate在这方面的处理就又比实体Bean简单了,主要把从客户端传来的DTO重新纳入Hibernate的管理即可,唯一需要注意的就是版本问题。
(1) 使用域DTO会有如下好处:
l 域模型结构可以在一次网络调用中复制到客户端,客户端可以读取、更新这个DTO而不需要额外的网络调用开销,而且客户端还可以通过将更新后的DTO回传到服务器端以更新数据。
l 易于实现快速开发。通过使用域DTO可以直接将域模型在层间传输,减少了工作量,可以快速地构建出一个应用。
(2) 但它也有如下的缺点:
l 将客户端和服务器端域对象耦合在一起。如果域模型变了,那么相应的DTO也会改变,即使对于Hibernate这种PO、DTO一体的系统来说也会同样导致客户端的代码要重新编译或者修改。
l 不能很好地满足客户端的要求。客户端可能只需要域对象的20个属性中的一两个,采用域DTO则会将20个属性都传递到客户端,浪费了网络资源。
l 更新域对象很烦琐。客户端对DTO可能做了很多更新或者很深层次的更新,要探查这些更新然后更新域对象是很麻烦的事情。






