Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2506921
  • 博文数量: 709
  • 博客积分: 12251
  • 博客等级: 上将
  • 技术积分: 7905
  • 用 户 组: 普通用户
  • 注册时间: 2005-07-17 00:00
个人简介

实现有价值的IT服务

文章存档

2012年(7)

2011年(147)

2009年(3)

2008年(5)

2007年(74)

2006年(431)

2005年(42)

分类: Java

2006-07-24 09:42:13

Struts+Hibernate谈J2EE的数据表示
在 struts+ hibernate 这种结构中,是不应该把Hibernate产生的PO直接传递给JSP的,不管他是Iterator,还是List,这是一个设计错误。

  我来谈谈在J2EE架构中各层的数据表示方法:

  Web层的数据表示是FormBean,数据来源于HTML Form POST

  业务层的数据表示是VO

  持久层的数据表示是PO,其数据来源于数据库,持久层的数据表示例如CMP。在一个规范的J2EE架构中,不同层的数据表示应该被限制在层内,而不应该扩散到其它层,这样可以降低层间的耦合性,提高J2EE架构整体的可维护性和可扩展性。比如说Web层的逻辑进行了修改,那么只需要修改FormBean的结构,而不需要触动业务层和持久层的代码修改。同样滴,当数据库表进行了小的调整,那么也只需要修改持久层数据表示,而不需要触动业务层代码和Web层代码。

  不过由于Hibernate的强大功能,例如动态生成PO,PO的状态管理可以脱离Session,使得在应用了Hibernate的J2EE框架中,PO完全可以充当VO,因此我们下面把PO和VO合并,统称为PO。

  先来谈谈ActionFormBean和持久层的PO之间的重大区别:

  在简单的应用中,ActionFormBean和PO几乎是没有区别,所以很多人干脆就是用ActionFormBean来充当PO,于是ActionFormBean从JSP页面到Servlet控制层再到业务层,然后穿过持久层,最后一直映射到数据库表。真是一竿子捅到了底!

  但是在复杂的应用中,ActionFormBean和PO是分离的,他们也不可能一样。ActionFormBean是和网页里面的Form表单一一对应的,Form里面有什么元素,Bean里面就有什么属性。而PO和数据库表对应,因此如果数据库表不修改,那么PO也不会修改,如果页面的流程和数据库表字段对应关系不一致,那么你又如何能够使用ActionFormBean来取代PO呢?

  比如说吧,用户注册页面要求注册用户的基本信息,因此HTML Form里面包含了基本信息属性,于是你需要一个ActionFormBean来一一对应(注意:是一一对应),每个Bean属性对应一个文本框或者选择框什么的。

  而用户这个持久对象呢?他的属性和ActionFormBean有什么明显不同呢?他会有一些ActionFormBean所没有的集合属性,比如说用户的权限属性,用户的组属性,用户的帖子等等。另外还有可能的是在ActionFormBean里面有3个属性,分别是用户的First Name, Middle Name, Last Name,而在我的User这个持久对象中就是一个 Name 对象属性。

  假设我的注册页面原来只要你提供First Name,那么ActionFormBean就这一个属性,后来我要你提供全名,你要改ActionFormBean,加两个属性。但是这个时候PO是不应该修改滴,因为数据库没有改。

  那么在一个完整的J2EE系统中应该如何进行合理的设计呢?

  JSP(View) ---> Action Form Bean (Module) ---> Action(Control)

  Action Form Bean是Web层的数据表示,它和HTML页面Form对应,只要Web页面的操作流程发生改变,它就要相应的进行修改,它不应该也不能被传递到业务层和持久层,否则一旦页面修改,会一直牵连到业务层和持久层的大面积的代码进行修改,对于软件的可维护性和可扩展性而言,是一个灾难,Actiont就是他的边界,到此为止!

  Action(Web Control) ---> Business Bean ---> DAO ---> ORM --->DB

  而PO则是业务层和持久层的数据表示,它在业务层和持久层之间进行流动,他不应该也不能被传递到Web层的View中去,而ActionServlet就是他的边界,到此为止!

  然后来看一看整个架构的流程:

  当用户通过浏览器访问网页,提交了一个页面。于是Action拿到了这个FormBean,他会把FormBean属性读出来,然后构造一个PO对象,再调用业务层的Bean类,完成了注册操作,重定向到成功页面。而业务层Bean收到这个PO对象之后,调用DAO接口方法,进行持久对象的持久化操作。

  当用户查询某个会员的信息的时候,他用全名进行查询,于是Action得到一个UserNameFormBean包括了3个属性,分别是first name, middle name, last name,然后Action把UserNameFormBean的3个属性读出来,构造Name对象,再调用业务Bean,把Name对象传递给业务Bean,进行查询。

  业务Bean取得Name(注意: Name对象只是User的一个属性)对象之后调用DAO接口,返回一个User的PO对象,注意这个User不同于在Web层使用的UserFormBean,他有很多集合属性滴。然后业务Bean把User对象返回给Action。

  Action拿到User之后,把User的基本属性取出(集合属性如果不需要就免了),构造UserFormBean,然后把UserFormBean request.setAttribute(...),然后重定向到查询结果页面。

  查询页面拿到request对象里面的ActionFormBean,自动调用tag显示之。

  总结:

  Form Bean 是Web层的数据表示,他不能被传递到业务层;PO是持久层的数据表示,在特定情况下,例如Hibernate中,他可以取代VO出现在业务层,但是不管PO还是VO都必须限制在业务层内使用,最多到达Web层的Control,绝不能被扩散到View去。

  Form Bean 和PO之间的数据转化是在Action中进行滴。
