Hibernate(见 参考资料)等工具大大简化了 Java 对象与其数据库存储之间的映射;尤其是,很容易给 Java 类加上注解,从而指定对象持久化的方式。开发人员不再需要编写大量数据库集成代码。Hibernate 解决了持久化问题;但是,仍然需要创建 Web 页面来处理这些元素。对于中等规模的 Web 应用程序,典型的开发过程可能是这样的:开发人员首先编写表示某个领域模型的 Plain Old Java Object(POJO),然后创建不同的事务和 Web 用户界面。一部分模型元素常常涉及非事务性数据。客户、国家、地区、职员和公司是业务模型的典型元素,它们由操作员维护。
为什么不生成一个 Web 表示层,让它根据带注解的 bean 创建、添加、列出、删除和搜索这些元素呢?为什么不让这个表示层产生友好的 Ajax 用户体验呢?这就是 jpa2web 工具的主要目标,它采用以下处理流程:
- 输入:带注解的 POJO bean(和可选的模板)。
- 输出:一个 Ajax Web 应用程序,它可以处理模型元素并进行持久化。
- 使用的技术:FreeMarker + ZK + Hibernate(关于这些技术的更多信息,参见 参考资料 中的链接)。
|
这个工具主要使用注解驱动的编程方式指定 ORM 映射。可以重用其中的许多注解来定义 Web 界面或创建可修改的原型。
下面几节解释如何使用 jpa2web 把不同复杂程度的 bean 映射到 Ajax Web 用户界面。接下来,说明 jpa2web 算法的工作原理和一些基本指令。最后,描述 jpa2web 的适用范围和以后的改进。
本文使用一个领域模型示例,一些读者可能很熟悉这个模型。它借鉴了 Bill Burke 和 Richard Monson-Haefel 所写的 Enterprise JavaBeans, 3.0 中的领域模型示例(参见 参考资料 中的链接)。这个领域模型包含一个船只管理类 Ship.java(见 清单 1),这个类是最简单的 POJO:其中只有基本数据类型的成员。
package
com.titan.domain;
import
javax.persistence.
*
;
@Entity
public
class
Ship
implements
java.io.Serializable
...
{
privateintid;
privateStringname;
privatedoubletonnage;
@Id@GeneratedValue
publicintgetId()...{returnid;}
publicvoidsetId(intid)...{this.id=id;}

publicStringgetName()...{returnname;}
publicvoidsetName(Stringname)...{this.name=name;}

publicdoublegetTonnage()...{returntonnage;}
publicvoidsetTonnage(doubletonnage)...{this.tonnage=tonnage;}

publicStringtoString()...{returnname;}
}
如果希望让 Ship.java 的实例与一个数据库保持同步,那么基本上需要两个用户界面:一个用来添加(或编辑)船只信息,另一个用来列出船只。第一个界面是一个用来输入船只信息(名称和吨位)的表单。第二个界面列出现有的船只及其字段,供用户选择和编辑(见 图 1):
图 1 显示 jpa2web 生成的表单。注意,有三个图标:一个用来创建新的船只,一个列出所有船只(见 图 2),另一个删除已经持久化的船只。单击 OK 按钮,就会把船只信息持久化到数据库中。注意,因为 ID 字段是一个 GeneratedValue,所以它看起来被禁用了。
图 2 显示生成的第二个 Web 页面,这个页面列出现有的船只。用户可以单击一个船只,这时会显示前一个表单,让用户编辑船只的详细信息。
如果对象引用其他类的对象,那么情况会复杂一些。Cabin.java 就属于这种情况,这个类与 Ship.java 之间存在多对一关系,它也有简单的属性(见 清单 2):
package
com.titan.domain;
import
javax.persistence.
*
;
@Entity
public
class
Cabin
implements
java.io.Serializable
...
{
privateintid;
privateStringname;
privateintbedCount;
privateintdeckLevel;
privateShipship;
@Id@GeneratedValue
publicintgetId()...{returnid;}
publicvoidsetId(intid)...{this.id=id;}

publicStringgetName()...{returnname;}
publicvoidsetName(Stringname)...{this.name=name;}

publicintgetBedCount()...{returnbedCount;}
publicvoidsetBedCount(intcount)...{this.bedCount=count;}

publicintgetDeckLevel()...{returndeckLevel;}
publicvoidsetDeckLevel(intlevel)...{this.deckLevel=level;}
@ManyToOne
publicShipgetShip()...{returnship;}
publicvoidsetShip(Shipship)...{this.ship=ship;}

publicStringtoString()...{
returnname;
}
}
jpa2web 生成一个与 Ship.java 的表单相似的表单,但是提供了将给定的船舱与某一船只联系起来的方法。这需要一个按钮,这个按钮会打开一个模态对话框,让用户选择船只。图 3 显示生成的表单,图 4 显示用来选择船只的模态对话框。ZK 可以很轻松地处理模态对话框,所以这种技术非常方便。
现在看一个更复杂的示例。如果类与其他类形成不同的关系,那么怎么办?例如 Customer.java 类(见 清单 3)。这个类与其他类(分别是 CreditCard、Phone 和 Reservation)同时形成一对一、多对多和一对多关系。
package
com.titan.domain;
import
javax.persistence.
*
;
@Entity
public
class
Customer
implements
java.io.Serializable
...
{
privateintid;
privateStringfirstName;
privateStringlastName;
privatebooleanhasGoodCredit;
privateAddressaddress;
privateCollection<Phone>phoneNumbers=newArrayList<Phone>();
privateCreditCardcreditCard;
privateCollection<Reservation>reservations=
newArrayList<Reservation>();
@Id@GeneratedValue
publicintgetId()...{returnid;}
publicvoidsetId(intid)...{this.id=id;}

publicStringgetFirstName()...{returnfirstName;}
publicvoidsetFirstName(StringfirstName)...{this.firstName=firstName;}

publicStringgetLastName()...{returnlastName;}
publicvoidsetLastName(StringlastName)...{this.lastName=lastName;}

publicbooleangetHasGoodCredit()...{returnhasGoodCredit;}
publicvoidsetHasGoodCredit(booleanflag)...{hasGoodCredit=flag;}

@OneToOne(cascade=...{CascadeType.ALL})
publicAddressgetAddress()...{returnaddress;}
publicvoidsetAddress(Addressaddress)...{this.address=address;}

@OneToOne(cascade=...{CascadeType.ALL})
@IndexColumn(name="INDEX_COL_CC")
publicCreditCardgetCreditCard()...{returncreditCard;}
publicvoidsetCreditCard(CreditCardcard)...{creditCard=card;}

@OneToMany(cascade=...{CascadeType.ALL},fetch=FetchType.EAGER)
@IndexColumn(name="INDEX_COL_PHON")
publicCollection<Phone>getPhoneNumbers()...{returnphoneNumbers;}
publicvoidsetPhoneNumbers(Collection<Phone>phones)...{
this.phoneNumbers=phones;
}
@ManyToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
@IndexColumn(name="INDEX_COL_CUS")
publicCollection<Reservation>getReservations()...{returnreservations;}
publicvoidsetReservations(Collection<Reservation>reservations)...{
this.reservations=reservations;
}

publicStringtoString()...{
returngetLastName()+","+getFirstName();
}
}
说明如何显示清单 3 中的类。这个页面包含多个区域,我们将逐一讨论这些区域。
我们来看看如何显示 CreditCard 关系。
这种情况与 Cabin.java 和 Ship.java 之间的关系(多对一)相似,在处理多对一关系时会通过一个按钮打开模态表单。但是,因为 Customer-CreditCard 的关系是一对一,即每个信用卡号只与一个客户相关联,所以打开对话框来选择现有实例是不合适的。而是应该打开一个 CreditCard 表单 对话框,可以在其中输入新信用卡的完整信息(见 图 6):
Customer-Phone 生成一个网格,用户可以在其中输入新电话号码的详细信息。按 Add Row 按钮就会创建一个用于输入新电话号码的新行。
Add.. 按钮在列表框中添加一个新预订,可以用一个选择器模态对话框收集所选的预订。图 7 显示完成后的表单,其中已经添加了电话号码和预订:
目前,您可能不喜欢生成的窗口的外观。有两个主要问题:字段标签显示 Java 属性名,而且它们出现的次序看起来很乱。可以用 jpa2web 工具的定制注解解决这些外观问题。例如,在 Cabin 类(清单 4)中添加 MemberField 注解,会让表单有更好的外观(见 图 8)。对比修饰后的版本和原来的版本(图 3)。标题现在包含有意义的名称,字段的次序现在也更有意义了。
图 8. 修饰后的船舱表单
清单 4. Cabin.java 中的修饰注解
@Entity
public
class
Cabin
implements
java.io.Serializable
...
{
@Id@GeneratedValue@MemberField(order=1,showName="ID")
publicintgetId()...{returnid;}

publicStringgetName()...{returnname;}@MemberField(order=2,showName="CabinName")
@MemberField(order=2,showName="NumberofBeds")
publicintgetBedCount()...{returnbedCount;}
@MemberField(order=3,showName="DeckLevel")
publicintgetDeckLevel()...{returndeckLevel;}
@ManyToOne@MemberField(showName="Ship")
publicShipgetShip()...{returnship;}
....
}
其他修饰性特性包括当某个字段可以接受一系列预定义值时,生成组合框而不是文本框。这也要使用 MemberField 注解来实现。还可以定义许多其他注解来定制生成的窗口的外观和感觉。
|
现在已经了解了 jpa2web 应用程序的性质,接下来看看它的运行情况。按照以下步骤用 Apache Tomcat 运行 jpa2web 应用程序:
- 从 Sourceforge 下载 jpa2web。
- 将下载文件解压到磁盘上的一个文件夹(下面将这个位置称为 [dir])。
- 从 Apache 站点下载 Tomcat 并安装在一个文件夹中(下面将这个位置称为 [tomcatdir])。
- 将 [dir]/zklibs 和 [dir]/jboss_libs 中的 JAR 文件复制到 [tomcatdir]/lib。
- 下载适当的 JDBC 驱动程序并将 .jar 文件复制到 [tomcatdir]/lib。
- 在 [dir]/modelsrc 文件夹下面编写领域模型源文件(带 JPA 注解的 bean)。
- 修改 [dir]/templates/hibernate-cfg.xml 文件以连接数据库(参见以下代码示例):
<
hibernate-configuration
>
<
session-factory
name
="thefactory"
>
<
property
name
="hibernate.connection.driver_class"
>
net.sourceforge.jtds.jdbc.Driver
</
property
>
<
property
name
="hibernate.dialect"
>
org.hibernate.dialect.SQLServerDialect
</
property
>
<
property
name
="hibernate.hbm2ddl.auto"
>
update
</
property
>
<
property
name
="hibernate.connection.url"
>
jdbc:jtds:sqlserver://127.0.0.1:1433/test
</
property
>
<
property
name
="hibernate.connection.username"
>
sa
</
property
>
<
property
name
="hibernate.connection.password"
>
*
</
property
>
<
property
name
="hibernate.max_fetch_depth"
>
0
</
property
>
<
#list
windowsaswin
><
mapping
class
="${win.mainClass.name}"
/>
</
#list
>
</
session-factory
>
</
hibernate-configuration
>
- 修改 [rootdir]/jpa2web-loader.xml 文件,包含希望生成的文件的包。
- 在 build-tomcat.xml ANT 文件中修改 Tomcat 的路径并执行默认目标:
$ ant -buildfile build-tomcat.xml。 - 现在,在 [dir]/dist 文件夹中会生成 Web 应用程序的 Web 文件夹,还将它复制到 Tomcat 的 webapps 目录中。可以通过 http://localhost:8080/sampleapp.war(或相似的 URL)访问这个应用程序。
尽管详细解释 jpa2web 生成器的工作方式超出了本文的范围,但是下面会简要讨论一下生成器引擎背后的主要思想和方法。
输入很简单:一系列带 JPA 注解的类。读取这些源文件并构建 bean 及其相互关系的内部表示(称为装载器)。jpa2web 只实现了 EntityLoader(它读取 JPA 注解),但是可以实现其他装载器(例如,读取 XML 映射配置文件的装载器)。然后,将这个结构用作生成器的输入,生成器还以一系列 FreeMarker 模板作为输入(关于 FreeMarker 模板的更多信息参见 参考资料)。使用模板使 jpa2web 能够以非常灵活的方式生成窗口。图 9 说明这个过程的各个阶段。生成器的输出是一个 Web 应用程序的 WAR 文件夹,其中包含与每个带注解的 bean 对应的窗口,以及用来选择窗口的索引。
经过简化的生成器伪代码如下:
for
eachbeaninthemodel:
-createaformwindowcalledfully
.
classified
.
className
.
zul:
for
eachsimplefield
if
(
fieldisString
)
rendertextbox
if
(
fieldisint
/
byte
/
short
)
renderintegerbox
if
(
fieldisboolean
)
rendercheckbox
if
(
fieldisdouble
/
float
)
renderdoublebox
/
floatbox
for
eachOneToOnemember:
renderabuttonthatwillopenaformtocreateaninstanceofthedestinyclass
for
eachManyToOnemember:
renderabuttonthatwillopenalisttochooseaninstanceofthedestinyclass
for
eachOneToManymember:
renderatablewiththedetailsofthedestinyclassandbuttons
toaddanddeleterows
for
eachManyToManymember:
renderalistboxwiththeelementsmapped
,
andabuttonthatwillopenalisttochooseaninstanceofthedestinyclass
-createawindowcalledlist
.
fully
.
classified
.
className
.
zul
whichlistsalloftheinstances
-addtheannotatedclasstothemappingareaofhibernate
.
cfg
.
xml
-addlinkstoamenubarwiththetwowindowsgenerated
for
thisbean
以上算法只是建立映射的一种方法,还有许多其他方法和解决方案,适用于不同的场景。通过使用其他注解,可以在运行时调整生成过程本身。
还有其他一些工具能够根据某种输入生成 Web 应用程序代码,其中许多是模型驱动体系结构(MDA)的完整实现。jpa2web 工具是一种轻量的注解驱动的代码生成工具,而不是 MDA 的完整实现。
|
要想开发能够在大型部署中使用的生成器,开发人员必须解决几个问题。对于产生复杂的方向图的类模型,jpa2web 显然无法处理,尤其是在存在复杂的循环引用的情况下。到编写本文时,jpa2web 不支持一对多关系内的一对多和多对多(但是这个实现是可行的,jpa2web 发展计划可能会包含这个特性)。无论如何,必须进一步研究如何为复杂的模型生成窗口。不适合使用 jpa2web 的最明显的情况包括,创建事务性更强的 Web 应用程序,以及用户界面不与类模型直接对应的场景。
尽管存在一些限制,在许多情况下 jpa2web 仍然是一个有用的工具。通过使用 jpa2web,只需提供带注解的 bean,就能够为非事务性元素快速生成 Web 界面。还可以用它在数据库中创建必要的对象实例以执行测试,从而避免编写复杂的实体创建脚本。jpa2web 的输出只能作为需要进一步开发的原型,不应该作为最终的应用程序。生成的代码相当简单,修改其行为以及添加检验功能和额外的处理也很容易。但是,对于更大型的应用程序,jpa2web 的适用范围受到事务、安全性、并发性和多层约束的限制,需要做一些额外的集成工作。
计划实现的改进包括支持元素间关系更复杂的领域模型,添加定制的注解以便更好地控制生成的用户界面,以及从其他 Ajax 工具箱(比如 Google 的 GWT、OpenLaszo 或 Echo2)生成代码。还可能扩展生成器的输入类型,可能使用 XML 文件输入模型。如果您有兴趣帮助扩展 jpa2web 或希望进一步了解它的功能,请与作者联系。
学习
- 了解关于 POJO(Plain Old Java Object) 的更多信息。
- 了解 Hibernate。
- 了解 ZK。
- 阅读 Bill Burke 和 Richard Monson-Haefel 所写的 “Enterprise JavaBeans, 3.0” 一书(O'Reilly,第 5 版,2006 年)。
- 了解 FreeMarker 模板。
- 了解关于 MDA(Model Driven Architecture) 的更多信息。
- 了解 JSR-000220 Enterprise JavaBeans 3.0。
- 了解 ZK 框架。
- 查看 ZK Live Demo。
- 了解 Red Hat Middleware, Hibernate hbm2ddl。
- 阅读 AndroMDA, an Open Source MDA Generator。
- 熟悉 Java 注解。
- 了解 Apache Struts Framework。
- 了解 JavaServer Faces 技术。
- 了解强大的 Spring Framework。
- Google Web Toolkit 可以帮助开发人员用 Java 语言构建 Ajax 应用程序。
- 了解 OpenLaszlo,这是一种用来构建富 Internet 应用程序的开放源码平台。
- 了解 Echo2。
jpa2web是一款轻量级工具,可根据带注解的Java对象生成完整的Ajax Web应用程序,极大简化了Web界面开发过程。








2万+

被折叠的 条评论
为什么被折叠?



