站内搜索: 请输入搜索关键词

当前页面: 开发资料首页J2EE 专题使用EJB 3.0简化企业级Java开发,第二部分

使用EJB 3.0简化企业级Java开发,第二部分

摘要: 在这个EJB 3.0学习系列中的第二部分,你将学到如何使用POJO开发数据模型,还有如何透明的将那些数据对象模型与关系型数据库相互映射。使用EJB 3.0中注释式的实体bean,开发数据库驱动的应用就是小菜一碟。
使用EJB 3.0简化企业级Java开发,第二部分

带来简便的POJO持久化技术

作者:Michael Juntao Yuan (迈克尔.袁俊涛)

翻译:simmone


版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
作者:
Juntao Yuan ;simmone
原文地址:
http://www.javaworld.com/javaworld/jw-09-2005/jw-0912-ejb.html
中文地址:
http://www.matrix.org.cn/resource/article/43/43839_EJB_POJO.html
关键词: EJB Enterprise POJO


概述

在这个EJB 3.0学习系列中的第二部分,你将学到如何使用POJO开发数据模型,还有如何透明的将那些数据对象模型与关系型数据库相互映射。使用EJB 3.0中注释式的实体bean,开发数据库驱动的应用就是小菜一碟。
请阅读有关EJB 3.0的整个学习系列:
第一部分:使用注释开发POJO服务
第二部分:带来简便的的持久化技术
(2000字;2005年9月12日)

在第一部分中,我讨论了在企业级JavaBean 3.0(EJB)中注释驱动的POJO编程模型。我阐述了如何开发POJO服务,如何让容器服务使用POJO, 如何使用依赖注入来组合应用。这些POJO服务主要是用来封装应用的商业逻辑。在商业逻辑的背后,现今的大多数应用都有由一个高性能的关系型数据库作为支撑的数据模型层。

在第二部分中,我将讨论EJB 3.0实体bean如何利用POJO和注释的优势来极大地简化你的数据模型以及它们与后台关系数据库的持久化。在我们进入EJB 3.0实体bean的细节之前,让我们先来看一下为什么对于企业级Java应用,数据模型和持久化是如此巨大的一个挑战。

对象-关系映射(ORM)

在Java虚拟机中,所有的数据都被模型化并且封装在了类和对象的树结构中。但是在后端的关系型数据库中,数据被模型化为关系型表,它们通过共享的键域(外键)相互关联起来。相同的数据却有两个视图,这对企业级Java的开发者来说是一个艰难的挑战:当你想从持久化的数据存储中存取数据时,你必须在对象与关系表达之间来回转换,这一过程叫做对象-关系映射(ORM)。在Java EE(Java企业版,以前叫做J2EE),你可以通过两个途径来实现对象-关系映射。

手动的:你可以使用Java数据库连接来直接操作持久化-对于简单应用的直截了当的解决方案。JDBC API的类是紧贴在关系型数据库表、行和列之后的数据模型。你必须手动地在应用的内部对象模型与JDBC对象模型之间进行转换,如果你的应用的内部模型本身就类似于2维的关系表的话,那采用JDBC是最佳手段。

自动的:你可以把ORM交给框架。框架通常向你提供一个可以和任意数据对象进行交互的API。通过那个API,你可以存储、获取和查询数据库。框架在后台完成了框架对象的转换。因为特定的关系型SQL查询不适合对象接口,ORM框架通常定义它自己的查询语言,并且自动为当前关系型数据库生成正确的SQL语句。对于拥有复杂的数据模型的应用来说,基于框架的手段能为你节省很多时间并降低了出错的可能。

对象数据库

一个对象型数据库直接在数据库中存储、获取和查找对象。因为不再需要ORM,所以它对于Java应用非常适合。不幸的是,现今的对象型数据库相对于关系型数据库来说还不成熟,速度也慢。你可以这样说,一个好的ORM框架从根本上来说,就是为关系型数据库提供一个对象型数据库的接口。两者它都要做到最好。


这篇文章,我将重点放在专为企业级Java ORM应用设计的自动框架上。下一节,我将提到几个流行的ORM框架和EJB 3.0中几个关键的革新。