================
再贴一篇还是MDA和面向数据表设计的译文,希望对更多人有益:

The Object-Relational Impedance Mismatch

现在设计应用系统时,主要有两个派别:传统的面向数据表设计和现在流行的面向对象设计。

数据库专家(DBA等)开发设计出数据表结果(SQL或使用powerdesign之类工具建模),而应用程序员则写它们的程序代码,这两种任务在过去一直是独立地进行着,互相不干扰,我曾经在Jdon看到一篇帖子,说网易的一个DBA很厉害,写出的数据表结构如何好,这代表着过去的一种数据库系统开发的方式。

这两种工作是没有联系的,因为数据模型只完成数据实体和数据关系,而应用模型只要实现如何操作这些数据,也就是完成SQL语句或存储过程语句。

当面向对象技术诞生后,从数据专家观点来看,他们觉得这一技术对他们影响不大,但是一些数据库专家已经快速地意识到这是软件开发的一场革命,由此加入新的领域,并且这样的群体不断在增长。

非常不幸的是,还有许多数据库专家仍然认为面向对象的分析流程只是一种暂时现象,根本不接受这种新的变化,依然使用过去方式来分析设计数据库。

这两个阵营目前在互相攻击,出现了严重分裂,但是数据库专家正在遭受更严峻的挑战,特别是使用EJB的实体bean或Hibernate来实现数据持久时,很多以前的数据表设计概念要接受挑战,这也是很多项目设计的痛苦根源。

令数据库专家惊慌的是:面向对象技术UML比数据建模技术更加robust鲁棒。并且可以证明:UML是数据模型的父集,很明显,现在天平已经倾向与面向对象设计了。

这里有很多概念误区:许多数据库专家错误地认为类图只不过是带有行为操作的数据模型,他们没有认识到这其中有些细微地区别,他们没有认识到模型行为的复杂性要超过类图。

面向对象和面向数据表的分裂造成很多恶劣的结果:
1. 项目由于未能及时完成而失败。
2. The technical impedance mismatch技术阻抗加剧
3. 数据建模与对象建模相比,无法涵括项目的全部需求设计,增加内部来回反复折腾。

如何愈合这种分裂?
1.项目参与人员必须同时掌握了解对象和数据表技术
2.按照减少耦合、封装分派等概念重构数据表。
===========================
由于Struts已经为我们提供了一个非常好的MVC框架,我们利用Struts开发MVC系统时可以大大加快开发的速度。在开发时可以采用的一个开发流程如下:

1、收集和定义应用需求。
2、基于数据采集和显示的原则定义和开发"屏幕显示"需求 。
3、为每一个"屏幕显示"定义访问路径。
4、定义ActionMappings建立到应用业务逻辑之间的联系。
5、开发满足"屏幕显示"需求的所有支持对象。
6、基于每一个"屏幕显示"需求提供的数据属性来创建对应的ActionForm对象
7、开发被ActionMapping调用的Action对象。
8、开发应用业务逻辑对象 (Bean,EJB,等等)。
9、对应ActionMapping设计的流程创建JSP页面。
10、建立合适的配置文件struts-config.xml , web.xml。
11、开发/测试/部署

