EJB3介绍:Overview
EJB作为企业级的数据访问/持久化标准在1999年作为J2EE规范的核心规范出现,极大的转变了java企业级开发的模式,为java软件开发提供了一个良好的架构。 EJB从1.0到2.1在J2EE架构中,都是作为一个服务器端的(Server side)的数据访问中间件。开发人员通过EJB标准的API接口来访问操作数据,避免直接用JDBC和Sql操作底层的数据库。
采用EJB架构的目标在于:
- 减轻直接操作底层数据库的工作量
- 为企业级开发引入了面向对象/面向服务的开发架构
- 数据对象生命周期的自动管理
- 分布式能力
- 集成/声明式的安全/事务管理
在旧的EJB模型中(2.1以前),EJB实现了大部分的目标,但一个巨大的缺陷是原有的模型在试图减轻数据访问工作量的同时也引入了更多的复杂开发需求。 例如EJB核心的的Entity Bean必须特定的Home,Remote,Business接口,发布前需要预编译,只能实现单表映射操作,静态的EJB-QL(EJB查询语言)都不能满足简化数据库操作的目标。 EJB 2.1的复杂度,开发成本和性能问题使得EJB在问世4年后仍然等不到广泛的应用。
到了2004年,随着POJO( Plain Old Java Object )模型的出现,动态代码操作,IOC模式等先进,简单实用技术的发展和它们在各种独立产品中的表现,都证明POJO,IOC的技术比原有的EJB 2.1模型更适合作为数据访问中间件,开发的难度和成本远远小于目前的EJB模型,确有更灵活和可扩展。 2004年9月J2EE平台规范集众家所长,推出了跨越式的Java EE 5.0规范,最核心的是全面引入新的基于POJO和IOC技术的EJB3模型。到此,J2EE 5规范( Java EE 5 )成为一个集大成者,纳百家之长,成为java企业开发统一的标准规范。
EJB 3和EJB 2.1的区别
从整个EJB规范的角度来说,EJB 3和EJB 2.1最大变更在Entity Bean持久化API上。在EJB3中,Entity Bean持久化已经单独作为一个Persistence API规范和其他的EJB部分分离开来。下面我们主要讨论EJB 3和EJB 2.1在持久化API上的区别。
EJB 2.1模型存在复杂度高的缺陷:
- EJB 2.0 模型要求创建多个组件接口并实现多个不必要的回调方法
- 组件接口要求实现 EJBObject 或 EJBLocalObject 以及处理许多不必要的异常
- 基于XML的EJB 2.0 部署描述符比较复杂并容易出错
- 基于 EJB 模型的容器管理持久性在开发和管理方面过于复杂,并且失去了几个基本特性--如使用数据库序列定义主键的标准方法
- EJBQL 语法非常有限,而且是静态的,无法做到运行期间的动态查询
- EJB 2.0 组件并非是真正面向对象的,因为它们在继承和多态性方面的有使用限制
- 开发人员无法在 EJB 容器外部测试 EJB 模块,而在容器内部调试 EJB非常复杂和耗时
- 查找和调用 EJB 2.0 是一项复杂的任务。即使是在应用程序中使用最基本的 EJB 也需要对 JNDI 有一个详细的了解
- 对容器的依赖使得EJB 2.0只能用于服务器组件的开发,无法实现一次编写,四处运行的面向构件的开发
所有这些复杂度和缺陷,都导致EJB 2.0的采用无法真正简化开发并提高生产力。
EJB 3.0 旨在解决以往EJB 2.0 模型的复杂性和提高灵活性,具体体现在:
- 消除了不必要的接口Remote, Home, EJB以及回调方法实现
- 实体Bean采用了POJO模型,一个简单的java bean就可以是一个Entity Bean。无需依赖容器运行和测试
- 全面采用O/R Mapping技术来实现数据库操作
- 实体Bean可以运用在所有需要持久化的应用,不管是客户端还是服务器端。从而真正实现面向构件的开发
- 实体 bean 现在支持继承和多态性
- 灵活丰富的EJB3查询语言
- SQL支持
- 使用元数据批注代替部署描述符,减少复杂配置和提高可维护性
- 将常规 Java 类用作 EJB 并将常规业务接口用于 EJB
EJB 3中的元数据批注:Annotation
EJB3 规范中引入了元数据批注(Annotation)。Annotation是从J2SE 1.5开始称为java语言的一部分。Annotation并不是一个新的事物,在J2SE 1.5以前,人们已经自行引入了象著名的XDoclet等外挂式的元数据批注方法。而在.NET中,元数据批注也早已经是C#语言的成分了。
在以往,我们都是采用xml作为配置批注,但采用文本的xml配置存在一些缺陷:
- 描述符多,不容易记忆和掌握
- 无法做自动的校验,需要人工排错
- 当系统变大时,大量的xml配置难以管理
- 读取和解析xml配置非常耗时,导致应用启动缓慢,不利于测试和维护
- 做O/R Mapping的时候需要在java文件和xml配置文件之间交替,增大了工作量
- 运行中保存xml配置需要消耗额外的内存
采用元数据可以很好的解决这些问题:
- 描述符大量减少。以往在xml配置中往往需要描述java属性的类型,关系等等。而元数据本身就是java语言,从而省略了大量的描述符
- 编译期校验。错误的批注在编译期间就会报错。
- 元数据批注在java代码中,避免了额外的文件维护工作
- 元数据被编译成java bytecode,消耗小的多内存,读取也非常迅速,往往比xml配置解析快几个数据量级,利于测试和维护
第一个Entity Bean:HelloWorld
EJB3中的Entity Bean是如此的简单,就是一个普通的java bean加上一些精炼的元数据批注。
@Entity
@Table( name="helloTable" )
public class HelloEntityBean {
private int id;
private String foo;
/**
* The entity class must have a no-arg constructor.
*/
public HelloEntityBean() {
}
public HelloEntityBean(int id, String foo) {
this.id = id;
this.foo = foo;
}
@Id(generate=GeneratorType.NONE)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFoo() {
return foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
public String toString(){
return "HelloEntityBean: id=" + id + ", foo=" + foo;
}
}
代码中元数据的说明:
@Entity :EJB3 Entity Bean的批注,表明当前的java bean作为一个Entity Bean来处理。
@Table( name="helloTable" ) :顾名思义,定义当前Entity对应数据库中的表。
@Id(generate=GeneratorType.NONE) :我们把HelloEntityBean的id属性定义为主键。主键产生策略为GeneratorType.NONE,意味这是一个业务主键。
就这么简单!
解说Entity
从EJB3.0开始,Entity Bean持久化规范与EJB的其他规范如Session Bean, Message Driven Bean和EJB3容器规范开始分离,单独作为一个持久化API。而在将来,该持久化API很可能会从EJB3.0规范中脱离出来成为一个独立的规范Java Persistence API。作为Java平台上的通用数据访问标准接口。 为了跟规范一致,我们将在开发手册中将Entity Bean称为Entity。
虽然EJB3 Entity可以是很简单的java bean,只要批注了@Entity或者在xml配置中作了说明,就被做一个可持久化的Entity处理。 但还是需要遵行一定的规则:
- Entity类必须要有一个无参数的public或者protected的Constructor。
- 如果在应用中需要将该Entity类分离出来在分布式环境中作为参数传递,该Entity Class需要实现java.io.Serialzable接口。
- Entity类不可以是final,也不可有final的方法。
- abstract类和Concrete实体类都可以作为Entity类。
- Entity类中的属性变量不可以是public。Entity类的属性必须通过getter/setter或者其他的商业方法获得。
定义对Entity中属性变量的访问
在绝大部分的商业应用,开发人员都可以忽略这部分无需关心。但如果你需要编写复杂的Entity类的话,你需要了解这个部分。复杂的Entity类是指在Entity类的getter/setter和商业方法中包含比较复杂的业务逻辑而不是仅仅返回/符值某个属性。
在大部分的情况下,我们都建议使Entity类中setter/getter中的逻辑尽可能简单,除了必要的校验符值外,不要包含复杂的业务逻辑,例如对关联的其他Entity类进行操作。但有些情况下,我们还是需要在Entity类的setter/getter方法中包含商业逻辑。这时候,采用何种属性访问方式就可能会影响代码的性能甚至是逻辑正确产生影响。
EJB3持久化规范中,在默认情况下所有的属性都会自动的被持久化,除非属性变量用@Transient元数据进行了标注。针对可持久化属性定义了两种属性访问方式(access): FIELD和PROPERTY。
- 如果采用access=FIELD, EJB3 Persistence运行环境(EJB3持久化产品,如Liberator EJB3)直接访问对象的属性变量,而不是通过getter。这种访问方式也不要求每个属性必须有getter/setter。如果需要在getter中包含商业逻辑,应该采用access=FIELD的方式。
- 如果采用access=PROPERTY, EJB3 Persistence运行环境将通过Entity类上的getter来访问对象的属性变量,这就要求每个属性变量要有getter/setter方法。在EJB3中,默认的属性访问方式是PROPERTY。access=PROPERTY时getter/setter的逻辑应该尽量简单。
规范中access方式还有多一层含义。就是采用access=FIELD时,元数据应该批注在属性上。
@Id(generate=GeneratorType.NONE)
private int id;
private String foo;
/**
* The entity class must have a no-arg constructor.
*/
public HelloEntityBean() {
}
public int getId() {
return id;
}
采用access=PROPERTY(默认方式)时,元数据应该批注在对应属性变量的getter上。
private int id;
private String foo;
/**
* The entity class must have a no-arg constructor.
*/
public HelloEntityBean() {
}
@Id(generate=GeneratorType.NONE)
public int getId() {
return id;
}
为了方便开发,Liberator EJB3实现对元数据批注的位置比规范规定的宽松,针对属性变量或它的getter进行批注都可以,不受access类型的影响。
对Entity类中,getter/setter的命名规则遵从java bean规范。
Entity类中的属性变量可以是以下数据类型:
- 原始数据类型和他们的对象类型
- java.lang.String
- java.math.BigInteger
- java.math.BigDecimal
- java.util.Date
- java.util.Calendar
- java.sql.Date
- java.sql.Time
- java.sql.Timestamp
- byte[]
- Byte[]
- char[]
- Character[]
- enums
- Entity类
- 嵌入实体类(embeddable classes)
还可以是以下集合类型:
- java.util.Collection和它的实体类
- java.util.Set和它的实体类
- java.util.List和它的实体类
- java.util.Map和它的实体类
主键和实体标识(Primary Key and Entity Identity)
每个Entity类都必须有一个主键。在EJB3中定义了两种主键:
简单主键必须对应Entity中的一个属性变量(Instance Variable),而该属性对应数据库表中的一列。使用简单主键,我们只需要用@Id元数据对一个属性变量或者她的getter方法进行批注。
当我们需要使用一个或多个属性变量(表中的一列或多列)联合起来作为主键,我们需要使用复合主键。复合主键要求我们编写一个复合主键类( Composite Primary Key Class )。复合主键类需要符合以下一些要求:
- 复合主键类必须是public和具备一个没有参数的constructor
- 复合主键类的每个属性变量必须有getter/setter,如果没有,每个属性变量则必须是public或者protected
- 复合主键类必须实现java.io.serializable
- 复合主键类必须实现equals()和hashcode()方法
- 复合主键类中的主键属性变量的名字必须和对应的Entity中主键属性变量的名字相同
- 一旦主键值设定后,不要修改主键属性变量的值
一起看一个复合主键的例子。Entity类Person,它的主键属性变量是firstName和lastName。
@Id
private String firstName;
@Id
private String lastName;
public Person() {
}
Person的复合主键类:
public class PersonPK implements java.io.Serializable{
private String firstName;
private String lastName;
public PersonPK() {
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PersonPK)) return false;
final PersonPK personPK = (PersonPK) o;
if (!firstName.equals(personPK.firstName)) return false;
if (!lastName.equals(personPK.lastName)) return false;
return true;
}
public int hashCode() {
int result;
result = firstName.hashCode();
result = 29 * result + lastName.hashCode();
return result;
}
}