在開始討論怎樣從ejb 2.1遷移到ejb 3.0之前,有必要先了解一下遷移之后將會得到什么:主要來說,ejb 3.0減少了在創建ejb時所需的類、接口、部署描述符的數量。ejb 3.0通過用純舊式java對象(pojo)取代抽象bean類,用純舊式java接口(poji)取代組件與主接口(component & home),簡化了ejb的開發過程,在此,后者是可選項--你不必全部包含進它們。 
  部署描述符--ejb-jar.xml--由其指定了ejb名、bean對象名、接口、查找者方法、容器管理關系(cmr),在此就不再需要其他與開發商相關的部署描述符了,因為已被組件類中的元數據注釋所取代。這就是你為什么需要使用jdk 5.0來開發ejb 3.0應用的原因,因為它們使用了注釋,而注釋在jdk 5.0之前不可用。
  ejb 3.0用javax.persistence.entitymanager api取代了ejb 2.1中的查找者方法,通常ejb 2.1的客戶端應用使用jndi名來獲得一個對實體(entity)及會話(session)bean對象的引用,而ejb 3.0客戶端應用則是使用@resource、@inject和@ejb。
  在ejb 2.1中,可使用javax.ejb包裝類與接口來開發實體與會話,在此,一個會話bean實現了sessionbean接口,而一個實體bean實現了entitybean接口;相比之下,ejb 3.0的會話與實體bean類是pojo,并沒有實現sessionbean和entitybean接口。
  一個ejb 2.1的會話bean類指定了一個或多個ejbcreate方法、回調方法、setsessioncontext方法和業務(business)方法;與此類似,一個ejb 2.1實體指定了ejbcreate()、ejbpostcreate()、回調、容量管理持久性(cmp)、cmr的getter/setter和業務方法。一個ejb 3.0會話bean類只指定了業務方法;同樣地,一個ejb 3.0實體bean只指定了業務方法、對不同bean屬性的getter/setter方法及對bean關系的getter/setter方法。
  ejb 2.1主接口擴展了javax.ejb.ejbhome接口、另有一個本地主接口擴展了javax.ejb.ejblocalhome接口;ejb 2.1的遠程接口擴展了javax.ejb.ejbobject接口,另有一個本地接口擴展了javax.ejb.ejblocalobject接口。在ejb 3.0中,并沒有指定組件與主接口--它們已被poji取代,如果一個會話bean類沒有指定一個業務接口,那么ejb服務器將從會話bean類中為它生成一個poji業務接口。
  請在腦海中記住這些變化,本文的后續部分,將用兩個示例來集中講述把一個會話bean和一個實體bean,從ejb 2.1遷移到ejb 3.0時所需的詳細信息。
  遷移會話bean
  示例中的ejb 2.1會話bean類--bookcatalogbean--指定了一個ejbcreate方法、一個稱為gettitle()的業務方法和一個回調方法:
// bookcatalogbean.java
import javax.ejb.sessionbean;
import javax.ejb.sessioncontext;
public class bookcatalogbean implements sessionbean 
{
 private sessioncontext ctx;
 public string getedition(string title)
 {
  if(title.equals("java & xml"))
   return new string("第二個版本");
  if(title.equals("java and xslt"))
   return new string("第一個版本");
 }
 public void ejbcreate(){}
 public void ejbremove() {}
 public void ejbactivate() {}
 public void ejbpassivate() {}
 public void setsessioncontext(sessioncontext ctx) 
 {this.ctx=ctx;}
}
  在ejb 3.0會話bean中,可使用元數據注釋來指定bean類型,即使用@stateful和@stateless來分別指定stateful(有狀態)或stateless(無狀態)。也可在一個會話bean中用一個業務接口來取代組件與主接口,因為業務接口是一個poji,所以可用@local和@remote來指定其為本地或遠程類型,而一個會話bean可同時實現本地與遠程接口。
  如果在bean類不指定接口類型(本地或遠程),那ejb服務器在默認情況下會自動生成一個本地業務接口,在此也可使用@local和@remote注釋來指定接口類。
  下面的ejb 3.0會話bean是一個pojo,其由前面的bookcatalogbean.java ejb 2.1無狀態會話bean移植而來,注意它使用了@stateless注釋,實現了一個本地業務接口,并在@local注釋中指定了本地接口類名。