ORM 框架

EJB 实体bean是Java EE中“官方”的ORM解决方案。但是,在EJB1.x和2.x中,实体bean的难以使用是出了名的,原因如下:

●EJB 1.x和2.x实体bean必须遵守一种严格的组件模型。每一个bean类必须实现一个home接口和一个商业接口。它们必须从某种抽象类中继承,而且必须实现其所有方法,即使它们多数为空。这样的一种严格组件模型使得想从EJB 1.x和2.x的实体bean中构建面向对象的数据模型几乎变得不可能了。
●EJB 1.x和2.x容器需要特别冗长的xml配置文件来建立实体bean与关系型数据库中的表映射。那些文件是非常单调乏味和容易出错的。

简而言之,EJB 1.x和2.x实体bean是一个设计拙劣的ORM框架。它既没有满足Java数据对象模型的需求,也没有满足关系表数据模型的需求。出于对EJB 1.x和2.x实体bean的不满,开发者开始寻找其它的ORM方案。实际使用中,开源的Hibernate(JBoss开发)和Oracle公司的TopLink是最成功的两个POJO ORM框架。Hibernate和TopLink都是基于POJO的。它们不依赖于任何预定义的组件模型。作为替代,它们使用POJO数据对象(简单的JavaBean式的),自动地解读出如何映射它们,以及它们之间的关系(关系型数据库)。通常,JavaBean类映射到一张数据库表,并根据数据库表中的外键映射出类之间的关系。你可以在一个简单直接的xml配置文件中指明ORM的配置信息,比如JavaBean类对应的表名和属性对应的列名。你可以通过框架中的工具(如:Hibernate中的Session类)来对那些POJO进行操作(如:存储、获取和查找)。

EJB 3.0是建立在Hibernate和TopLink的思想和成功之上。它为Java EE提供了一个标准的POJO ORM框架。另外,EJB 3.0有两个超越现今所有持久化解决方案的关键革新:

●没有使用XML文件来指明ORM配置信息, EJB 3.0允许开发者直接在POJO代码中注释出映射信息。举例来说,你可以用注释来指明每个JavaBean属性对应的关系型表列。在这篇文章的后面,你将看到更多的例子。注释使得映射更直接,更容易维护了。
●EJB 3.0为实体bean定义了一个新的归档格式。每个档案使用一组独立的,为后端数据库和ORM行为所专用的配置集来定义一个持久化上下文。在这篇文章的后面,我会讨论持久化上下文。
现在,让我们通过几个简单的例子来看一下EJB 3.0是如何完成POJO ORM的。

映射一个简单的对象

在EJB 3.0中,每个实体bean都是一个简单的JavaBean式的类。为了告诉EJB 3.0容器这个类应该为持久化进行映射,你应该用@Entity来注释这个类。
每一个实体bean类映射到一个关系型数据库表。默认地,表名对应类名。你可以用@Table来为类指定另一个表。每一个JavaBean属性映射到表的列上,同样的,默认列名就是属性名。你可以用@Column注释在属性的Setter方法上来改变这种默认关系。下面是一个EJB 3.0的简单例子:

@Entity
// @Table (name="AlternativeTableName")
public class Person implements Serializable {

protected int id;
protected String name;
protected Date dateOfBirth;

public void setId (int id) {
this.id = id;
}

@Id(generate = GeneratorType.AUTO)
public int getId () {
return id;
}

public void setName (String name) {
this.name = name;
}

// @Column (name="AlternativeColumnName")
public String getName () {
return name;
}

public void setDateOfBirth (Date dateOfBirth) {
this.dateOfBirth = dateOfBirth;
}

public Date getDateOfBirth () {
return dateOfBirth;
}
}


当容器把Person类映射到Person SQL数据库表以后,每一个Person实例就是表中的一条数据记录。

映射一个简单的JavaBean类是容易的。但自动ORM框架真正闪光之处在于映射互相关联的对象。下一节中,我们看一下EJB 3.0如何操作对象间的关系。

关系

