前面兩期我們提到entity bean在J2EE架構中是用來塑模business object的元件,它同時也是在business tier保存資料的技術之一,它被用來存取保存在EIS tier(Enterprise Information System)的資料,而目前運用的最為普遍的EIS技術則是關聯式資料庫。在關聯式資料庫中,我們以所謂的E-R model(Entity Relationship)來定義表格之間的關係,而這些關係是利用主鍵(Primary Key)與外鍵(Foreign Key)的宣告來表示。由於entity bean所要存取的關聯式資料庫的表格之間有關係存在,因此EJB必須發展出相應的技術來處理entity bean之間的關係,這就是EJB從2.0版後提出的CMR(Container Management Relationship)所要處理的問題。
與CMP相同,我們是透過所謂的Abstract Schema Persistence/Abstract Programming Model來定義entity bean的CMR,這表示我們不需要撰寫存取資料庫的程式碼,我們只需要分別在部署描述子(Deployment Descriptor)與bean class中定義entity bean之間的關係,container的部署工具就會在部署時期會自動產生相應的程式碼。下面我們將以Member與Address兩個entity bean為例子,實際說明CMR的重要觀念與實作方式。
<enterprise-beans>
<entity>
<ejb-name>Member</ejb-name>
<local-home>com.eland.ejb.MemberLocalHome</local-home>
<local>com.eland..ejb.MemberLocal</local>
<ejb-class>com.eland.ejb.MemberBean</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>java.lang.String</prim-key-class>
<reentrant>False</reentrant>
<cmp-version>2.x</cmp-version>
<abstract-schema-name>Member</abstract-schema-name>
<cmp-field><field-name>userid</field-name></cmp-field>
<cmp-field><field-name>username</field-name></cmp-field>
<cmp-field><field-name>password</field-name></cmp-field>
<primkey-field>userid</primkey-field>
</entity>
<entity>
<ejb-name>Address</ejb-name>
<local-home>com.eland.ejb.AddressLocalHome</local-home>
<local>com.eland.ejb.AddressLocal</local>
<ejb-class>com.eland.ejb.AddressBean</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>java.lang.String</prim-key-class>
<reentrant>False</reentrant>
<cmp-version>2.x</cmp-version>
<abstract-schema-name>Address</abstract-schema-name>
<cmp-field><field-name>type</field-name></cmp-field>
<cmp-field><field-name>city</field-name></cmp-field>
<cmp-field><field-name>street</field-name></cmp-field>
<primkey-field>type</primkey-field>
</entity>
</enterprise-beans>
首先我們要說明定義CMR時必須了解的四個概念,關係(Relationship)、角色(Role)、多數性(Multiplicity)、可視性(Navigability)。下圖顯示了這四個概念的關係:
1. <![endif]> 關係
關係表示entity bean之間的連結。在我們的例子中,關係的名字為Member-Address。
2. 角色
每一個關係都有兩個角色,分別代表entity bean連結的兩個方向。角色有其名稱,在我們的例子中,兩個角色分別叫做theMember與addresses。
3. 多數性
意指對一角色而言有多少entity bean能夠參與該關係。兩個entity bean之間能夠存在的關係有一對一、一對多、多對一、多對多等。在我們的例子中,Member與Address之間具有一對多關係,這表示一個Member bean可以有多個Address bean與其連結。我們通常以多數性來決定角色的名稱,以我們的例子而言,由於Member與Address具有一對多關係,因此我們將定冠詞the加在Member之前,而以複數型的addresses命名Address。
4. 可視性
意指一角色是否能夠透過關係被直接存取。可視性可以是單向的,也可以是雙向的。在我們的例子中,箭頭指向的角色是可視的,而這裡的可視性是單向的。從Member到Address的方向來看,addresses這個角色是可視的,而從Address到Member的方向來看,theMember這個角色是不可視的。若一個角色是可視的,則其相關的entity bean必須有local interface。一個沒有local interface的entity bean只能與另一個有local interface的entity bean建立單向的關係。
接下來我們要看如何在部署描述子中運用上面提到的概念定義CMR。下面是Member-Address這組單向的一對多關係在部署描述子中的定義方式:
<relationships>
<ejb-relation>
<ejb-relation_name>Member-Address</ejb-relation-name>
<ejb-relationship-role>
<ejb-relation-role-name>theMember</ejb-relation-role-name>
<multiplicity>Many</multiplicity>
<cascade-delete/>
<relationship-role-source>
<ejb-name>Address</ejb-name>
</relationship-role-source>
</ejb-relationship-role>
<ejb-relationship-role>
<ejb-relation-role-name>Address</ejb-relation-role-name>
<multiplicity>One</multiplicity>
<relationship-role-source>
<ejb-name>Member</ejb-name>
</relationship-role-source>
<cmr-field>
<cmr-field-name>addresses</cmr-field-name>
<cmr-field-type>java.util.Collection</emr-field-type>
</cmr-field>
</ejb-relationship-role>
</ejb-relation>
</relationships>
各標籤的意義如下:
1. <relationships>標籤在部署描述子中位於<enterprise-beans>與<assembly-descriptor>標籤之間,一個部署描述子中只能有一個<relationships>標籤。
2. <relationships>標籤內可以有多組<ejb-relation>標籤,分別定義不同的CMR。每一組<ejb-relation>標籤內可以有一組<ejb-relation-name>標籤用來定義關係名稱。每一組<ejb-relation>標籤內必須有兩組<ejb-relationship-role>標籤用來定義該關係的兩個角色。
3. <ejb-relationship-role>標籤內的<ejb-relation-role-name>標籤用來定義角色名稱。
4. <ejb-relationship-role>標籤內的<relationship-role-source>標籤定義了該角色所參照(reference)的entity bean的名稱。在我們的例子中,theMember這個角色所參照的entity bean為Address。
5. <relationship-role-source>標籤內的<ejb-name>標籤必須存在於同一個部署描述子的<enterprise-beans>標籤中,亦即一CMR的兩個角色所參照的entity bean必須在同一個EJB模組中。
6. <ejb-relationship-role>標籤內的<multiplicity>標籤用來定義該角色所參照的entity bean是一個(One)或多個(Many)。
7. 如果一個角色是可視的,則必須定義<cmr-field>標籤CMR。<cmr-field>標籤內的<cmr-field-name>標籤用來定義CMR的名稱,CMR的名稱必須以小寫開頭,且依慣例與角色名稱相同。每一個<cmr-field-name>在其所參照的bean class裡都必須有抽象的getter/setter方法與其對應,EJB規格書中將這些方法叫做CMR fields。
8. 由於theMember與addresses具有一對多的關係,因此addresses的型態必須是其local interface的集合,我們必須用<cmr-field-type>標籤定義其型別為java..util.Collection或java.util.Set(不允許重覆)。
9. 在theMember這個角色中的<cascade-delete>標籤意謂著若刪除一個Member bean,則其所參照的Address bean也會被刪除,如此可以避免在資料庫裡遺留不會再被使用到的資料。這個標籤可以用於一對一與一對多關係,但不能用於多對一與多對多關係。
以上是一對多關係在部署描述子中的定義方式。若關係為一對一,則兩個角色的<multiplicity>標籤都必須定義為One,且都不需要定義<cmr-field-type>標籤;若關係為多對多,則兩個角色的<multiplicity>標籤都必須定義為Many,且兩者的<cmr-field-type>標籤型態都必須定義為java.util.Collection或java.util.Set。多對一關係同一對多關係。
前面提到,部署描述子中的<cmr-field>標籤必須在bean class中有CMR fields與其對應。宣告CMR fields時必須遵循下列事項:
1. CMR fields的命名格式為get<cmr-field-name>()與set<cmr-field-name>,<cmr-field-name>的值的第一個字母改為大寫,例如getAddress()與setAddress()。
2. get<cmr-field-name>()方法的回傳型態必須與傳入set<cmr-field-name>()方法的參數型態一致。型態必須是其所參照的entity bean的local interface或local interface的集合型態(java.util.Collection或java.util.Set)。
值得注意的是,使用CMR fields並非如同使用CMP fields一樣實際存取資料庫裡的資料,而是參照到其他entity bean(setter方法)或取得所參照的entity bean的local interface reference(getter方法)。因此呼叫一個CMP bean的CMR fields,並不會改變資料庫裡的資料,而是改變兩種具有CMR關係的entity bean之間的參照狀態。接下來我們要看一些實際使用CMR fields的例子:
public abstract class MemberBean implements javax.ejb.EntityBean {
// CMR fields
public abstract java.util.Collection getAddress();
public abstract void setAddress(java.util.Colletion address);
// Business methods
public void addAddress(String type, String city, Sring street) {
InitialContext ctx = new InitialContaxt();
AddressHomeLocal addressHome = ctx.lookup(“AddressHomeLocal”);
AddressLocal address = addressHome.create(type, city, street);
Collection addressHomes = this.getAddress();
addressHomes.add(address);
}
public void updateAddress(String type, String city, String street) {
Collection addressHomes = this.getAddress();
Iterator iter = addressHomes.iterator();
while(iter.hasNext()) {
AddressLocal address = (AddressLocal)iter.next();
if(address.getType.equals(type)) {
address.setCity(city);
address.setStreet(street);
break;
}
}
}
public void removeAddress(String type) {
Collection addressHomes = this.getAddress();
Iterator iter = addressHomes.iterator();
while(iter.hasNext()) {
AddressLocal address = (AddressLocal)iter.next();
if(address.getType.equals(type)) {
iter.remove(addess);
break;
}
}
}
…
}
1. 由於Member與Address具有一對多關係,且Address對Member而言具有可視性,因此我們在Memebr bean class中宣告一組CMR fields,該CMR fields以java.util.Collection為其回傳型態與參數型態。
2. 在上面的範例中,我們宣告了三個使用getAddress() CMR field的business methods。addAddress()方法新增一個Address bean,並建立該entity bean與Member bean之間的關係,意即在使用getAddress() CMR field所取回的集合中新增一個Address bean的local interface reference,而updateAddress()方法與removeAddress()方法則在使用getAddress() CMR field取回的集合中找到合乎條件的Address bean的local interface reference,然後修改或刪除。需要注意的是iter.remove(address)這行程式碼並沒有真的刪除address這個local interface reference所代表的entity bean,被刪除的是entity bean與entity bean之間的參照,而不是entity bean或資料庫裡的資料。
3. 使用setAddress() CMR field要特別注意,因為它意謂著清除原來集合中所有local interface references,另以新的local interface references取代。舉例而言,如果要將兩個不同的Member bean所參照的Address bean對換,我們可以以下面的例子為之:
Collection addressHomesA = memberA.getAddress();
memberB.setAddress(addressHomesA);
但如果只是要將原先為memberA所參照到的一個Address bean由memberA移到memberB,那我們必須先取得該Address bean的local interface reference後,再將其加入memberB所參照的Address bean的local interface references的集合,示範如下:
Collection addressHomesA = memberA.getAddress();
Collection addressHomesB = memberB.getAddress();
Iterator iter = addressHomesA.iterator();
while(iter.hasNext()) {
AddressLocal addressA = (AddressLocal)iter.next();
if(// Condition) {
addressHomesB.add(addressA);
break;
}
}
4. 除了上面的business methods外,我們也可以在新增一個entity bean時,在bean class的create方法中使用CMR fields建立或變更與其他entity bean的參照。依照EJB規格書規定,這必須在ejbPostCreate()方法中為之。
對CMR的介紹到此告一段落,下期我們將介紹與CMP與CMR一樣使用Abstract Schema Persistence/Abstract Programming Model來實作的Query Language與Query Methods。
參考資料
1. Richard Monson-Haefel ---- Enterprise JavaBeans, 3rd Edition, O’Reilly, 2001
2. Ueli Wahli, Wouter Denayer, Lars Schunk, Deborah Shaddon, Martin Weiss ---- EJB 2.0 Development with WebSphere Studio Application Developer, 1st Edition, IBM/Redbooks, 2003