// bookcatalogbean.java ejb 3.0 session bean
@stateless
@local ({bookcataloglocal.java})
public class bookcatalogbean implements 
bookcataloglocal
{
 public string getedition(string title)
 {
  if(title.equals("java & xml"))
   return new string("第二個版本");
  if(title.equals("java and xslt"))
   return new string("第一個版本");
 }
}
  另外,也要注意,通過@local注釋,上面的ejb 3.0bean類用一個本地業務接口(poji)取代了ejb 2.1中的組件與主接口。
  遷移ejb會話bean客戶端
  一個ejb 2.1會話bean的客戶端通過使用jndi名可取得一個會話bean對象,如下所示的客戶端使用了bookcataloglocalhome的jndi名取得一個本地主對象,接著調用了create()方法,隨后,客戶端用getedition(string)業務方法輸出特定標題的版本值。
import javax.naming.initialcontext;
public class bookcatalogclient
{
 public static void main(string[] argv)
 {
  try{
   initialcontext ctx=new initialcontext();
   object objref=ctx.lookup("bookcataloglocalhome");
   bookcataloglocalhome cataloglocalhome = (bookcataloglocalhome)objref;
   bookcataloglocal cataloglocal = (bookcataloglocal) cataloglocalhome.
   create();
   string title="java and xml";
   string edition = cataloglocal.getedition(title);
   system.out.println("標題的版本:" + title + " " + edition);
  }
  catch(exception e){}
 }
}
  在ejb 3.0中,可通過依賴性注入,來獲取一個對會話bean對象的引用,這通常由@inject、@resource、@ejb注釋來實現。如下所示的ejb 3.0會話bean客戶端使用了@inject注釋注入到bookcatalogbean類中,仍可由getedition(string)業務方法來獲取標題的版本值。
public class bookcatalogclient
{
 @inject bookcatalogbean;
 bookcatalogbean catalogbean;
 string title="java and xml";
 string edition=catalogbean.getedition(edition);
 system.out.println("標題版本:" + title + " " + edition);
}
  遷移實體bean
  本節講述如何遷移ejb 2.1的實體bean到ejb 3.0。一個ejb 2.1實體bean實現了entitybean接口,其由getter和setter cmp字段方法、getter和setter cmr字段方法、回調方法及ejbcreate/ejbpostcreate方法組成。示例實體bean(見例1)--bookcatalogbean.java,由cmp字段標題、作者、發行者和cmr字段版本組成。
  例1:bookcatalogbean.java
import javax.ejb.entitybean;
import javax.ejb.entitycontext;
public class bookcatalogbean implements entitybean
{
 private entitycontext ctx;
 public abstract void settitle();
 public abstract string gettitle();
 public abstract void setauthor();
 public abstract string getauthor();
 public abstract void setpublisher();
 public abstract string getpublisher();
 public abstract void seteditions(java.util.collection editions);
 public abstract java.util.collection geteditions();
 public string ejbcreate(string title)
 {
  settitle(title);
  return null;
 }
 public void ejbremove() {}
 public void ejbactivate() {}
 public void ejbpassivate() {}
 public void ejbload() {}
 public void ejbstore() {}
 public void setentitycontext(entitycontext ctx)
 {
  this.ctx=ctx;
 }
 public void unsetentitycontext() 
 {
  ctx = null;
 }
}
  而這個ejb 2.1實體bean的ejb-jar.xml部署描述符(見例2)文件,指定了ejb類、接口、cmp字段、ejb ql查詢和cmr關系。bookcatalogbean實體bean定義了一個查找方法findbytitle()、一個cmr字段及版本。
  例2:ejb-jar.xml部署描述符
<?xml version="1.0"?>
<!doctype ejb-jar public
"-//sun microsystems, inc.//dtd enterprise javabeans 2.0//en" 
"http://java.sun.com/dtd/ejb-jar_2_0.dtd">
<ejb-jar>
 <enterprise-beans> 
  <entity>
   <ejb-name>bookcatalog</ejb-name>
   <local-home>bookcataloglocalhome</local-home>
   <local>bookcataloglocal</local>
   <ejb-class>bookcatalogbean</ejb-class>
   <persistence-type>container</persistence-type>
   <prim-key-class>string</prim-key-class>
   <reentrant>false</reentrant>
   <cmp-version>2.x</cmp-version>
   <abstract-schema-name>bookcatalog</abstract-schema-name>
   <cmp-field>
    <field-name>title</field-name>
   </cmp-field>
   <cmp-field>
    <field-name>author</field-name>
   </cmp-field>
   <cmp-field>
    <field-name>publisher</field-name>
   </cmp-field>
   <query>
    <query-method>
     <method-name>findbytitle</method-name>
     <method-params>
      <method-param>java.lang.string</method-param>
     </method-params>
    </query-method>
    <ejb-ql>
     <![cdata[select distinct object(obj) from bookcatalog obj where obj.title = ?1 ]]>
    </ejb-ql>
   </query>
  </entity>