在一个数据模型里面,一般来说类相互之间都会有某种联系。比如,一个Person(个人)对象可以和一个Resume(确认)对象相关联,反过来也一样(一对一关系);一个Person对象可以和多个CreditCard(信用卡)对象相关,而一个CreditCard对象只能和一个Person对象相关(一对多关系)。多个Person对象可以和一个Address(地址)对象相关,而一个Person对象只能对应一个Address对象(多对一关系)。(译者注:此处原著笔误, Person与Address位置颠倒了;编者注:我看两者是多对多的关系。一家人住在同一个地方,这个地址对于这一家人来说是一对多的关系;房主在别的地方又买了一套房,房主与地址的关系是一对多的关系。)

在一个数据模型中,对象指针用来操作那些关系。举例来说,一个Person对象可以有一个属性(也就是域)指向一个Resume对象。而另一个属性是CreditCard对象的集合。为了告知EJB 3.0容器对象间的关系,你只需简单地在POJO中注释JavaBean属性。

@Entity
public class Person implements Serializable {

// ... ...
protected Resume resume;
protected CreditCard [] cards;
protected Address addr;

// ... ...

@OneToOne
public Resume getResume () {
return resume;
}

// ... ...

@ManyToOne
// @JoinColumn (name="MyCustomId")
public Address getAddr () {
return addr;
}

// ... ...

@OneToMany
public Collection getCards () {
return cards;
}
}


在关系型数据库中,那些关系自动地被EJB 3.0容器使用外键来重建了。举例来说,Person表有一个外键包含了Resume表中相应的主键。运行时,EJB 3.0容器加强了一对一的关系:它保证了Resume键值对于Person表中的每一行是唯一的。为了启用Resume表到Person表的双向查询,你可以Resume表中定义一个Person属性,并把它也加上@OneToOne注释。
Person表中也有一个外键包含了Address表中相应行的主键。这种情况下,相同的Address主键可以出现在多个Person行中,这是多对一关系。对于一对多的关系,映射稍有一点复杂,因为外键列是定义在多对一表中的。于是,在CreditCard类中,你必须用@ManyToOne来定义一个Person属性。

改变外部键字段名

ORM中使用的外部键字段的名字是由容器自动决定的或者由@JoinColumn注释来显式的指定。

上面讨论的关系只是实体bean之间关系的一种类型,实体类之间另外一种重要关系是继承。

继承

面向对象设计方法的一个关键概念是继承。使用继承,你可以创建一个复杂的对象树而不需要重复的代码。举例来说,Consultant(顾问)是提供有偿服务的一个人,那么在我们的数据模型中,Consultant类就从Person(个人)类中继承,并增加了一个价格属性。不幸的是,当今的关系型数据库并不存在继承的概念。ORM框架主要通过以下两个手段来模仿这种行为:

●框架可以为每一个类生成一个单独的表。子类的表重复了那些从父类的字段。子类和父类都存储为各自对应的表。
●框架可以使用包含了所有子类属性的表。两种类(父类和子类)的实例都存储于同一张表—父类中不存在的字段(也就是,子类的字段)取null值。为了使继承映射更为强壮,表也可以有一个“区别”列,它存储的标记表明该行数据映射到哪一个类。

EJB 3.0实体bean支持上述两种映射策略,默认是单表映射策略。你可以简单地用注释指明子类的继承策略和区别字段的名字。下面是Consultant类的例子,它从Person类中继承:

@Entity
@Inheritance(discriminatorValue="C")
@DiscriminatorColumn(name="person_type")
public class Consultant extends Person {

protected double rate;

public void setRate (double rate) {
this.rate = rate;
}

public double getRate () {
return rate;
}
}


从上面的例子中,容器使用默认策略将Consultant类映射到Person类对应的同一张表中。如果表中的person_type字段的值为C,那么那一行数据就代表了一个顾问类。否则,当前行代表的是一个普通的Person类。

持久化档案

现在你的数据模型有了一组使用了注释EJB 3.0实体bean的类,你可以将它们捆绑在一起布署到服务器环境中。EJB 3.0为实体bean定义了一种特殊的归档格式,叫做持久化档案(文件后缀名为.par)。

