分享
 
 
 

关于EJB的持久化对象技术分析

王朝java/jsp·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

表格型的关系型数据库与树型java对象之间的映射问题是一个至今争论不休的问题,好在现在已经有了一些好的解决方案。在本文中,我们将介绍EJB技术是怎样用自已特定的方式来解决这个问题的。

只要是涉及到保存及查询信息 ,那绝大多数应用程序都需要与关系数据库打交道。但由于关系数据库与Java对象在结构上有着本质的区别,关于它们之间的映射关系对于那些Java开发者们来说,是一个很令人头痛的问题。关系型数据库是以表格方式存储数据的,而Java对象是以树型方式表现的。这种类型上的不匹配引发了各种各样的对象持久化解决方案,用来缩小关系世界与对象世界之间的鸿沟。EJB框架正是这种解决方案之一。

对象的持久性

目前有很多不同的工具出现,使得开发人员可以将JAVA对象转化为数据库中的字段或记录,或者将数据库中的字段或记录还原为JAVA对象。这些处理涉及到要将树型的JAVA对象序列化到数据库中,反之亦然。此工作的核心是怎样在保证最小性能损失的前提下,来完成这项工作。

EJB框架提供了这样一个对象持久化机制。我们将在本文中讨论这种机制,但首先我们还是对EJB构架作一个全面的熟悉。

企业级JavaBeans(EJB)

EJB技术可以这样定义:它是一个基于JAVA服务端的,为分布式应用提供的一个可复用的组件框架。所有的商业逻辑、部署定义、对象持久性都由这个框架统一治理,EJB框架的一些特色如下:

· EJB是一种运行在服务端环境下的JAVA对象。

· EJB能分布在不同的机器上进行远程访问,但对客户端来而言,调用EJB与调用本地JavaBean一样方便。

· EJB容器对EJB进行统一治理。

尽管企业级JavaBean的名字与普通JavaBean在命名上有些相像,但它们在设计上有着本质上的区别。为了能让你更清楚地熟悉到这点,我们最好先了解一下EJB的基本概念、几种EJB组件模式和其配置环境。

EJB的运行环境

从本质上来说,EJB只是实现了特定接口的普通JAVA对象,但这个对象必须运行在一个特定的环境:EJB容器中。假如脱离了EJB容器,EJB是无法运行的。EJB与EJB容器之间的关系有时候被称为"反向调用"――或者叫"好莱坞原理"(别联系我,到时候我会给你打电话的)。

EJB容器是一种用来治理EJB的运行时环境。它容纳并治理不同类型的EJB,这与JAVAservlet容器治理servlet有些类似。EJB容器负责初始化EJB,并给其提供系统级的服务。

当客户端程序要调用某一个EJB时并不直接与EJB打交道,客户端与EJB被容器隔离起来。

EJB容器提供的服务

当开发者创建一系列的类与接口,用来组成一个EJB时,容器会为他们提供如下的系统级服务:

· 事务处理

· 安全治理

· EJB的持久化治理

· EJB的远程访问

· EJB的生命周期治理

· 数据库连接池

· EJB的实例池治理

由于EJB容器负责为EJB提供这种底层服务,使得一个EJB开发者只需关注具体应用的商业逻辑,从而减少了很多不必要的麻烦。

EJB的类型

EJB规范定义了以下三种不同类型的EJB类型:

· 消息驱动EJB(MDB)

· 会话EJB

· 实体EJB

当客户端与会话EJB或实体EJB交互时,它们的通信方式是同步通信。而消息驱动EJB(MDB)则只与JMS进行交互,它相当于JMS中的一个发布/订阅主题。

消息驱动EJB

消息驱动EJB工作在异步通信模式下。一个消息驱动EJB充当一个消息侦听者的角色,它侦服从JMS的发布/订阅主题中传来的消息。

EJB容器治理着消息驱动EJB的生命周期,然而与会话EJB和实体EJB不同之处在于客户端并不能直接调用它的方法。消息驱动EJB是通过一个名为onMessage的回调函数来接收客户端的消息的。

会话EJB

会话EJB的特点是不能同时被多个客户端共享。当客户端调用会话EJB的方法时,先经过EJB容器处理,然后再由容器对会话EJB进行调用。会话EJB处理开发者编写商业逻辑,容器再将处理结果返回给客户端。会话EJB不能在多个会话中持久保存。它分为两种类型:有状态的会话EJB和无状态的会话EJB。

