`
- 浏览:
103232 次
- 性别:
- 来自:
北京
-
Java EE/J2EE面向对象实战之道
板桥里人 http://www.jdon.com 2006/9/28(转载请保留)
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>
</hibernate-mapping>
在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系统快速有效高质量的解决之道。
分享到:
Global site tag (gtag.js) - Google Analytics
相关推荐
- **面向对象编程(OOP)**:理解类、对象、继承、多态、封装等概念。 - **异常处理**:学习如何使用try-catch-finally块来捕获和处理异常。 - **集合框架**:熟悉List、Set、Map等集合类及其用法。 **2. Swing** ...
- 面向对象编程:类、对象、封装、继承、多态等概念。 - 异常处理:了解try-catch-finally结构以及自定义异常。 - 集合框架:List、Set、Map接口及其实现类,如ArrayList、LinkedList、HashSet、HashMap等。 - ...
- **价值**:通过详细的面试问题解答和实战案例,本书能够帮助读者深入了解Java/J2EE领域的技术和实践,提高面试成功率。 - **适用人群**:适合所有希望提升Java/J2EE技能、准备面试的专业人士,无论初学者还是经验...
Java EE(J2EE)是企业级应用开发的主流平台,它提供了丰富的组件和服务,用于构建分布式、多层的企业级应用程序。SSM架构是Java EE中的一种流行开发框架组合,由Spring、Struts和MyBatis三个核心组件构成,极大地...
1. **Spring框架**:作为J2EE最广泛使用的框架之一,Spring提供了一个全面的应用开发框架,包括依赖注入、AOP(面向切面编程)、MVC(模型-视图-控制器)和数据访问。Spring Boot简化了Spring应用程序的启动和配置,...
随着技术的发展,J2EE演进为了Java EE(Java Platform, Enterprise Edition),但在早期的学习资料中,J2EE仍然是一个非常重要的概念。 ### Servlet简介 Servlet是一种服务器端的小程序,用于扩展服务器的功能。它...
1. **Spring框架**:Spring是J2EE领域最流行的轻量级框架之一,提供了依赖注入(DI)和面向切面编程(AOP)等功能。Spring MVC是其Web层的实现,用于构建RESTful Web服务。此外,Spring Boot简化了Spring应用的启动...
【郭克华J2EE高级框架实战教学视频05】是一部深入讲解J2EE高级技术的教程,由知名IT讲师郭克华主讲。在这个视频系列中,郭克华老师将带领我们探索J2EE平台上的核心框架及其实际应用,帮助开发者提升在企业级Java开发...
总之,《轻量级J2EE企业应用实战》是一本非常适合初学者和有经验的Java开发者深入理解并掌握轻量级Java EE开发的书籍。通过阅读本书,你将能够利用Struts、Spring和Hibernate的强大力量,提升自己的开发技能,并在...
Java是一种广泛使用的面向对象的编程语言,以其平台独立性、丰富的类库和高效性能而闻名。J2EE(Java 2 Platform, Enterprise Edition)是Java技术在企业级应用中的核心框架,它提供了一整套服务,包括Web组件、EJB...
综上所述,"郭克华_J2EE高级框架实战教学视频PPT"涵盖了J2EE开发的多个重要方面,对于希望在企业级Java开发领域深造的程序员来说,是一份非常有价值的参考资料。通过学习,你可以更好地理解和掌握J2EE框架的精髓,...
《轻量级 J2EE 企业应用实战--Struts+Spring+Hibernate 整合开发》是一本深入探讨Java EE(以前称为J2EE)轻量级框架集成的书籍,主要聚焦于Struts、Spring和Hibernate这三大开源技术的协同工作。这本书的源代码提供...
Spring框架是Java EE开发中的核心组件,它是一个全面的后端应用程序框架,提供了依赖注入(DI)、面向切面编程(AOP)、事务管理等多种功能。Spring的IoC容器使得开发者能够轻松管理和配置对象,而AOP则允许我们编写...
在IT行业中,Java Enterprise Edition(J2EE,现在被称为Java EE)是一个广泛使用的开源框架,用于构建企业级的分布式应用程序。这个"J2EE面试实战测试1.rar"压缩包文件显然是针对那些寻求J2EE相关职位的求职者设计...
《轻量级J2EE企业应用实战源码 5 6 8 9》这个压缩包文件包含了四个关键部分:05、06、08和09,这些子文件很可能是按照章节或者主题进行组织的,分别代表了J2EE开发中的不同阶段或技术点。J2EE(Java Platform, ...
- **Spring**是一个轻量级的Java EE应用框架,提供了强大的依赖注入和面向切面编程功能。Spring的核心是IoC容器,它可以管理对象的生命周期和依赖关系。 - 本书中的Spring主要用于管理和配置Struts与Hibernate等...
《轻量级Java EE企业应用实战》是李刚编著的第三版书籍,该书主要针对Java EE在企业级开发中的实践应用进行深入探讨。Java EE(以前称为J2EE)是一个用于构建分布式、多层的企业级应用程序的平台,它提供了一系列的...