一个.par文件是一组实体bean类文件加上一个简单的配置文件META-INF/persistence.xml的jar打包文件。persistence.xml文件定义了持久化上下文,它告知EJB 3.0哪一个后端数据库(数据源)与这一组实体bean相对应。persistence.xml也包含了配置属性的细节。举例来说,JBoss EJB 3.0是在Hibernate 3.0之上实现的,于是你可以通过persistence.xml传递任意的Hibernate配置选项。这有一个范例persistence.xml文件,它包含了JBoss和Hibernate专用的配置属性,包括SQL 方言(dialect)和二级缓存。


cal
java:/DefaultDS

value="org.hibernate.dialect.MySQLDialect" />
value="org.jboss.ejb3.entity.TreeCacheProviderHook"/>
value="jboss.cache:service=EJB3EntityTreeCache"/>




实体管理器

一旦你部署了实体bean, 你必须通过EJB 3.0的实体管理器的API来访问和操作它们。EJB 3.0容器为每个部署的持久化上下文(也就是,.par文件)提供了一个实体管理器对象。从一个EJB 3.0 session bean POJO(参看第一部分)中 ,你可以通过@PersistenceContext注释将实体管理器对象注入,并传入上下文的名字。

@Stateless
public class ManagerBean implements Manager {

@PersistenceContext (unitName="cal")
protected EntityManager em;

// Use "em"
// 使用“em”
// ... ...
}


基本操作

要创建一个数据对象并把它存入数据库中,你只需简单地使用Java的new关键字来创建POJO,并把它传给EntityManager.persist()方法。

Person p = new Person ();
p.setName ("A new baby");
p.setDateOfBirth (new Date ());
em.persist (p);


要从数据库中取得对象,你可以使用EJB 3.0查询语言来搜索数据库。下面的例子演示了如何将Person表中的所有行作为Person Java对象的集合来返回。
// 得到所有人的对象

Collection  persons = (Collection ) 
em.createQuery("from Person p").getResultList();


可管理的POJO

通过实体管理器保存和获取的对象是被管理在持久化上下文中的。这意味着如果对象后来被改变了,那这种改变将会被自动检测并持久化到数据库中。在下面的例子中,我们更新了一个可管理的POJO的一个属性。这个改变会被EJB 3.0容 器自动检测到并发送给了数据。

Person p = em.find(Person.class, personId);
p.setName ("Another Name");


//p会在当前事务结束时被自动地更新到数据库中去。
// 并没用更多的API调用

既然EJB 3.0实体仅只是POJO,那么它们就可以能够被序列化并通过网络传递。如果一个对象不是被容器创建的(也就是说,它是从网络连接中传递过来的或者是某一个远程调用返回的结果),那么持久化上下文并不会管理它。不过,你可以通过调用EntityManager.merge()方法将一个非管理的POJO合并到持久化上下文中。下面是将一个解序列化的POJO合并入当前持久化上下文中的例子。

InputStream in; 
// 初始化输入流
Person p = Util.deserialize (in);

// ... ...
em.merge (p);

// p现在是一个可管理的对象了。p的任何改变将会被自动检测并持久化
p.setName ("Another Name");


数据库同步

当实体管理器对象在一个session bean中使用时,它是和服务器的事务上下文绑定的。实体管理器在服务器的事务提交时提交并且同步它的内容。在一个session bean中,服务器的事务默认地会在调用堆栈的最后提交。当然,你也可以通过注释来为每个商务方法指定具体的事务属性。下面的例子展示了如何为一个session bean的方法声明一个新的事务。