有状态的会话EJB

当一个客户端与某一个有状态的会话EJB开启一个会话时,这个EJB为客户端维护了一个会话状态。这暗示着客户端向此EJB发出不同的调用请求之间保证EJB的成员变量值不会丢失。

一旦客户端结束与有状态的会话EJB的交互后,EJB容器会自动销毁它。于是整个会话结束,并且此有状态的会话EJB所保存的状态数据会全部丢失。

无状态会话EJB

无状态会话EJB并不为客户端保存任何状态数据。你可以这样认为:客户端每次对无状态会话EJB的调用都会产生一个新的EJB实例,因此所有的状态信息都不会保存。 同样,EJB容器也不会持久化任何无状态会话EJB,因此开发者必须意识到客户端与无状态会话EJB之间进行交互时,所有的状态数据都是临时的。无状态会话EJB的这种特性使得容器可以重复地使用它的实例,因此无状态会话EJB能得到比有状态会话EJB更好的性能。

实体EJB

实体EJB表达的的是一种持久存储的商业逻辑,通常存储于关系型数据库中。实体EJB与关系型数据库有如下的相似之处:

· 实体EJB是持久的――它可以在应用程序的生命周期之外存在,甚至可以在EJB容器的生命周期以外存在。

· 实体EJB答应共享访问――多个客户端可以共享同一个实体EJB,而容器负责治理它们之间的同步。

· 实体EJB有主键――主键用来确定实体EJB的一个唯一实例,利用它可以找到一个特定的持久化实体。

· 实体EJB有事务的概念――由于客户端能并发访问并修改它的数据,因此事务治理是非常重要的。事务治理属性被显示地定义在部署描述文件中,而容器负责治理事务的边界。

要实现对象-关系映射,那实体EJB必须能提供插入、更新、查询、删除的操作。而用于治理实体EJB对象与数据源之间的映射的过程被称为持久化。换句话说,持久化是一个将信息写入外部数据源的一个过程。EJB规范定义了实体EJB的两种持久化方式:Bean自身治理的持久化(BMP)和容器治理的持久化(CMP)。

Bean自身治理的持久化(BMP)

假如你选用BMP,那你必须在你的代码中负责维护所有的持久化发。那么所有的数据层访问代码都必须由开发者来完成,这种方式能带给开发者更大的灵活性。

容器治理的持久化(CMP)

假如你选用CMP,那你不用编写数据层访问代码,EJB容器将会为你治理所有的持久化。因此,数据层访问代码与数据源之间是松耦合的。这能减轻开发者的代码编写量,并且使得CMP能部署到不同厂商的应用服务器中,也不必关心具体的数据源(参见图1)。

图1会话EJB与实体EJB的关系:此图显示了EJB容器在客户端与EJB实例中充当的代理角色。

EJB部署与运行时环境

我们将以JBoss3.0.2作为EJB部署与运行时环境的服务器。我们将设计一个简单的WEB应用,它答应创建用户帐号,用户通过访问WEB浏览器,而WEB浏览器通过调用一个servlet来取得这个帐号,这个servlet与一个实体EJB相互通信(参见图2)。

图2.通过web访问EJB:此图显示了一个客户端请求是怎样从客户端传到应用层的。当这个作为控制器的servlet接收到客户请求后,它将这个请求转化一个业务请求并向业务服务层调用相应的服务。业务服务层使用一个或多个实体EJB来从数据层中取得或保存数据。

编写并部署一个实体EJB

以下四个步骤是开发一个实体EJB的典型流程:

1. 为你的实体EJB编写相应的类及接口

2. 编写相应的部署描述文件

3. 将实体EJB及相应的部署描述文件打包成为一个jar文件

4. 部署此实体EJB

一个实体EJB至少由以下三个类(接口)组成:

组件接口――在本例中我们只考虑从同一JVM虚拟机中访问实体EJB,因此我们需要继续javax.ejb.EJBLocalObject接口。

2. package com.jeffhanson.datatier.ejb;

3.

4. import javax.ejb.EJBLocalObject;

5.

6. public interface LocalUser extends EJBLocalObject

