用EJB 3.0简化企业Java开发
在分为两部分的这篇文章中,你将了解到如何使用EJB 3.0中的普通Java对象(POJO)编程模型来开发更简单、更健壮的企业Java应用程序。可运行的示例代码演示了EJB 3.0应用程序中的重要元素。本文将在上篇中阐述如何使用EJB 3.0注释来开发松散耦合的POJO应用程序、为可管理的POJO提供容器服务。将在下篇中讨论EJB 3.0实体bean如何利用POJO和注释来大大简化数据模型及后端的关系数据库。
上篇:使用注释开发POJO服务
对开发服务器端应用程序而言,Java企业版本即Java EE(以前叫J2EE)是一个功能强大、但又过于复杂的平台。很早以来,过于复杂历来被认为是阻碍人们采用Java EE的一个重要因素。
但在过去的三年,Java开放源代码社区、Java社区组织(JCP)以及主要的Java EE开发商都在致力于简化Java EE。譬如说,实际的应用程序使用新的设计范例来简化Java EE的开发,譬如普通Java对象(POJO)服务、服务拦截器和依赖注入。而诸多新的工具和框架也得到了广泛采用,用于同样的目的,譬如Hibernate、面向方面编程(AOP)、Struts、XDoclet和Spring。
这些模式和工具让刚入门的开发人员更容易上手,同时提高了经验丰富的Java开发人员的生产力,目前它们正在被JCP集成到下一代Java EE标准(即EJB 3.0)当中。Java开发人员Raghu Kodali最近开展的一项调查表明,把Sun的Java EE示例应用程序RosterApp从EJB 2.1移植到EJB 3.0可以减少50%以上的代码。
Java注释是EJB3.0的重要特性,它把POJO服务、POJO持久性和依赖注入联系起来,成为完整的企业中间件解决方案。本文使用了一个示例应用程序:JBoss EJB 3.0 TrailBlazer,以演示开发添加注释的轻便型EJB 3.0 POJO应用程序。TrailBlazer应用程序多次使用EJB 3.0中的不同工具和API,实现了一个投资计算器。示例应用程序在JBoss 应用服务器4.0.3里面以非传统方式运行,完全符合最新的EJB 3.0规范(公众预览版)。
EJB 3.0的注释驱动编程模型
从开发人员的角度来看,EJB 3.0广泛使用Java注释。注释有两个重要优点:它们取代了过多的XML配置文件,而且不需要严格的组件模型。
注释与XML
基于XML的部署描述符和注释都可以用来配置Java EE应用程序中的服务相关属性。两者的区别在于:XML文件与代码分开处理(往往在运行时);而注释与代码一起编译,而且由编译器进行检查。这对开发人员产生了以下这些重要影响:
● 冗长性:XML配置文件以冗长出名。为了配置代码,XML文件必须从代码地方复制许多信息,譬如类名称和方法名称。另一方面,Java注释却是代码的一部分,不需要另外引用代码,就可以指定配置信息。
● 健壮性:XML配置文件中的复制代码信息带来了多个潜在故障点。譬如说,如果拼错了XML文件中的方法名称,应用程序会在运行时出错。换句话说,XML配置文件不如注释来得健壮。注释可以由编译器来检查,同代码的其余部分一起处理。
● 灵活性:因为XML文件与代码分开处理,所以基于XML的配置信息不是“硬编码”的,以后可以改动。部署时间的灵活性对系统管理员来说是一项很好的特性。
注释使用简单,足以满足大多数应用程序的要求。XML文件比较复杂,可用来处理更高级的问题。EJB 3.0允许通过注释来配置大多数应用程序的设置。EJB 3.0还支持XML文件用于取消默认的注释值、配置外部资源(如数据库连接)。
POJO与严格组件
除了取代及简化XML描述符外,注释还可以让我们弃用曾困扰EJB 1.x和EJB 2.x的严格的组件模型。
EJB 组件是容器管理的对象。容器在运行时操纵bean实例的行为和内部状态。为了让这种行为出现,EJB 2.1规范定义了bean必须遵守的严格的组件模型。每个EJB类必须从为容器提供回调钩子(callback hook)的某个抽象类继承而来。因为Java只支持单一继承,严格的组件模型就限制了开发人员使用EJB组件创建复杂对象结构的能力。读者会在本文下篇分看到,如果映射实体bean中复杂的应用程序数据,这更是个问题。
在EJB 3.0中,所有容器服务都可以通过注释进行配置,并提供给应用程序里面的任何POJO。大多数情况下,不需要特殊的组件类。
开发松散耦合的服务对象
Java EE等企业中间件的最重要的好处之一就是,让开发人员可以使用松散耦合的组件来开发应用程序。这些组件仅仅通过已发布的业务接口来进行耦合。因此,可在不改变应用程序其余部分的情况下,改变组件实现类。这样使应用程序更健壮、更容易测试,而且更容易移植。EJB 3.0简化了在POJO中构建松散耦合的业务组件。
会话bean
在EJB 3.0应用程序中,松散耦合的服务组件通常作为会话bean来实现。会话bean要有一个接口(即业务接口),那样其他应用程序的组件就可以通过它使用其服务。下面的代码为我们的示例投资计算器服务提供了业务接口。根据投资者开始投资时及终止投资时的年龄、基金增长率及每月储蓄额,它只有一个方法来计算总的投资回报。
public interface Calculator {
public double calculate (int start, int end, double growthrate, double saving); }
会话bean类仅仅实现了业务接口。必须通过为其添加无状态或者有状态的注释,告诉EJB 3.0容器这个POJO类是会话bean。有状态的会话bean可以在几个不同的服务请求期间保持客户端状态。与之相反,无状态的会话bean的请求每次都是由随机的会话bean实例来处理。其行为与原来EJB 2.1中的有状态和无状态的会话bean的行为相一致。EJB 3.0容器计算出什么时候为bean对象创建实例,然后通过业务接口来提供。下面是会话bean实现类的代码:
@Stateless
public class CalculatorBean implements Calculator {
public double calculate (int start, int end, double growthrate, double saving) {
double tmp = Math.pow(1. + growthrate / 12., 12. * (end - start) + 1);
return saving * 12. * (tmp - 1) / growthrate; }
}
还可以为一个会话bean指定多个接口-一个用于本地客户端,一个用于远程客户端。只要使用@Local和@Remote注释,就可以区别接口。下面的代码片断显示了CalculatorBean会话bean同时实现了本地接口和远程接口。如果你没有@Local和@Remote注释,会话bean接口就是默认的本地接口。
@Stateless
@Local ({Calculator.class})
@Remote ({RemoteCalculator.class})
public class CalculatorBean implements Calculator, RemoteCalculator {
public double calculate (int start, int end, double growthrate, double saving) {
double tmp = Math.pow(1. + growthrate / 12., 12. * (end - start) + 1);
return saving * 12. * (tmp - 1) / growthrate; }
public String getServerInfo () {
return "This is the JBoss EJB 3.0 TrailBlazer"; }
}
会话bean用户通过Java命令和目录接口(JNDI)得到bean的存根对象。由容器提供的存根对象实现了会话bean的业务接口。针对存根对象的所有调用都被转向容器,并针对可管理的bean实例进行调用。至于无状态的会话bean,每次进行调用时,都能获得新的存根对象。至于有状态的会话bean,必须把存根对象缓存在客户端上,那样容器就知道以后每次调用时为你提供相同的的bean实例。下面的代码片断显示如何调用会话bean。这里介绍获得bean存根对象的一种更简单的方法。
InitialContext ctx = new InitialContext();
cal = (Calculator) ctx.lookup(Calculator.class.getName());
double res = cal.calculate(start, end, growthrate, saving);
会话bean的生命周期管理
为了实现松散耦合,应用程序把会话bean实例的创建、缓存、销毁全部交给EJB 3.0容器(即反向控制设计模式)。而应用程序只处理业务接口。
但如果应用程序需要对会话对象实行粒度更细的控制,该如何呢?譬如说,应用程序可能需要在容器创建会话bean时执行数据库初始化,或者在销毁bean时需要关闭外部连接。只要在bean类中实现生命周期回调方法,就能实现这些操作。这些方法由容器在bean生命周期的不同阶段(如bean创建和销毁)进行调用。在EJB 3.0中,可以指定任何bean方法作为回调,只要为其添加下列注释。不像EJB 2.1里面,所有的回调方法都必须加以实现,即便回调方法是空的;EJB 3.0 bean可以有好多回调方法,可以是任何方法名称。
● @PostConstruct:bean实例创建后,容器立即调用添加了注释的方法。这个注释同时适用于有状态和无状态的会话bean。
● @PreDestroy:容器从对象池当中销毁闲置或者过期的bean实例之前,调用添加了注释的方法。这个注释同时适用于有状态和无状态的会话bean。
● @PrePassivate:如果某个有状态的会话bean实例闲置时间过长,容器就会将它挂起(passivate),并把其状态保存在缓存当中。容器将bean实例挂起之前,调用由这个注释作以标记的方法。这个注释适用于有状态的会话bean。
● @PostActivate:如果客户端再次使用已被挂起的的有状态的会话bean时,新的实例被创建,bean状态被恢复。如果被激活的bean实例准备就绪,就调用由该注释作以标记的方法。这个注释只适用于有状态的会话bean。
● @Init:这个注释为有状态的会话bean指定了初始化方法。它有别于@PostConstruct注释之处在于:在有状态的会话bean中,可以用@Init对多个方法作以标记。不过,每个bean实例只能有一个@Init方法被调用。EJB 3.0容器决定调用哪个@Init方法,具体取决于bean是如何创建的。@PostConstruct方法在@Init方法之后被调用。
生命周期方法的另一个有用注释是@Remove,对有状态的会话bean来说更是如此。应用程序通过存根对象调用使用@Remove标注的方法时,容器就知道在该方法执行完毕后,把bean实例从对象池当中移走。下面是这些生命周期方法注释在CalculatorBean中的一个示例:
@Stateful
public class CalculatorBean implements Calculator, Serializable {
@PostConstruct
public void initialize () {
//初始化历史记录,并从数据库中装入必要数据。 }
@PreDestroy
public void exit () {
// 若有必要,把历史记录保存至数据库中 }
@Remove
public void stopSession () {
// 调用该方法以通知容器,移除该bean实例、终止会话。方法体可以是空的。}
}
消息驱动的bean
会话bean服务通过同步方法调用来提供。另一种重要的松散耦合的服务就是,由入站消息触发的异步服务,入站消息包括电子邮件或者Java消息服务(JMS)消息。EJB 3.0消息驱动的bean(MDB)是为了处理基于消息的服务请求而设计的组件。
MDB类必须实现消息监听器(MessageListener)接口。当容器检测到该bean的消息后,就调用onMessage()方法,并把入站消息作为调用参数传递。MDB会决定在OnMessage()方法中如何处理消息。可以用注释来配置这个MDB监控哪些消息队列。MDB部署后,容器使用注释里面指定的配置信息。在下面的示例中,当容器检测到queue/mdb JMS队列中的入站消息后,就会调用CalculatorBean MDB。MDB会解析消息,并根据消息内容执行投资计算。
@MessageDriven(activateConfig =
{
@ActivationConfigProperty(propertyName="destinationType", ropertyValue="javax.jms.Queue"),
@ActivationConfigProperty(propertyName="destination", propertyValue="queue/mdb")
})
public class CalculatorBean implements MessageListener {
public void onMessage (Message msg) {
try {
TextMessage tmsg = (TextMessage) msg;
Timestamp sent = new Timestamp(tmsg.getLongProperty("sent"));
StringTokenizer st = new StringTokenizer(tmsg.getText(), ",");
int start = Integer.parseInt(st.nextToken());
int end = Integer.parseInt(st.nextToken());
double growthrate = Double.parseDouble(st.nextToken());
double saving = Double.parseDouble(st.nextToken());
double result = calculate (start, end, growthrate, saving);
RecordManager.addRecord (sent, result);
} catch (Exception e) {
e.printStackTrace (); }
}
}
依赖注入
在前面一节中,介绍了如何开发松散耦合的服务组件。然而,为了使用这些服务对象,你需要通过服务器的JNDI来查询存根对象(用于会话bean)或者消息队列(用于MDB)。JNDI查询是把客户端从实际实现的服务对象解除耦合的一个关键步骤。不过,基于字符串名的普通JNDI查询并不方便。以下是几个原因:
● 客户端与服务端必须就基于字符串的名字达成一致。这不是由编译器或者任何部署时间检查所执行的契约。
● 已获取的服务对象在编译时不进行检查,可能会导致运行时出现数据类型转换错误(casting error)。
● 应用程序里面一再出现冗长的查询代码,该代码有自己的try-catch代码块。
EJB 3.0采用了一种简单、便利的方法,把解除耦合的服务对象和资源提供给任何POJO使用。你使用@EJB注释,就可以把EJB存根对象注入到EJB 3.0容器管理的任何POJO中。如果对某字段变量标以注释,容器会在第一次访问之前,为该变量赋予正确的值。下面的示例显示了如何把CalculatorBean无状态会话bean的存根对象注入到CalculatorMDB MDB类中。
public class CalculatorMDB implements MessageListener {
@EJB Calculator cal;
// 使用cal变量
// ... ... }
如果对某个属性的JavaBean风格的设置方法标以注释,属性第一次使用之前,容器会自动用正确的参数调用属性设置方法。下面的代码片断演示了工作过程:
public class CalculatorMDB implements MessageListener {
Calculator cal;
@EJB
public void setCal (Calculator cal) {
this.cal = cal; }
// 使用cal变量
// ... ... }
除@EJB注释外,EJB 3.0还支持@Resource注释注入来自JNDI的任何资源。下面的例子演示了如何注入服务器的默认的TimerService和SessionContext对象,并且演示了如何注入来自JNDI的命名数据库和JMS资源。
@Resource
TimerService tms;
@Resource
SessionContext ctx;
@Resource (name="DefaultDS")
DataSource myDb;
@Resource (name="ConnectionFactory")
QueueConnectionFactory factory;
@Resource (name="queue/A")
Queue queue;
此外,你还可以把容器管理的持久性管理器(即实体管理器——类似Hibernate会话对象)注入到EJB 3.0 POJO中。
为POJO提供容器服务
除了管理松散耦合的服务对象的生命周期和访问外,EJB 3.0容器还通过简单的注释为可管理的POJO提供运行时服务。
事务
最有用的容器服务可能就是事务服务:万一应用程序出现错误或者异常,它可以保证数据库的完整性。你只要为POJO方法添加注释,即可声明事务属性。容器可以在适当的事务上下文中运行方法。譬如说,下面的代码声明:容器应当创建新的事务来运行updateExchangeRate()方法。如果该方法存在,事务就提交。实际上,从updateExchangeRate()里面被调用的所有方法也都在同样的事务上下文中执行,除非以其他方式进行显示声明。updateExchangeRate()方法中执行的数据库操作要么全部成功,要么全部失败。
@Stateless
public class CalculatorBean implements Calculator {
// ... ...
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void updateExchangeRate (double newrate) throws Exception {
// 在循环中更新数据库。
// ... ...
//循环中的操作必须全部成功,否则数据库根本不更新。 }
}
安全
容器还能提供验证用户身份的安全服务,并且可以根据用户角色,限制对可管理的POJO的访问。对每个POJO类而言,你可以使用@SecurityDomain注释指定安全域,它能告诉容器到哪里去找密码和用户角色列表。JBoss里面的other域表明文件是类路径中的users.propertes和roles.properties文件。然后,对于每个方法,你可以使用安全限制注释来指定谁可以运行这个方法。譬如在下面例子中,容器对所有试图执行addFund()方法的用户进行验证,只允许角色是AdminUser的用户才能实际运行。如果你没有登录,或者不是以管理员的身份登录,就会引发安全异常。
@Stateless
@SecurityDomain("other")
public class CalculatorBean implements Calculator {
@RolesAllowed({"AdminUser"})
public void addFund (String name, double growthrate) {
// ... ... }
@RolesAllowed({"AdminUser"})
public void addInvestor (String name, int start, int end) {
// ... ... }
@PermitAll
public Collection < Fund > getFunds () {
// ... ... }
// ... ...
@RolesAllowed({"RegularUser"})
public double calculate (int fundId, int investorId, double saving) {
// ... ... }
}
通用拦截器
事务服务和安全服务都可以被看成是由容器管理的运行时拦截器。容器拦截来自EJB存根对象的方法调用后,为调用添加事务上下文或者安全限制。
在EJB 3.0中,你可以自己编写拦截器来扩展容器服务。使用@AroundInvoke注释,就可以把任何bean方法指定为在其他任何bean方法运行前后执行的拦截器方法。在下面例子中,log()方法是分析及记录其他bean方法的执行时间的拦截器:
@Stateful
public class CalculatorBean implements Calculator {
//被“log()”拦截的bean方法
// ... ...
@AroundInvoke
public Object log (InvocationContext ctx) throws Exception {
String className = ctx.getBean().getClass().getName();
String methodName = ctx.getMethod().getName();
String target = className + "." + methodName + "()";
long start = System.currentTimeMillis();
System.out.println ("Invoking " + target);
try {
return ctx.proceed();
} catch(Exception e) {
throw e;
} finally {
System.out.println("Exiting " + target);
cal.setTrace(cal.getTrace() + "" +"Exiting " + target);
long time = System.currentTimeMillis() - start;
System.out.println("This method takes " + time + "ms to execute");
}
}
}
下篇:可管理的POJO持久性
在Java虚拟机(JVM)里面,所有数据都被建模,并且被封装在树结构的类和对象中。然而,在后端关系数据库中,数据被建模成关系表,它们通过共享的键字段相互关联起来。同一数据却有两个不同的视图,这给企业Java的开发人员带来了挑战:如果你要把数据保存到持久性数据存储区,或者从持久性数据存储区获取数据,就必须在对象和关系表示之间来回转换数据,这个过程就叫作对象-关系映射(ORM)。在Java EE(Java企业版,以前叫J2EE)中,可以通过两个方法来完成对象-关系映射。
● 人工方法:使用Java数据库连接性(JDBC)直接处理持久性——这个简单的解决方法适用于简单的应用程序。JDBC API的类紧密地按照关系数据库里面的表、行和列进行建模。但必须在应用程序的内部对象模型和JDBC对象模型之间进行人工转换,如果应用程序的内部模型已经类似二维关系表,采用JDBC是最佳方法。
● 自动方法:可以把ORM任务交给框架去处理。框架通常提供了可以处理任何数据对象的API。通过这个API,可以保存、获取及查找数据库。框架在后台完成对象-关系的转换。因为针对特定关系的SQL查询不适合对象接口,ORM框架通常定义了自己的查询语言,可以为当前的关系数据库自动生成正确的SQL语句。对数据模型复杂的应用程序而言,基于框架的方法可以节省许多时间,并且减少出错。
ORM 框架
EJB 实体bean是Java EE中的“官方”ORM解决方案。不过在EJB1.x和2.x中,实体bean使用起来非常困难,这有两个原因:
● EJB 1.x和2.x实体bean必须符合严格的组件模型。每个bean类必须实现本地接口和业务接口。它们必须从某些抽象类继承而来,还要实现所有方法,即便许多方法是空的。有了这样一种严格的组件模型,就不可能利用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是两个最成功的Java ORM框架。Hibernate和TopLink都基于POJO:它们不依赖任何预定义的组件模型。相反,它们获得POJO数据对象(采用简单的JavaBean格式)后,会自动解释如何把这些数据对象以及它们之间的关系映射到关系数据库。通常,一个JavaBean类映射到一张数据库表,类之间的关系通过表里面的外来键字段进行映射。可以在简单、直观的XML配置文件里面指定ORM元数据,譬如与JavaBean类相对应的表名以及与属性相对应的列名。可以通过框架中的工具类(如Hibernate中的Session类)来操作这些POJO(譬如保存、获取及查找)。
EJB 3.0建立在 Hibernate和TopLink的思想和成功这一基础上。它为Java EE提供了标准的POJO ORM框架。另外,较之现有的POJO持久性解决方案,EJB 3.0有两项重要创新:
● EJB 3.0让开发人员可以直接在POJO代码中注释映射信息,而不是使用XML文件来指定ORM元数据。譬如说,你可以用注释来指定与每个JavaBean属性相对应的关系列名。读者会在本文后面看到更多的示例。注释使得映射更直观,也更容易维护。
● EJB 3.0为实体bean定义了新的存档格式。每个存档定义了持久性上下文,后端数据库和ORM行为各使用独立的一组配置。本文会在后面讨论持久性上下文。
现在,我们不妨通过几个简单的示例来看一下EJB 3.0是如何实现POJO ORM的。
映射简单对象
在EJB 3.0中,每个实体bean都是JavaBean样式的简单类。为了告诉EJB 3.0容器这个类应当进行映象以实现持久性,应当用@Entity来注释这个类。
每个实体bean类映射到关系数据库表。默认情况下,表名与类名相对应。可以使用@Table注释,为该类指定另一个表名。bean类的每个JavaBean属性映射到表中的列。默认情况下,列名就是属性名。可以通过为属性的设置方法添加@Column注释,来改变这种默认关系。下面是EJB 3.0实体bean类的简单示例:
@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对象联系起来,而一个Address对象只对应于一个Person对象(多对一关系)。
在对象模型中,对象引用负责处理这些关系。譬如说,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 < CreditCard > 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属性。
上面讨论的相互关系只是实体bean关系的一种类型,实体bean类之间的另一种重要关系是继承。
继承
面向对象设计的一个重要概念就是继承。使用继承,你可以为对象创建复杂的树结构,而不需要重复代码。譬如说,顾问(Consultant)是提供有偿咨询服务的人。因而在我们的数据模型中,Consultant类从具有另外收费属性的Person类继承而来。遗憾的是,关系数据库里面没有继承这个概念。ORM框架主要依靠这两种方法来模仿这种行为:
● 框架可以为每个类生成单独的表。子类的表从超类的表当中复制了所有的列。子类和超类的实例被保存在相应的表中。
● 框架可以使用包含所有子类属性列的一张表。两种类的实例保存在同一张表中——超类对象的行把该类没有的列里面的值设为空值。为了让继承映射更健壮,表还有“区别”列,它里面存放的标记表明每行映射到哪个类。
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,当前行就代表Consultant对象。否则,当前行代表普通的Person对象。
持久性档案
鉴于数据模型有一组添加了注释的EJB 3.0实体bean类,就可以把它们捆绑起来,部署到服务器环境中。EJB 3.0为实体bean定义了特殊的存档文件格式,名为持久性存档(文件后缀名为.par)。
.par文件是包括诸多实体bean类组成的一个jar文件,另外加上一个简单的配置文件:META-INF/persistence.xml。persistence.xml文件定义了持久性上下文的名称,它告诉EJB 3.0使用哪个后端数据库(数据源)用于这一组实体bean。persistence.xml还包含了针对特定实现的配置属性。譬如说,JBoss EJB 3.0在Hibernate 3.0上面实现。所以你可以传递persistence.xml文件中的任何Hibernate配置选项。下面是作为示例的persistence.xml文件,专门针对JBoss和Hibernate的配置属性有关于SQL方言和二级缓存。
< entity-manager >
< name >cal< /name >
< jta-data-source >java:/DefaultDS< /jta-data-source >
< properties >
< property name="hibernate.dialect"
value="org.hibernate.dialect.MySQLDialect" />
< property name="hibernate.cache.provider_class"
value="org.jboss.ejb3.entity.TreeCacheProviderHook"/>
< property name="hibernate.treecache.mbean.object_name"
value="jboss.cache:service=EJB3EntityTreeCache"/>
< /properties >
< /entity-manager >
实体管理器
一旦部署了实体bean, 必须通过EJB 3.0实体管理器API来访问及操纵它们。EJB 3.0容器为每个已部署的持久性上下文(即.par文件)提供了一个实体管理器对象。可以从EJB 3.0会话bean POJO,通过@PersistenceContext注释注入实体管理器对象,并传入上下文的名字。
@Stateless
public class ManagerBean implements Manager {
@PersistenceContext (unitName="cal")
protected EntityManager 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 < Person > persons = (Collection < Person >)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");
数据库同步
实体管理器对象用于会话bean时,它与服务器的事务上下文绑在一起。服务器的事务在提交时,实体管理器提交,并且把同步内容发送给数据库。在会话bean中,默认情况下,服务器的事务在调用堆栈末尾处提交。当然,也可以通过注释为每个业务方法指定详细的事务属性。下面的示例演示了如何为会话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相关联,并有自己的数据库设置。它还可以自动与服务器的事务管理器绑在一起。
- 1存款准备金率结构性调整还有一个优势
- 2IE 6中存在的安全隐患
- 3OA选型中的舍与得
- 4OA软件:无差异 不未来
- 5家庭网络构建实用攻略
- 62013年值得关注的五大技术趋势 微软的创新
- 7选OA系统:小投入,大改变
- 8OA选型如挑衣衫:要好看的还是合适的
- 9简述RAID级别
- 10OA观察:决战周边化 杀回工作流
- 11无线局域网站点测量
- 12对数据仓库探讨
- 13动态VPN技术
- 142013年OA市场竞争热点分析
- 15南昌OA系统采购申请单与ERP数据对接
- 16在线OA系统提高了考试管理的信息化水平
- 17企业服务器双机容错使用方法
- 18设置VLAN提高数据效率
- 19内网安全的典型的误区
- 20信息安全没有在十全十美的方案
- 21网络骗术分析
- 22SIP的生产力
- 23OA软件为单位实现强而有力的企业管控
- 24安全的数据隔离与交换系统
- 25同步网状网络提供可伸缩性
- 26四招保障企业数据安全
- 27OA选型的三大陷阱
- 28网络改造重视什么
- 29远程访问不再头疼
- 3030秒清除Windows系统所有垃圾