</enterprise-beans>
<relationships>
 <ejb-relation>
  <ejb-relation-name>bookcatalog-editions</ejb-relation-name>
  <ejb-relationship-role>
   <ejb-relationship-role-name>
    bookcatalog-has-editions
   </ejb-relationship-role-name>
   <multiplicity>one</multiplicity>
   <relationship-role-source>
    <ejb-name>bookcatalog</ejb-name>
   </relationship-role-source>
   <cmr-field>
    <cmr-field-name>editions</cmr-field-name>
    <cmr-field-type>java.util.collection</cmr-field-type>
   </cmr-field>
  </ejb-relationship-role>
  <ejb-relationship-role>
   <ejb-relationship-role-name>
    editions-belong-to-bookcatalog
   </ejb-relationship-role-name>
   <multiplicity>one</multiplicity>
   <cascade-delete />
   <relationship-role-source>
    <ejb-name>edition</ejb-name>
   </relationship-role-source>
  </ejb-relationship-role>
 </ejb-relation>
</relationships>
</ejb-jar>
  相比之下,對應于ejb 2.1實體bean類的ejb 3.0實體bean類是一個純舊式java對象(pojo),并且非常簡單(請看例3)。此bean類的ejb 3.0版本使用了元數據注釋@entity,而ejb 2.1部署描述符ejb-jar.xml文件中用元素符指定的查找方法,在ejb 3.0 bean類中,則使用@namedqueries和@namedquery注釋來指定;ejb-jar.xml文件中用元素符指定的cmr關系,在ejb 3.0 bean類中,則用元數據注釋來指定;另外,主要的關鍵字段通過@id注釋來指定。表1中列出了一些ejb 3.0的元數據注釋。
  例3:bookcatalogbean.java
import javax.persistence.entity;
import javax.persistence.namedquery;
import javax.persistence.id;
import javax.persistence.column;
import javax.persistence.onetomany;
@entity
@namedquery(name="findbytitle", querystring =
"select distinct object(obj) from bookcatalog obj where obj.title = ?1")
public class bookcatalogbean
{
 public bookcatalogbean(){}
 public bookcatalogbean(string title)
 {
  this.title=title;
 }
 private string title;
 private string author;
 private string publisher;
 @id
 @column(name="title", primarykey="true")
 public string gettitle(){return title;}
 public void settitle(){this.title=title;}
 public void setauthor(string author){this.author=author;}
 public string getauthor(){return author;}
 public void setpublisher(string publisher) 
 {
  this.publisher=publisher;
 }
 public string getpublisher(){return publisher;}
 private java.util.collection<edition>editions;
 @onetomany
 public void seteditions(java.util.collection editions)
 {
  this.editions=editions;
 }
 public java.util.collection geteditions(){return editions;}
}
  表1:ejb 3.0常用元數據注釋
| 注釋 | 說明 | 注釋元素 | 
| @entity | 注明一個實體bean類。 |  | 
| @table | 注明實體bean表。如果未指定@table,表名與ejb名相同。 | name, schema | 
| @id | 注明一個主要關鍵屬性或字段。 |  | 
| @transient | 注明一個非持久性屬性或字段。 |  | 
| @column | 為一個持久性實體bean屬性注明一個映射欄。 | name、primarykey、nullable、length。默認欄名為屬性或字段名。 | 
| @namedqueries | 注明一組命名查詢。 |  | 
| @namedquery | 注明一個命名查詢或與查找方法相關的查詢。 | name, querystring | 
| @onetomany | 注明一個一對多聯系。 | cascade | 
| @onetoone | 注明一個一對一聯系。 | cascade | 
| @manytomany | 注明一個多對多聯系。 | cascade | 
| @manytoone | 注明一個多對一聯系。 | cascade | 
  ejb 2.1 bean類中的查找方法findbytitle(),在ejb 3.0中則使用相應的@namedquery注釋;ejb 2.1實體bean中的cmr關系,在ejb 3.0實體bean中則使用@onetomany注釋。注釋@id注明了標識符屬性標題,注釋@column指定了與標識符屬性標題對應的數據庫欄。如果一個持久性實體bean屬性未用@column注明,那ejb服務器會假定欄名與實體bean屬性名相同。而瞬態實體bean屬性通常用@transient來注明。  遷移ejb實體bean客戶端
  你可在實體bean主接口或本地主接口中使用create()方法,來創建一個ejb 2.1實體bean主對象或本地主對象。通常,一個ejb 2.1實體bean的客戶端可通過jndi查找來獲取一個實體bean的本地或遠程對象。下面有一段示例代碼,其創建了一個ejb 2.1實體bean的本地主對象。