7. {

8. public String getUserID(); //主键

9. public String getFullName();

10. public String setAddress(String address);

11. public String getAddress();

12. public String setCity(String city);

13. public String getCity();

14. public String setState(String state);

15. public String getState();

16. public String setZip(String zip);

17. public String getZip();

}

18. Home接口――同样,由于我们处于同一JVM虚拟机中,因此我们需要继续javax.ejb.EJBLocalHome接口。

19. package com.jeffhanson.datatier.ejb;

20.

21. import javax.ejb.CreateException;

22. import javax.ejb.FinderException;

23. import javax.ejb.EJBLocalHome;

24. import java.util.Collection;

25. public interface LocalUserHome extends EJBLocalHome

26. {

27. public LocalUser create(String userID,

28. String fullName,

29. String address,

30. String city,

31. String state,

32. String zip)

33. throws CreateException;

34.

35. public Collection findByFullName(String fullName)

36. throws FinderException;

37.

38. public LocalUser findByPRimaryKey(String userID)

39. throws FinderException;

}

Bean类――假如你要开发会话EJB,那么需要实现javax.ejb.sessionBean接口,假如是实体EJB,那么需要实现javax.ejb.EntityBean接口(参见列表1)。

列表1. EJB类

package com.jeffhanson.datatier.ejb;

import javax.ejb.EntityBean;

import javax.ejb.EntityContext;

import javax.ejb.CreateException;

import java.util.Locale;

public class UserEJB

implements EntityBean

{

// 地区缺省设为美国英语

private Locale locale = Locale.US;

transient private EntityContext ctx;

public String USERID;

public String FULLNAME;

public String ADDRESS;

public String CITY;

public String STATE;

public String ZIP;

public UserEJB()

{}

public void setLocale(Locale locale)

{

this.locale = locale;

}

//访问CMP域相关的方法

public void setUserID(String userID)

{

USERID = userID;

}

public String getUserID() //主键

{

return USERID;

}

public void setFullName(String fullName)

{

FULLNAME = fullName;

}

public String getFullName()

{

return FULLNAME;

}

public void setAddress(String address)

{

ADDRESS = address;

}

public String getAddress()

{

return ADDRESS;

}

public void setCity(String city)

{

CITY = city;

}

public String getCity()

{

return CITY;

}

public void setState(String state)

{

STATE = state;

}

public String getState()

{

return STATE;

}

public void setZip(String zip)

{

ZIP = zip;

}

public String getZip()

{

return ZIP;

}

public String ejbCreate(String userID,

String fullName,

String address,

String city,

String state,

String zip)

{

System.out.println("ejbCreate called with userID: " + userID);

setUserID(userID);

setFullName(fullName);

setAddress(address);

setCity(city);

setState(state);

setZip(zip);

return userID;

}

public void ejbPostCreate(String userID,

String fullName,

String address,

String city,

String state,

String zip)

throws CreateException

{

// 容器在调用ejbCreate()方法后会自动调用它

System.out.println("ejbPostCreate called with userID: " + userID);

}

public void setEntityContext(EntityContext ctx)

{

this.ctx = ctx;

System.out.println("setEntityContext called");

}

public void unsetEntityContext()

{

ctx = null;

}

public void ejbActivate()

{

// 当此EJB被载入内存之前容器会自动调用它

}

public void ejbPassivate()

{

// 当此EJB被交换入固定存储器之前

// 容器会自动调用它

}

public void ejbLoad()

{

// 容器调用,用来更新实

// 体EJB的状态

}

public void ejbStore()

{

// 容器调用,用来将实体

// EJB的状态存储入数据库中

}

public void ejbRemove()

{

// 当实体EJB从数据中删除之前被

// 容器调用

}

}

部署描述文件

要将你开发的EJB部署到EJB容器中去的话,那你必须为此容器提供一个部署描述文件。部署描述文件是一个xml格式的文档,文件名为ejb-jar.xml,里面包含有Bean的持久类型以及事务属性。你必须将这个文件与编写的Java类一起打包到一个jar或ear文件中去。

ejb-jar.xml是由SUN公司提供的一个标准部署描述文件,JBoss还有另外一个名为jaws.xml的部署描述文件(参见列表2),这个文件描述了CMP定义以及其持久属性。

列表2. The ejb-jar.xml Deployment Descriptor

<?xml version="1.0"?>

<!DOCTYPE ejb-jar PUBLIC

"-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN"

"http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd">