具体在使用Struts框架时,对应各个部分的开发工作主要包括:
1、Model部分:采用JavaBean和EJB组件,设计和实现系统的业务逻辑。根据不同的请求从Action派生具体Action处理对象。完成"做什么"的任务来调用由Bean构成的业务组件。创建由ActionForm 的派生类实现对客户端表单数据的封装。
2、Controller部分:Struts为我们提供了核心控制部分的实现。我们只需要配置ActionMapping对象
3、View部分:为了使用Model中的ActionForm 对象,我们必须用Struts提供的自定义标记创建HTML 表单。利用Struts提供的自定义标记库编写用户界面把应用逻辑和显示逻辑分离。Struts框架通过这些自定义标记建立了View和Model之间的联系。Struts的自定义标记还提供了很多定制页面的功能。

同时需要编辑两个配置文件:web.xml和struts-config.xml。
======================================
在用struts进行项目开发的时候,你是用什么来持久化你的数据的呢?(OJB、Castor还是hibernate?)如果没有,我建议你看看我这篇文章,Hibernate是目前开发员普遍都很推崇的ORM,而且自带的文档极其丰富(我为什么没有选择OJB,恐怕这是主要的原因。)
Struts在今年的下半年初推出了它的1.1正式版,标志其在成熟和稳定方面有了更一步的提高。本文正是基于此版本,本来想把struts1.1中的好的特性都用到做一个比较好的例子,由于时间的原因,譬如:tiles,exception hadling,validator,多模块、国际化等等在例子中没有涉及,相关内容请看我在csdn的专栏文章。
(一)前期准备:
a.struts1.1

b.hibernate 2.0

c.eclipse2.1(呵呵,我比较喜欢的一个IDE,此为可选)

d.ant1.5.1

e.jdk1.4
f.tomcat4.1

(相关网站都可以得到其最新版本)
(二)用例说明
很简单的一个例子,关系也不复杂:猫科(Animal.java)和猫(Cat.java)。 前者对后者是一对多的关系,后者对前者是多对一的关系。
第一部分,和hibernate 相关
1.Animal类

package com.iplateau.test.hibernate.persistence;
import java.util.Set;
/**
* Class or Interface Dis cription
* @author $Author:jack$
* @version $ReVision:1.0 $

* $Id:Animal.java 2003-8-4 16:44:02 jack Exp.
*/
public class Animal {
private String id;
private String name;
private Set cats;

public Animal() {
}

/**
* @return
*/
public Set getCats() {
return cats;
}
/**
* @return
*/
public String getId() {
return id;
}
/**
* @return
*/
public String getName() {
return name;
}
/**
* @param set
*/
public void setCats(Set set) {
cats = set;
}
/**
* @param string
*/
public void setId(String string) {
id = string;
}
/**
* @param string
*/
public void setName(String string) {
name = string;
}
}

Cat类:

package com.iplateau.test.hibernate.persistence;
/**
* Class or Interface Dis cription
* @author $Author:jack$
* @version $ReVision:1.0 $

* $Id:Cat.java 2003-8-3 14:33:11 jack Exp.
*/
public class Cat {

private String id;
private String name;
private String sex;
private float weight;
//select name form Cat as cat where cat.name=
private Animal animal;

public Cat() {
}
/**
* @return
*/
public String getId() {
return id;
}
/**
* @return
*/
public String getName() {
return name;
}
/**
* @return
*/
public String getSex() {
return sex;
}
/**
* @return
*/
public float getWeight() {
return weight;
}
/**
* @param string
*/
public void setId(String string) {
id = string;
}
/**
* @param string
*/
public void setName(String string) {
name = string;
}
/**
* @param c
*/
public void setSex(String c) {
sex = c;
}
/**
* @param f
*/
public void setWeight(float f) {
weight = f;
}
/**
* @return
*/
public Animal getAnimal() {
return animal;
}
/**
* @param animal
*/
public void setAnimal(Animal animal) {
this.animal = animal;
}
}
这两个类的代码可以在本文后边的代码包中找到,我之所以在此列出来的原因,想让大家知道,可持久化的类和平常你熟悉的java类对象没有什么不同:属性、get、set方法。
"透明"就"透明"在这里了吧。

Hibernate为每一个可持久的类配备一个xml格式的mapping文件,他们可以通过hibernate自带的或其他的工具生成(反过来也一样:先有mapping文件,然后生成java代码),刚开始学习的时候最好用手写来熟悉。

Animal.hbm.xml






















Cat.hbm.xml




















具体mapping文件的格式以及词法(仔细看还是满简单的),请参照hibernate自带的文档,绝对详细!