@TransactionAttribute(TransactionAttributeType.REQUIRESNEW)
public void update () {

// 这个方法更新Person对象
// 更新将会在这个方法的末尾被提交和刷新到数据库中


批处理中刷新数据库操作

为了只在当事务提交时才将改变更新到数据库中,容器将所有数据库操作集中到一个批处理中,这样就减少了代价昂贵的与数据库的交互。


如果你需要在事务提交之前将更新刷新到数据库中,你可以直接地调用EntityManager.flush()方法。或者你可以将一个方法注释为@FlushMode(FlushModeType.NEVER),于是事务管理器将不会在方法的结尾(也就是事务的结尾)处刷新更新到数据库中。这种情况下,你可以手工地来刷新数据库以获得对数据库操作的最大控制。

总结
EJB 3.0 提供了一种简单有效的框架将Java POJO映射到SQL数据库中的关系型表中。它基于Java类中的名字和结构进行智能的默认映射策略。但你也可以用一组简单的注释重载所有的默认值,来处理复杂的对象关系。

EJB 3.0实体管理器提供了简单的API来持久化和从数据库中查找对象。每一个实体管理器对象与一组映射的POJO相关联,并有着它自己的数据库设置。它会自动地捆绑到服务器的事务管理器中。


相关资源
这一系列的第一部分介绍了EJB 3.0的session bean和其它基于POJO的服务对象:“使用EJB 3.0简化企业级Java开发,第一部分 使用注释 开发POJO服务” 迈克尔.袁俊涛(JavaWorld, 2005年8月)
http://www.javaworld.com/javaworld/jw-08-2005/jw-0815-ejb3.html

这篇文章所使用的代码范例源于JBoss EJB 3.0的TrailBlazer应用的代码。你可以在线研究或从以下地址下载TrailBlazer:http://www.jboss.com/docs/trailblazer

“EJB 3.0的简易性”Raghu R.Kodali(Java 开发者杂志, 2005年8月)


下面是与这篇文章中所涉及内容相关的学习资源列表:
o无状态 session beans:
http://trailblazer.demo.jboss.com/EJB3Trail/serviceobjects/slsb/index.html
o有状态 session beans:
http://trailblazer.demo.jboss.com/EJB3Trail/serviceobjects/sfsb/index.html
oSession bean 生命周期内的回调:
http://trailblazer.demo.jboss.com/EJB3Trail/serviceobjects/lifecycle/index.html
o消息驱动 beans:
http://trailblazer.demo.jboss.com/EJB3Trail/serviceobjects/mdb/index.html
oJBoss消息驱动 POJOs:
http://trailblazer.demo.jboss.com/EJB3Trail/serviceobjects/mdpojo/index.html
o针对服务端对象的依赖注入:
http://trailblazer.demo.jboss.com/EJB3Trail/serviceobjects/injection/index.html
o事务服务:
http://trailblazer.demo.jboss.com/EJB3Trail/services/transaction/index.html
o安全服务:
http://trailblazer.demo.jboss.com/EJB3Trail/services/security/index.html
o拦截器:
http://trailblazer.demo.jboss.com/EJB3Trail/services/interceptor/index.html

●这些范例可以在JBoss 应用服务器的4.0.3版本中运行。请确认下载和安装步骤是否正确:
http://trailblazer.demo.jboss.com/EJB3Trail/background/install/index.html
●迈克尔.袁早些时候在JavaWorld上发表的文章 “简化之路”(2005年2月)阐述了POJO编程模型对比老的EJB 2.1的优势:
http://www.javaworld.com/javaworld/jw-02-2005/jw-0221-jboss4.html
●Anil Sharma所写的“EJB 3.0技术手册”(JavaWorld,2004年8月)是一个对早期版本的EJB 3.0规范的分析。它为我们提供了对于范规后面的基本原理的认识:
http://www.javaworld.com/javaworld/jw-08-2004/jw-0809-ejb.html
●迈克尔.袁也进行无线领域的开发。请查看他过去在JavaWorld的无线Java专栏中的文章:
http://www.javaworld.com/columns/jw-wireless-index.shtml
●查看更多的有关Java EE的文章,请浏览在JavaWorld的顶层索引中的Java 2 Platform, Enterprise Edition (J2EE) 部分:
http://www.javaworld.com/channel_content/jw-j2ee-index.shtml?


↑返回目录
前一篇: 通过Jpetstore学习WSAD、ROSE、WEBSPHERE
后一篇: J2EE 部署中的下一个冲击波