<ejb-jar>

<display-name>Users</display-name>

<enterprise-beans>

<entity>

<description>封闭一个用户对象</description>

<ejb-name>UserEJB</ejb-name>

<local-home>com.jeffhanson.datatier.ejb.LocalUserHome</local-home>

<local>com.jeffhanson.datatier.ejb.LocalUser</local>

<ejb-class>com.jeffhanson.datatier.ejb.UserEJB</ejb-class>

<persistence-type>Container</persistence-type>

<prim-key-class>java.lang.String</prim-key-class>

<reentrant>False</reentrant>

<cmp-field><field-name>USERID</field-name></cmp-field>

<cmp-field><field-name>FULLNAME</field-name></cmp-field>

<cmp-field><field-name>ADDRESS</field-name></cmp-field>

<cmp-field><field-name>CITY</field-name></cmp-field>

<cmp-field><field-name>STATE</field-name></cmp-field>

<cmp-field><field-name>ZIP</field-name></cmp-field>

<primkey-field>USERID</primkey-field>

</entity>

</enterprise-beans>

<assembly-descriptor>

<container-transaction>

<method>

<ejb-name>UserEJB</ejb-name>

<method-name>*</method-name>

</method>

<trans-attribute>Required</trans-attribute>

</container-transaction>

</assembly-descriptor>

</ejb-jar>

列表2描述了主键的名字和类型,以及实体EJB的哪些域将被持久化。假如关系数据库中没有一个名为"UserEJB"的表的话,那容器会自动建立一个相应的表。

在列表3中,每一个域都定义了一个相应的cmp-field元素。当容器自动建立一个新表时,它会由此得知要创建那些新的字段,并将域与特定的字段对应起来。

列表3. jaws.xml部署描述文件

<?xml version="1.0" encoding="ISO-8859-1"?>

<jaws>

<datasource>java:/DefaultDS</datasource>

<type-mapping>Hypersonic SQL</type-mapping>

<enterprise-beans>

<entity>

<ejb-name>UserEJB</ejb-name>

<create-table>true</create-table>

<table-name>UserEJB</table-name>

<remove-table>false</remove-table>

<tuned-updates>false</tuned-updates>

<read-only>false</read-only>

<time-out>300</time-out>

<cmp-field>

<field-name>USERID</field-name>

<column-name>USERID</column-name>

</cmp-field>

<cmp-field>

<field-name>FULLNAME</field-name>

<column-name>FULLNAME</column-name>

</cmp-field>

<cmp-field>

<field-name>ADDRESS</field-name>

<column-name>ADDRESS</column-name>

</cmp-field>

<cmp-field>

<field-name>CITY</field-name>

<column-name>CITY</column-name>

</cmp-field>

<cmp-field>

<field-name>STATE</field-name>

<column-name>STATE</column-name>

</cmp-field>

<cmp-field>

<field-name>ZIP</field-name>

<column-name>ZIP</column-name>

</cmp-field>

</entity>

</enterprise-beans>

</jaws>

UserEJB的部署方法如下:先将部署描述文件与编译好的类一起打成一个jar包,然后将这个包放在JBoss服务器的deploy目录下就可以了,JBoss会自动发现这个包,并自动进行部署。

EJB客户端

在本例中,你可以在同一JVM虚拟机中访问EJB。这种设计简化了我们的一些工作,要获得EJB的home接口,我们需要进行一个非凡的下溯造型。列表4简述了UserService对象,它用来访问我们的EJB。

列表4. UserService对象

public class UserService

{

private static LocalUserHome home = null;

private static LocalUserHome getUserHome()

throws NamingException

{

if (home != null)

{

return home;

}

// 取得一个上下文

InitialContext ctx = new InitialContext();

// 取得对UserEJB的一个引用

System.out.println("Looking up EJB...");

Object objRef = ctx.lookup("local/UserEJB");

// 取得UserEJB的home接口

home = (LocalUserHome)objRef;

return home;

}

public static UserInfo getUser(String userID)

{

UserInfo userInfo = null;

try

{

LocalUserHome home = getUserHome();

LocalUser localUser = null;

try

{

System.out.println("Finding user...");

localUser = home.findByPrimaryKey(userID);

}

catch (FinderException e)

{

}

if (localUser == null)

{

System.out.println("Creating user...");

localUser = home.create(userID,"John " + userID + " Doe","123 anywhere st.","Seattle","WA","87654");

}

System.out.println("EJB returned User ID: " + localUser.getUserID());

userInfo = convertToUserInfo(localUser);

System.out.println("User FullName: " + localUser.getFullName());

}

catch (Exception e)

{

System.err.println(e.toString());

}

return userInfo;

}

private static UserInfo convertToUserInfo(LocalUser localUser)

{

UserInfo userInfo;

userInfo = new UserInfo();

userInfo.setId(localUser.getUserID());

userInfo.setFullName(localUser.getFullName());

userInfo.setAddress(localUser.getAddress());

userInfo.setCity(localUser.getCity());

userInfo.setState(localUser.getState());

userInfo.setZip(localUser.getZip());

return userInfo;

}

}

