一个真正面向对象的JavaEE或J2EE系统,应该是一个围绕领域模型的多层架构,以面向对象OO思维进行领域模型提炼和重构,继续以OO思维进行表现层和持久层的配置实现,才能寻找到一条Java系统快速有效高质量的解决之道。
OO思维
经常看到不少人抱怨Java EE/J2EE中配置太复杂,烦琐,不简单易学,其实所谓简单易学是取决于你是否有OO思维方式。
分层架构是面向对象OO在企业软件中应用的标志,目前一个企业软件系统包括表现层、业务层和持久层,那么分层架构和OO关系是如何?
表现层的界面表单中通常是一些离散数据,也就是单个字段数据,通过Struts等框架提供ActionForm以及标签库,将这些单个字段数据封装起来和业务层的Domain Model进行了映射,因此,表现层的主要编程工作就是映射配置。
持久层是将Domain Model对象保存到数据库中,过去使用JDBC,我们要逐个打开这些Model对象,然后每个字段逐个 保存到数据库中,如果说表现层框架是实现离散数据封装,那么持久层实现的是反方向:拆封。Hibernate是一个持久层O/R mapping框架, 也就是在对象和关系数据库之间进行映射的框架,EJB的CMP也是类似道理,因此,持久层的主要编程工作也是映射配置。
表现层和持久层这种配置工作就如同打包邮寄一样:你首先要将你的单件用一个箱子包装起来,达到目的地,这个箱子被打开,单件被逐步取出。表现层和持久层这样做的目的是保证中间业务层完全面向对象,保证业务层完全是和一个个对象模型打交道。
在一个真正面向对象的系统中,表现层和持久层是为了将非对象化的数据转为对象。因此,在先进的JavaEE/J2EE架构中,表现层和持久层的主要工作就是配置工作,而且主要是映射mapping的配置。
下面的问题就是:如何解决映射配置简单而且易用,如果拥有正确的指导配置的思维,那么配置工作就容易简单多, 否则,就倍感配置复杂。 那些感觉Java配置复杂的人其实他并没有完整的OO思维。为什么这么说呢?以ORM(Hibernate)配置简易方式说明:
配置的简要之道
首先,配置是映射XML配置,顾名思义,也就是在两者之间做协调,牵线搭桥,说白了,就是做红娘,但和做红娘又有些区别,做红娘可以要求双方做些改变,互相迁就,但是做映射配置,则不能这样,因为那样做就可能做出和需求要求不一样的东西。
配置的简要之道就是:围绕对象模型进行配置;而不是围绕数据表进行配置。
以持久层映射配置来说:存在Domain Model对象和关系数据表,如果感觉在两者之间配置映射很困难,双方做些改变,但是有可能 需求不答应,你一旦为协调而作出的改变可能偏离需求实现的目标,最后作出的系统面貌全非,根本不是客户所需要的。
那么怎么办?很显然,紧扣需求,反映需求的那一方坚决不要变动,那么Domain Model和关系数据表哪一方反映需求呢?按照OO分析,当然 是Domain Model,Model对象我们是依据Evans Model等模型驱动设计MDD概念设计出来,他们是需求的代表。
很显然,我们的映射配置必须顺着Model对象这个思维来配,对于名词式的Model,关联无外乎是其主要关系,当然还有继承,因此,象Hibernate 这些映射配置语法也是面向这些主要对象关系的。
表现层配置也是同样的道理,需要将Domain Model配置成界面表单,在实际中,我们有可能采取的是通过界面收集需求,因此,这个映射配置过程也是考验Model对象是否提炼正确与否,有可能发现Model不能实现一些界面需求功能,这时反过来必须修改我们的Model,而不是仅仅在表现层这个技术层面做些补救措施就糊弄过去。
Java EE/J2EE系统开发过程 敏捷的迭代是必然的。没有一个天才能够一步到位提炼出兼顾界面和数据表以及需求的统一模型出来。
总之,完成一个真正面向对象的Java EE/J2EE系统,必须抓住领域建模和具体框架熟练配置两点,只有这样才能保证Java项目成功实施。最关键的是提炼出反映出业务系统的领域模型:Domain Model,完成业务建模后,就是依赖Struts/Hibernate等配置分配将Model 映射到界面和数据库,其实就是将业务模型移植到计算机领域并能够正确运行。
高聚合和低关联
如果一个系统都被设计成相互没有任何不包含的单个对象,很显然是不能正确反映实际需求的,万事万物都是有其部分组成的,例如窗户由玻璃和框架组成,人是由胳膊 腿等身体部分组成,现实世界中,事物之间总是存在关系,聚合和组成是最常见的。
例如订单,一个订单Orders中由客户名称和地址,订购的产品品种和数量,客户名称和地址我们可以抽象为Customer来代表,产品我们使用Product来代表,由于一个订单中可能订购了多个产品,很显然,一个订单对象中应该有多个Product对象,而且每个Product的数量不一样,我们将Product和其数量再抽象包装成OrderLine订单条目对象,这样,订单中包含多个订单条目,而且订单条目只有依赖某个订单,是其组成部分,是一种强聚合关系,不是普通的聚合或关联关系。而Customer和Order之间是一种聚合关系,如果订单没有客户信息,就不成为订单了。
下面再以用户User这个对象为例,用户User可能拥有很多动态属性,一些属性需要运行时动态确定,用户和动态属性是一个整体和部分的聚合关系;每个用户都必然属于某个部门,因此,用户和部门属性对象之间也是一个整体和部分的聚合关系,这两种聚合关系不同之处在于:前者一个用户可能有多个动态属性,是1:N关系;部门Dept和用户User之间是1:N关系,一个部门中可能有多个用户,反过来说,对于用户User来说:它和部门Dept之间是N:1关系。
通过以上建模过程,我们基本搞清楚两件事:这个领域中存在哪些模型对象?按照Evans的DDD理论,哪些是实体,哪些是值对象;然后我们必须搞清楚那些聚合关系,他们是整体部分的关系,用来共同组成一个完整对象的。
持久层Hibernate聚合实现
在持久层我们需要做的主要工作就是将上述Domain Model 进行持久化映射配置,以User为例,User是一个实体,我们配置User.hbm.xml如下:
<hibernate-mapping>
<class name="sample.model.User" table="testuser">
<id name="userId" type="java.lang.String" >
<generator class="assigned"/>
</id>
<property name="username" type="java.lang.String">
<column name="name" />
</property>
<!--表示和部门Dept之间是一种多对一关系 -->
<many-to-one cascade="save-update" name="dept"
class="sample.model.Dept" column="categoryId" />
<!-- 表示和用户属性UserPropperty之间是一种1对多关系-->
<bag name="userProps" inverse="true" cascade="all" >
<key column="userId" />
<one-to-many class="sample.model.UserProperty" />
</bag>
</class>
在User的映射配置文件中,我们很自然地表达了上节Model之间的聚合关系,通过Hibernate配置,我们将模型对象之间的关系可以持久化保存到数据库中了,也就是可以永久维持这种关系,实际上,现实世界中也是这样的,部分和整体的关系是一直存在,除非这个整体这个对象不存在,而且修改部分对象内部值,必须通过整体这个对象。
在user配置中,我们并没有去做任何关系数据表testuser的设计和设定,因为我们知道,当User.hbm.xml配置完成后,这个J2EE系统部署发布到J2EE容器中时,Hibernate会根据这个配置自动创建数据表testuser,数据表的建立已经是一个部署调试阶段的、技术层面的具体工作。
Hibernate重要的父子关系
Hibernate在处理User和UserProperty这样一对多的父子关系时,具体实现起来要有一些具体细节必须注意,而且Hibernate2和Hibernate3两个版本是不一样的:
当我们需要只通过一句话session.save(user)或session.update(user)就能完成User和它其中多个Userproperty都能自动保存或更新时(必须指定cascade="all" 或save-update),尤其是update(user)更新时,其子集合userProps属性中可能有一些Userproperty是修改过的,一些Userproperty则是新增的,对于新增要使用insert语句;而对于修改则使用update语句,当我们笼统地调用一句update(user)时,那么Hibernate是如何判断这个user中子集合中哪些是修改?哪些是新增的?
Hibernate是通过主键来判断的,也就是说,通过UserProperty的主键来判断该对象是修改?还是新增。最关键的是:这个主键必须由Hibernate自动产生,如果你想自己指定子对象UserProperty主键,那么就有可能很多麻烦,这个麻烦是出其的麻烦,无法判断具体原因。所以,在简单方便的道路上迈错一步就是万丈深渊。下面是UserProperty的映射配置:
<hibernate-mapping>
<class name="sample.model.UserProperty" table="userprops">
<id name="propId" type="java.lang.String" >
<generator class="uuid.hex"/><!-- 不能为assigned-->
</id>
<property name="name" />
<property name="value" />
<!-- 为提升性能而设定 -->
<many-to-one name="user" column="userId" not-null="true"/>
</class>
</hibernate-mapping>
注意:以上配置只适合Hibernate 3.0以上版本,如果是Hibernate 2,那么必须在 :
<id name="propId" type="java.lang.String" >
中加入unsaved-value="null",而且这个值是null还是0或-1,取决你的主键类型:
<id name="propId" type="java.lang.String" unsaved-value="null">
是不是感觉Hibernate2太麻烦了!在Hibernate3中,就没有这个规定了,所以,如果当初使用Hibernate2来实现J2EE的oo简洁实现之道,还存在技术上的困难和难点。
Hibernate2和Hibernate3在处理父子关系上,还有一个不同就是lazy设定上:Hibernat2缺省lazy是false,当通过load将User获取以后,在session关闭以后,你可以直接通过user.getUserProps()方法获得其中子集合;而Hibernate3则不行了,缺省lazy是true,在session关闭情况下,只有两种方式获得子集合:
1. Open session in view,也就是在表现层一直打开持久层的session,这不但违背分层不干扰原则,而且造成数据库连接一直打开,一旦出错,有可能造成内存泄漏死机等问题。
2.在load父对象User时,调用Hibernate.initialize(user.getUserProps());强行装载所有的子对象,这样问题是:我们再也无法通过简单一句load生成父对象User及其所有内部部分,而无须照顾其内部关系。
板桥实践中总结方法是:根据当初EJB CMP的读取模式,采取JDBC来读取整个User及其部分子集合,缺点也是必须在Dao语句中打开User(破坏封装),根据其内部结构从数据库中获取数据,这样的好处是:我们可以使用统一的Hibernate模板来进行任何一个模型的持久化(不必为每个模型写一套DAO实现类),而无须关心其内部结构了。
注意:Spring+Hibernate采取的是Open session in view方案,这也是这种架构在系统复杂时发生性能问题一个原因,J道性能板块有多个这样的求救贴。
使用Hibernate映射配置另外一个注意点就是:使用双向关系可以提高性能,但是Evans DDD告诉我们,建模时尽量搞 单向关系,不要用双向,这两者有矛盾之处,实际中,我们如果使用Hibernate作为持久层框架,那么就采取双向,性能很重要啊,否则后果很严重,这种设计和性能不匹配也是目前面向对象领域需要解决的另外一个问题。
通过在子对象UserPropery配置中引入many-to-one ,然后在父对象User配置中规定inverse="true" 来实现双向,Hibernate会通过和insert或update一条SQL语句完成关系设定。
表现层Struts聚合实现
前面我们完成了Hibernate的映射配置,下面是表现层的映射配置,这是使用标签库来实现,我们使用Struts的标签库来实现:在界面主要实现下图效果:
当进行用户User资料增删改查时,需要一个如图录入页面,部门是通过下来菜单选择,用户属性UserPropery是通过一行行属性名称和属性值输入的,主要是在user.jsp中完成:
<html:form action="/userSaveAction.do" method='post'>
<html:hidden property="action" />
<!-- 下拉菜单选择部门,通过使用Struts的Action串联,产生deptListForm新ActionForm-->
<html:select property="dept.deptId" >
<logic:notEmpty name="deptListForm" >
<html:optionsCollection name="deptListForm" property="list" value="deptId" label="name"/>
</logic:notEmpty>
</html:select>
<br>
UserId:<html:text property="userId" />
<br>
Username:<html:text property="username" />
<table>
<tr><td>属性Id</td><td>属性名称</td><td>属性值</td></tr>
<tr><td>
<html:hidden property="userProp[0].propId" />
</td><td>
<html:text property="userProp[0].name" />
</td><td>
<html:text property="userProp[0].value" />
</td></tr>
<tr><td>
<html:hidden property="userProp[1].propId" />
</td><td>
<html:text property="userProp[1].name" />
</td><td>
<html:text property="userProp[1].value" />
</td></tr>
<tr><td>
<html:hidden property="userProp[2].propId" />
</td><td>
<html:text property="userProp[2].name" />
</td><td>
<html:text property="userProp[2].value" />
</td></tr>
</table>
<br><input type='submit' value='submit'></input>
</html:form>
相应的UserActionForm和User Model内容差不多,不同之处:为接受多个动态属性的输入,需要设定一个特定的方法:
public class UserForm extends ModelForm {
.....
public UserProperty getUserProp(int index) {
return (UserProperty)((List)userProps).get(index);
}
.....
}
增删改查和批量查询根据JdonFramework的简化可迅速配置实现,这里不再描述,整个项目的代码结果如下图:也就是10个类左右,而且都是和业务有关,简要,扣主题,整个案例代码是免费自由下载,作为JdonFramework应用源码下载之一的sample。
总结
一个真正面向对象的JavaEE或J2EE系统,应该是一个围绕领域模型的多层架构,以面向对象OO思维进行领域模型提炼和重构,继续以OO思维进行表现层和持久层的配置实现,才能寻找到一条Java系统快速有效高质量的解决之道。
来源:http://developer.51cto.com/art/200812/99404.htm
分享到:
相关推荐
JPA是Java EE中用于对象关系映射(ORM)的标准API,它的出现是为了简化数据库操作,让开发者可以使用面向对象的方式来处理数据。JPA通过提供一套API和元数据(如注解或XML),允许将Java对象映射到数据库表,从而...
《Java EE核心框架实战》是一本专为Java开发者设计的进阶教程,旨在深入解析企业级应用开发中广泛采用的SSM(Spring、Spring MVC、MyBatis)和SSH(Struts、Spring、Hibernate)两大主流框架。这些框架是Java EE世界...
《轻量级Java EE企业应用实战(第4版)》这本书深入探讨了Struts 2、Spring 4和Hibernate这三大框架的整合开发,旨在帮助读者掌握如何在实际项目中高效构建Java EE应用。SSH(Struts 2、Spring、Hibernate)是Java ...
《轻量级Java EE企业应用实战 第3版》是一本深入浅出地讲解如何构建高效、灵活的企业级应用的书籍。源代码作为辅助学习的重要部分,可以帮助读者更好地理解和实践书中介绍的技术和概念。在这个压缩包中,我们主要...
《轻量级Java EE企业应用实战(第4版)》是一部深入浅出的教程,旨在帮助读者掌握在实际开发环境中运用Java EE技术构建轻量级企业应用的方法。这本书的光盘文件包含了书中前三个章节的示例代码,这些代码是学习和...
在《轻量级Java EE企业应用实战第三版》中,第二章主要探讨了Java EE的基础概念和核心组件,以及如何构建轻量级的企业级应用程序。本章源码提供了实际操作的示例,帮助读者深入理解Java EE开发的关键技术。下面我们...
4. **JPA(Java Persistence API)与Hibernate**:JPA是Java EE规范中用于对象关系映射(ORM)的API,Hibernate是其流行的实现之一,允许开发者用面向对象的方式来操作数据库。 5. **CDI(Contexts and Dependency ...
《轻量级Java EE企业应用实战第4版》是一本深度探讨如何使用Struts2、Spring4和Hibernate进行集成开发的书籍。这本书的核心是讲解如何在实际的企业环境中,利用这些轻量级框架构建高效、可扩展的应用程序。下面将...
### Java EE 实战教程知识点概览 #### 一、Java EE 技术栈与应用场景 - **Java EE**(Java Platform, Enterprise Edition)是用于构建可伸缩且健壮的企业级应用程序的标准平台。它提供了丰富的API和服务,如**JSF*...
掌握Java编程基本语法,用Java编写程序来解决一些实际问题,赋予学 员面向对象思想,充分理解并运用Java面向对象思想来进行程序开发, 实现开发者从过程时编程到面向对象编程的转变,实现Java编程完整化 ,理解多...
Hibernate是Java领域的一款流行的对象关系映射(ORM)框架,它简化了数据库操作,使得开发者可以使用面向对象的方式来处理数据库事务,而无需关心底层SQL语句。Hibernate通过配置文件或注解将Java对象与数据库表进行...
本资源是《轻量级Java EE企业应用实战》第四版的第8章源代码,主要关注轻量级Java EE技术在实际项目中的应用。由于文件大小限制,源代码被分成了多个压缩包进行上传。本压缩包包含的是"lib"目录,通常这个目录下存放...
《轻量级Java EE企业应用实战 第3版》是一本深度探讨Java EE开发技术的书籍,专注于Struts 2、Spring 3和Hibernate这三大框架的整合应用。这本书旨在帮助开发者掌握如何在实际项目中有效地集成这三个流行的技术,...
【轻量级Java_EE企业应用实战_Struts2_Spring3_Hibernate整合开发】这个主题主要涉及的是Java EE领域中的三大框架——Struts2、Spring3和Hibernate的集成应用,这是构建现代Java后端系统的一种常见方式。这些框架...
《轻量级Java EE企业应用实战_Struts 2_Spring3_Hibernate(第三版)》是一本深入浅出地介绍如何构建高效、可扩展的企业级应用程序的书籍,它结合了Struts 2、Spring 3和Hibernate这三大流行框架,为Java EE开发者...
### 轻量级Java EE企业应用实战(第4版) #### 一、概述 《轻量级Java EE企业应用实战(第4版)》是一本深入讲解如何使用Struts 2、Spring 4与Hibernate进行整合开发的专业书籍。该书不仅覆盖了这三种框架的基本...
Hibernate则是Java EE应用中的持久层神器,它允许开发者以面向对象的方式操作数据库,通过配置文件或注解定义实体类与数据库表的映射。Hibernate支持多种数据库,且提供了丰富的查询语言HQL和Criteria API,使得SQL...
3. **JPA与Hibernate**:JPA(Java Persistence API)是JAVA EE中的ORM(对象关系映射)标准,而Hibernate是其常见的实现,它们使得Java对象可以直接操作数据库,简化了数据访问层的开发。 4. **Spring框架**:...
《轻量级Java EE企业应用实战(第三版)》是一本深入浅出的Java EE开发指南,旨在帮助开发者掌握在企业环境中构建高效、轻量级的应用程序。第四章的主题通常会涉及Java EE的核心技术,如Servlet、JSP、JDBC以及...
《轻量级Java EE企业应用实战(第三版)》是由著名IT专家李刚撰写的一本经典书籍,专注于讲解如何在企业环境中高效地运用Java EE技术。这本书的第一部分为我们揭示了Java EE开发的核心概念和实践技巧,是理解轻量级...