欢迎光临
我们一直在努力

关于EJB的持久化对象技术分析-JSP教程,面向对象/设计

建站超值云服务器,限时71元/月

摘自久久学院

表格型的关系型数据库与树型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,这与java servlet容器管理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查询语言(ejb ql)

  ejb规范提供了一种名为ejb ql的对象查询语言,用来查询cmp。这种对象查询语言实际上被容器转化为sql语言。

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

  以下xml代码(选自于jaws.xml文件)定义了在userejb的home接口中findbystate()方法与相应ejb ql之间的映射。这个示例程序用这个ejb ql查询语言及相应的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文件中的ejb ql查询语言。

  列表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框架提供了一个基于容器管理的持久化机制,如果使用得当,将会是一种提供给开发者的优秀解决方案

赞(0)
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com 特别注意:本站所有转载文章言论不代表本站观点! 本站所提供的图片等素材,版权归原作者所有,如需使用,请与原作者联系。未经允许不得转载:IDC资讯中心 » 关于EJB的持久化对象技术分析-JSP教程,面向对象/设计
分享到: 更多 (0)