请注重在列表4中,UserService对象首先试着通过findByPrimaryKey()方法来找到一个EJB的实例,并在这个方法中传递一个用户ID的参数。假如没有找到实体EJB的实例,UserService对象将调用EJB的home接口中的create()方法来创建一个新的EJB实例。

findByPrimaryKey()方法在调用过程中首先被EJB容器拦截,EJB容器试着从数据库中找出一个与用户ID相同主键的记录。create()方法将在数据库中插入一个主键等于用户ID的记录。

EJB查询语言(EJBQL)

EJB规范提供了一种名为EJBQL的对象查询语言,用来查询CMP。这种对象查询语言实际上被容器转化为SQL语言。

EJBQL语言用来实现EJB的home接口中的find()查询方法,并执行实体EJB定义的一些内部的select方法。这些EJBQL一般放在应用程序的部署描述文件中。

以下XML代码(选自于jaws.xml文件)定义了在UserEJB的home接口中findByState()方法与相应EJBQL之间的映射。这个示例程序用这个EJBQL查询语言及相应的find()方法寻找给出状态的某个用户。

<entity>

...

<query>

<query-method>

<method-name>findByState</method-name>

<method-params>

<method-param>java.lang.String</method-param>

</method-params>

</query-method>

<ejb-ql>

[!CDATA[

SELECT DISTINCT object(u)

FROM UserEJB u

WHERE u.STATE = ?1]]

</ejb-ql>

</query>

</entity>

在以上的定义当中,ejb-ql标签定义了实际使用的EJB QL。当你只想查找一个特定类型的对象时,必须要在查询语言中加入object要害字。另外这个查询中的"?1"的作用类似于JDBC中的PrepareStatement,表明这个查询是一个参数化查询。假如有多个参数,则依次为"?2"、"?3"以此类推,这些参数与查询方法findByState()中的参数表从"?1"开始一一对应。

在上例中,"?1"将被findByState()方法的第一个名为state的参数所取代。除此之外,CMP还有一个名为findAll()或findByPrimaryKey()的

内置方法,你只需要定义它们,而不需实现任何代码,EJB容器会为你自动生成关于这两个方法的代码。

在列表5中,UserService类定义了一个名为getUsersByState()的方法。这个方法调用UserEJB的home接口上的findByState()方法,EJB容器会拦截这个方法,然后执行定义在jaws.xml文件中的EJBQL查询语言。

列表5. 通过用户状态来查找用户信息

public static UserInfo[] getUsersByState(String state)

{

UserInfo[] users = null;

// 找出所有收入高于John的职员

try

{

LocalUserHome home = getUserHome();

Collection userList = home.findByState(state);

System.out.println("Found userList");

if (userList != null)

{

users = new UserInfo[userList.size()];

int i = 0;

Iterator iter = userList.iterator();

while (iter.hasNext())

{

LocalUser localUser = (LocalUser)iter.next();

users[i++] = convertToUserInfo(localUser);

}

}

}

catch (NamingException e)

{

System.err.println(e.toString());

}

catch (FinderException e)

{

System.err.println(e.toString());

}

return users;

}

由于Java树型对象与表格型的关系数据库中在构架上的差异,对于开发者来说,将Java对象持久化到关系型数据库中这一工作是一件非常复杂的事情。它们之间的这种差异引发了若干种对象持久技术的产生,以使得关系型世界与对象世界之间的鸿沟日益缩小。EJB框架提供了一个基于容器治理的持久化机制,假如使用得当,将会是一种提供给开发者的优秀解决方案

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有