initialcontext ctx=new initialcontext();
object objref=ctx.lookup("bookcataloglocalhome");
bookcataloglocalhome cataloglocalhome = (bookcataloglocalhome)objref;
  在上面的代碼段中,bookcataloglocalhome是bookcatalogbean實體bean的jndi名。
  在得到一個引用之后,ejb 2.1的客戶端通過create()方法創建了一個本地對象。
bookcataloglocal cataloglocal = (bookcataloglocal) 
cataloglocalhome.create(title);
  在ejb 2.1中,可通過查找方法,從一個本地主對象中取得一個本地或遠程對象。例如,你可像如下所示通過findbyprimarykey方法取得一個本地對象。
bookcataloglocal cataloglocal = (bookcataloglocal) 
cataloglocalhome.findbyprimarykey(title);
  另外在ejb 2.1中,可使用remove()方法移除一個實體bean的實例:
cataloglocal.remove();
  ejb 3.0通過javax.persistence.entitymanager類實現了持久性、查找和移除。表2列出了entitymanager類中用于取代ejb 2.1方法的一些常用方法。
  表2:entitymanager類方法
| entitymanager方法 | 描述 | 
| persist(object entity) | 使一個實體bean實例持久化。 | 
| createnamedquery(string name) | 創建一個query對象的實例,以執行命名查詢。 | 
| find(class entityclass, object primarykey) | 查找一個實體bean實例。 | 
| createquery(string ejbql) | 創建一個query對象,以運行ejbql查詢。 | 
| remove(object entity) | 移除實體bean的一個實例。 | 
  在ejb 3.0實體bean的客戶類中,可使用@resource注釋來注入entitymanager對象。
@resource
private entitymanager em;
  可調用entitymanager.persist()方法來使一個實體bean的實例持久化,例如:
bookcatalogbean catalogbean = new bookcatalogbean (title);
em.persist(catalogbean);
  類似地,可調用entitymanager.find()方法來取得一個實體bean的實例。
 
bookcatalogbean catalogbean = (bookcatalogbean) 
em.find("bookcatalogbean", title);
  在此還可以定義一個相當于命名查詢findbytitle的ejb 3.0客戶類查找方法(與ejb 2.1中的查找方法可不一樣),用createnamedquery(string)方法取得一個query對象。
query query=em.createnamedquery("findbytitle");
  通過setparameter(int paramposition, string paramvalue)或setparameter(string parametername, string value)方法設置query對象的參數,注意此處的參數位置是從0開始的。
query.setparameter(0, title);
  使用query.getresultlist()方法取得bookcatalogbean對象的一個集合,如果確定查詢只返回一個單一的結果,還可以使用getsingleresult()方法代替。
java.util.collection catalogbeancollection = (bookcatalogbean)query.getresultlist();
  最后,用entitymanager.remove(object entity)方法移除實體bean的實例。
bookcatalogbean catalogbean;
em.remove(catalogbean);
  例4演示了一個完整的ejb 3.0實體bean的無狀態會話bean客戶類。
  例4:bookcatalogclient類
import javax.ejb.stateless;
import javax.ejb.resource;
import javax.persistence.entitymanager;
import javax.persistence.query;
@stateless
@local
public class bookcatalogclient implements bookcataloglocal
{
 @resource
 private entitymanager em;
 public void create(string title)
 {
  bookcatalogbean catalogbean=new bookcatalogbean(title);
  em.persist(catalogbean);
 }
 public bookcatalogbean findbyprimarykey(string title)
 {
  return (bookcatalogbean)em.find("bookcatalogbean", title);
 }
 public java.util.collection findbytitle(string title)
 {
  query query=em.createnamedquery("findbytitle");
  query.setparameter(0, title);
  return (bookcatalogbean)query.getresultlist();
 }
 public void remove(bookcatalogbean catalogbean)
 {
  em.remove(catalogbean);
 }
}
  以上的示例演示了如何把一個會話bean和實體bean從ejb 2.1遷移到ejb 3.0,從ejb 2.0遷移的情況也與此類似。
  在本文完稿時,已有一些應用服務器支持ejb 3.0規范,如jboss應用服務器、oracle應用服務器及caucho應用服務器。不幸的是,這些應用服務器對ejb 3.0的實現會有所不同----它們可能沒有實現全部的ejb 3.0特性,所以,在開始編寫程序之前,一定要仔細閱讀相關應用服務器提供的文檔說明。