好了,通常在完成mapping文件以后,就可以利用hibernate自带的工具生成建表语句,这时要用到ant来帮助你完成。{见附代码包中build.xml},以下为建表片断:
……
……
des cription="Generates the database schema for Mssql server."
depends="compile">
property="hibernate.mappings" pathsep=" "/>














……
……

第二部分,整合struts,运行实例
在第一部分,我们用hibernate对我们的数据对象进行持久化,那么hibernate如何操纵我们的数据的呢?,粗略的说就是创建系统SessionFactory(通常只创建一次),然后实例一个Session,通常我们直接对数据进行操作都是通过Session来完成,它就象jdo中的persistenceManager,而SessionFactory呢,就象jdo的persistenceManagerFactory。两者之间的关系不管是jdo还是hibernate都是相同的。

下面说一下SessionFactory的创建方法,大概有三种方法(详细请看hibernate的手册):通过*.class;通过*.hbm.xml;通过hibernate.cfg.xml;我比较倾向于用最后一种方法,本例就是采用hibernate.cfg.xml来创建SessionFactory以及数据库连接的。

Hibernate.cfg.xml


"">





com.microsoft.jdbc.sqlserver.SQLServerDriver
jdbc:microsoft:sqlserver://yourserver;DatabaseName=yourdatabase;SelectMethod=Cursor
your account
your password
20
net.sf.hibernate.dialect.SybaseDialect







OK,确定了创建方法以后我们就可以应用了,应为SessionFactory通常只创建一次,所以推荐在系统初始化的建立,那么可以利用struts的plugin机制来完成这一任务,见:

Com.iplateau.test.hibernate.InitHibernateDataStore.java
(hibernate手册也推荐在struts中用这种方法创建)

关于struts的扩展机制请参见我的另一篇文章《扩展你的struts》


package com.iplateau.test.hibernate;

import java.io.File;
import java.util.List;
import java.net.URL;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.cfg.Configuration;

import javax.servlet.ServletException;

import org.apache.struts.action.ActionServlet;
import org.apache.struts.action.PlugIn;
import org.apache.struts.config.ModuleConfig;

/**
* Class or Interface Dis cription
* @author $Author:jack$
* @version $ReVision:1.0 $

* $Id:InitHibernateDataStore.java 2003-8-3 14:08:50 jack Exp.
*/
public class InitHibernateDataStore implements PlugIn {

private String _configFilePath = "/hibernate.cfg.xml";
private SessionFactory _factory = null;
static String contextFactory =
"com.sun.jndi.rmi.registry.RegistryContextFactory";

private Context ctx = null;
/* (non-Javadoc)
* @see org.apache.struts.action.PlugIn#destroy()
*/
public void destroy() {
// TODO Auto-generated method stub
try {
if (ctx != null) {
ctx.close();
ctx = null;
}
} catch (NamingException ex) {
ex.printStackTrace();
}
}

/* (non-Javadoc)
* @see org.apache.struts.action.PlugIn#init(org.apache.struts.action.ActionServlet, org.apache.struts.config.ModuleConfig)
*/
public void init(ActionServlet arg0, ModuleConfig arg1)
throws ServletException {
// TODO Auto-generated method stub
Configuration configuration = null;
URL configFileURL = null;
System.out.println("4385738758237482173841847382374823742");
try {
configFileURL =
InitHibernateDataStore.class.getResource(_configFilePath);
configuration = (new Configuration()).configure(configFileURL);
_factory = configuration.buildSessionFactory();

Context ctx = new InitialContext();
ctx.addToEnvironment(
javax.naming.Context.INITIAL_CONTEXT_FACTORY,
contextFactory);

ctx.bind("hibernate_connection_factory", _factory);

} catch (Exception e) {

}
}
}

在创建sessionFactory以后,采用jndi来取得:
SessionFactory sf =
(SessionFactory) inttex.lookup("hibernate_connection_factory");
{见com.iplateau.test.hibernate.DBManager.java}

数据库的中文问题,我采用filter来解决,效果还不错,具体参看web.xml以及com.iplateau.test.hibernate.SetEncodingFilter.java

小节:
总的来说,把struts和hibernate这两种在业内比较推崇的开源技术相结合,在项目开发中不管是从效率上还是易维护上都是完美的结合。
希望从这个简单的例子中给你更多的提示,同时也希望你提出好的建议,通过plateau_t@sina.com和我联系。
附上代码包:

(两星期内有效,过后可能会删除)

参考资源:
hibernate参考手册(hibernate发布包中自带)
hibernate example

Introduction to hibernate


同时和都有很多关于hibernate的讨论。


阅读(1036) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~