Entity Bean(2)
在上一節我們簡略地介紹了entity bean的生命週期的各種狀態與相關的life-cycle與call back方法,在本節中我們則要以實際的程式碼說明CMP與BMP在實作這些life-cycle與call back方法時的異同與值得注意之處。但無論是CMP bean或BMP bean,開發人員都必須提供下列檔案:
1. Bean class
Bean class必須視其為CMP或BMP,宣告或實作其屬性的getter/setter方法、call back方法、與home interface相應的life-cycle方法。
2. Primary key
Entity bean的primary key的類型必須是任何繼承或實作java.io.Serializable的物件或class。如果primary key field是基本類型或複合型的primary key,則我們必須提供另外的class檔將其包裹起來使用。
3. Home interface
我們必須在Home interface上定義與entity bean生命週期有關的life-cycle方法,如create()、findBy()等方法。
4. Component interface
我們在這裡定義client端所能使用的business方法。
5. Deployment descriptor(部署描述子)
我們在此設定與entity bean及其背景服務有關的訊息。
|
CMP bean
在EJB 1.1規格書中,CMP與BMP都是concrete classes,但在EJB 2.0規格書中,則將CMP規定為abstract classes。開發人員必須將bean class宣告為abstract,並為每一個定義在部署描述子中與資料庫欄位對應的CMP field,宣告抽象的getter/setter方法。在部署階段,部署工具會產生abstract class的subclass以實作這些抽象的getter/setter方法,以及其他life-cycle與call back方法。以下是相關的部署描述子、bean class以及interface的範例。:
Code 1:Deployment Descriptor(ejb-jar.xml) Fragment for Member Entity Bean(CMP)
10 <entity>
11 <display-name>MemberEJB</display-name>
12 <ejb-name>MemberEJB</ejb-name>
13 <local-home>com.eland.ejb.MemberLocalHome</local-home>
14 <local>com.eland..ejb.MemberLocal</local>
15 <home>com.eland.ejb.MemberRemoteHome</home>
16 <remote>com.eland.ejb.MemberRemote</remote>
17 <ejb-class>com.eland.ejb.MemberBean</ejb-class>
18 <persistence-type>Container</persistence-type>
19 <prim-key-class>java.lang.String</prim-key-class>
20 <reentrant>False</reentrant>
21 <cmp-version>2.x</cmp-version>
22 <abstract-schema-name>Member</abstract-schema-name>
23 <cmp-field>
24 <description>Primary key</description>
25 <field-name>userid</field-name>
26 </cmp-field>
27 <cmp-field>
28 <description></description>
29 <field-name>username</field-name>
30 </cmp-field>
31 <cmp-field>
32 <description></description>
33 <field-name>password</field-name>
34 </cmp-field>
35 <query>Implemetation of QL</query>
36 <primkey-field>userid</primkey-field>
36 </entity>
Code 2:Entity Bean Implementation for Member Entity Bean(CMP)
1 package com.eland.ejb;
2
3 import javax.ejb.*;
4 import javax.naming.*;
5
6 public abstract class MemberBean implements EntityBean {
7
8 private EntityContext context;
9
10 // virtual persistence fields
11 public abstract String getUserid();
12 public abstract void setUserid();
13
14 public abstract String getUsername();
15 public abstract void setUsername();
16
17 public abstract String getPassword();
18 public abstract void setPassword();
19
20 // container callback methods
21 public String ejbCreate(String userid, String username, String password)
22 throws CreateException {
23 setUserid(userid);
24 setUsername(username);
25 setPassword(password);
26 return null;
27 }
28
29 public void postCreate(String userid, String username, String password)
30 throws CreateException {
31 }
32
33 public void setEntityContext(EntityContext ctx) {
34 context = ctx;
35 }
36
37 public void unsetEntityContext() {
38 context = null;
39 }
40
41 public void ejbRemove() {}
42 public void ejbActivate() {}
43 public void ejbPassavate() {}
44 public void ejbLoad() {}
45 public void ejbStore() {}
46 }
Code 3:Local Component Interface for Member Bean
1 package com.eland.ejb;
2
3 import javax.ejb.*;
4
5 public interface MemberLocal extends EJBLocalObject {
6 public String getUserid();
7 public String getUsername();
8 public String getPassword();
9 public void remove();
9 }
Code 4:Local Home Interface for Member Bean
1 package com.eland.ejb;
2
3 import javax.ejb.*;
4
5 public interface MemberLocalHome extends EJBLocalHome {
6 public MemberLocal create(String userid, String username, String password)
7 throws CreateException;
8 public MemberLocal findByPrimaryKey(String userid)
9 throws FinderException;
1 public Collection findByName(String name)
10 throws FinderException;
11 }
Code 5:Remote Component Interface for Member Bean
1 package com.eland.ejb;
2
3 import javax.ejb.EJBObject;
4 import java.rmi.RemoteException;
5
6 public interface MemberRemote extends EJBObject {
7 public String getUsername() throws RemoteException;
8 public void setUsername(String name) throws RemoteException;
9 public String getPassword() throws RemoteException;
10 public void setPassword(String pw) throws RemoteException;
11 public void remove() throws RemoteException;
12 }
Code 6:Remote Home Interface for Member Bean
2 package com.eland.ejb;
3
4 import javax.ejb.EJBHome;
5 import javax.ejb.CreateException;
6 import javax.ejb.FinderException;
7 import java.rmi.RemoteException;
8 import java.util.Collection;
9
10 public interface MemberRemoteHome extends EJBHome {
11 public MemberRemote create(String userid, String username, String password)
12 throws RemoteException, CreateException;
13 public MemberRemote findByPrimaryKey(String id)
14 throws RemoteException, FinderException;
15 public Collection findByName(String name)
16 throws RemoteException, FinderException;
17 }
在編寫CMP bean的程式碼時需要注意幾件事:
1. Entity bean的bean class必須實作javax.ejb.EntityBean,home interface與component interface則必須分別繼承javax.ejb.EJBLocalHome與javax.ejb.EJBLocalObject;如果有remote home interface與remote interface,則必須分別繼承javax.ejb.EJBHome與javax.ejb.EJBObject。
2. Bean class中必須宣告與所有定義在部署描述子中的CMP fields相應的getter/setter方法。
3. Bean class中的ejbCreate()方法與ejbPostCreate()方法必須成對出現,並接受相同的參數傳入。
4. Bean class中可以有多對ejbCreate()方法與ejbPostCreate()方法,bean class中有幾對ejbCreate()方法與ejbPostCreate()方法,home interface上就必須有幾個create()方法,並且傳入的參數要一致。
5. Home interface中必須要有findByPrimaryKey()方法,但bean class裡不必有相應的方法,實作部分於部署階段由部署工具產生。除了findByPrimaryKey()方法以外的findBy()方法,則必須在部署描述子中宣告與其對應<query>標籤與QL語法,我們將在下期介紹QL語法的使用。
6. Home interface上所有create()方法必須宣告丟出CreateException,findBy()方法必須丟出FinderException。所有remote interface上的方法必須宣告丟出RemoteException。
7. 開發人員不需實作CMP bean class中的call back方法。而在屬於life-cycle方法的ejbCreate()方法中只需要初始化CMP fields即可。
|
BMP bean
前面提到,BMP bean將資料保存的責任交由開發人員負責,因此開發人員必須在BMP bean的bean class中實作存取資料庫的程式碼,而這些程式碼主要就是寫在所謂的call back與life-cycle方法中。實作這些程式碼的目的在於在適當時機對entity bean與資料庫裡的資料作同步化的動作,但何時是適當的時機則由EJB Container視系統狀況而定。以下是BMP bean的部署描述子、bean class的範例。由於CMP bean與BMP bean的interfaces並無規格上的分別,因此BMP bean的interface部分請參考前面CMP bean interfaces的範例(Code 3至Code 6)。
Code 7:Deployment Descriptor(ejb-jar.xml) Fragment for Member Entity Bean(BMP)
10 <entity>
11 <display-name>MemberEJB</display-name>
12 <ejb-name>MemberEJB</ejb-name>
13 <local-home>com.eland.ejb.MemberLocalHome</local-home>
14 <local>com.eland..ejb.MemberLocal</local>
15 <home>com.eland.ejb.MemberRemoteHome</home>
16 <remote>com.eland.ejb.MemberRemote</remote>
17 <ejb-class>com.eland.ejb.MemberBean</ejb-class>
18 <persistence-type>Bean</persistence-type>
19 <prim-key-class>java.lang.String</prim-key-class>
20 <reentrant>False</reentrant>
21 <resource-ref>
22 <description>DataSource for the Test database</description>
23 <res-ref-name>jdbc/TestDB</res-ref-name>
24 <res-type>javax.sql.DataSource</res-type>
25 <res-auth>Container</res-auth>
26 </resource-ref>
27 </entity>
Code 8:Entity Bean Implementation for Member Entity Bean(BMP)
1 package com.eland.ejbl
2
3 import javax.naming.*;
4 import javax.ejb.*;
5 import java.rmi.RemoteException;;
6 import java.sql.*;
7 import javax.sql.DataSource;
8 import java.util.*;
9
10 public class MemberBean implements EntityBean {
11 private String userid;
12 private String username;
13 private String password;
14
15 public EntityContext context;
16
17 public String getUsername() {
18 return username;
19 }
20 public void setUsername(String name) {
21 username = name;
22 }
23 public String getPassword() {
24 return password;
25 }
26 public void setPassword(String pw) {
27 password = pw;
28 }
29
30 public String ejbCreate(String userid, String username, String password)
31 throws CreateException {
32 this.userid = userid;
33 this.username = username;
34 this.password = password;
35
36 Connection con = null;
37 PreparedStatement ps = null;
38 try {
39 con = this.getConnection();
40 ps = con.prepareStatement(
41 “insert into Member(userid, username, password) values(?,?,?)”);
42 ps.setString(1, userid);
43 ps.setString(2, username);
44 ps.setString(3, password);
45 if(ps.executeUpdate() != 1) {
46 throw new CreateException(“Failed to add member to database”);
47 }
48 return userid;
49 } catch(SQLException se) {
50 throw new EJBException(se);
51 } finally {
52 try {
53 if(ps != null) ps.close();
54 if(con != null) con.close();
55 } catch(SQLException se) {
56 se.printStackTrace();
57 }
58 }
59
60 public void ejbPostCreate(String userid, String username, String password) {
61
62 }
63
64 public String ejbFindByPrimaryKey(String primaryKey) throws FinderException {
65 Connection con = null;
66 PreparedStatement ps = null;
67 ResultSet result = null;
68 try {
69 con = this.getConnection();
70 ps = con.prepareStatement(“select userid from Member where userid=?”);
71 ps.setString(1, primaryKey);
72 result = ps.executeQuery();
73 if(!result.next()) {
74 throw new ObjectNotFoundException(
75 “Cannot find member with userid = ” + primaryKey);
76 }
77 } catch(SQLException se) {
78 throw new EJBException(se);
79 } finally {
80 try {
81 if(result != null) result.close();
82 if(ps != null) ps.close();
83 if(con != null) con.close();
84 } catch(SQLException se) {
85 se.printStackTrace();
86 }
87 }
88 return primaryKey;
89 }
90
91 public Collection ejbFindByName(String username) throws FinderException {
92 Connection con = null;
93 PreparedStatement ps = null;
94 ResultSet result = null;
95 try {
96 con = this.getConnection();
97 ps = con.prepareStatement();
98 “select userid from Member where username=?”);
99 ps.setString(1, username);
100 result = ps.executeQuery();
101 Vector keys = new Vector();
102 while(result.next()) {
103 keys.addElement(result.getObject(“userid”));
104 }
105 return keys;
106 } catch(SQLException se) {
107 throw new EJBException(se);
108 } finally {
109 try {
110 if(result != null) ps.close();
111 if(ps != null) ps.close();
112 if(con != null) con.close();
113 } catch(SQLException se) {
114 se.printStackTrace();
115 }
116 }
117 }
118
119 public void setEntityContext(EntityContext ctx) {
120 context = ctx;
121 }
122
123 public void unsetEntityContext() {
124 context = null;
125 }
126
127 public void ejbActivate() {}
128 public void ejbPassavate() {}
129
130 public void ejbLoad() {
131 String primaryKey = (String)context.getPrimaryKey();
132 Connection con = null;
133 PreparedStatement ps = null;
134 ResultSet result = null;
135 try {
136 con = this.getConnection();
137 ps = con.prepareStatement();
138 “select userid, username, password from Member where userid=?”);
139 ps.setString(1, primaryKey);
140 result = ps.executeQuery();
141 if(result.next()) {
142 userid = primaryKey;
143 username = result.getString(“username”);
144 password = result.getString(“password”);
145 }
146 } catch(SQLException se) {
147 throw new EJBException(se);
148 } finally {
149 try {
150 if(result != null) ps.close();
151 if(ps != null) ps.close();
152 if(con != null) con.close();
153 } catch(SQLException se) {
154 se.printStackTrace();
155 }
156 }
157 }
158
159 public void ejbStore() {
160 Connection con = null;
161 PreparedStatement ps = null;
162 try {
163 con = this.getConnection();
164 ps = con.prepareStatement();
165 “update Member set username=?, password=? where userid= ?”);
166 ps.setString(1, username);
167 ps.setString(2, password);
168 ps.setString(3, userid);
169 if(ps.executeUpdate() != -1) {
170 throw new EJBException(“ejbStore”);
171 }
172 } catch(SQLException se) {
173 throw new EJBException(se);
174 } finally {
175 try {
176 if(ps != null) ps.close();
177 if(con != null) con.close();
178 } catch(SQLException se) {
179 se.printStackTrace();
180 }
181 }
182 }
183
184 public void ejbRemove() {
185 Connection con = null;
186 PreparedStatement ps = null;
187 try {
188 con = this.getConnection();
189 ps = con.prepareStatement(“delete from Member where userid=?”);
190 ps.setString(1, userid);
191 result = ps.executeQuery();
192 if(ps.executeUpdate() != -1) {
193 throw new EJBException(“ejbRemove”);
194 }
195 } catch(SQLException se) {
196 throw new EJBException(se);
197 } finally {
198 try {
199 if(ps != null) ps.close();
200 if(con != null) con.close();
201 } catch(SQLException se) {
202 se.printStackTrace();
203 }
204 }
205 }
206
207 private Connection getConnection() throws SQLException {
208 try {
209 Context jndiCntx = new InitialContext();
210 DataSource ds = (DataSource)jndiCntx.lookup(“java:comp/env/jdbc/TestDB”);
211 return ds.getConnection();
212 } catch(NamingException ne) {
213 throw new EJBException(ne);
214 }
215 }
216 }
在編寫BMP bean的程式碼時需要注意下列事項:
1. BMP bean的部署描述子裡的<persistence-type>標籤的值為Bean,並且沒有宣告<container-managed>、<cmp-version>、<abstract-schema-name>、<cmp-field>、<relationship-field>等專屬CMP bean的標籤。
2. 我們可以在部署描述子裡宣告查詢資料庫時所需的DataSource訊息,然後在BMP bean的bean class中實作取得資料庫連線的方法(Code 8:line 207-215),以便在各call back與life-cycle方法中直接取用。
3. 各call back方法中所實作的程式碼分別屬於四種存取資料庫的基本語法:INSERT、SELECT、UPDATE、DELETE。
4. 在ejbCreate()方法裡面的是INSERT語法(Code 8:line 30-58),當client端呼叫home interface上的create()方法時,EJB Container會呼叫bean class中相應的ejbCreate()方法,以新增一筆資料。
5. 當client端呼叫home interface上的findBy()方法時,EJB Container會呼叫bean class中相應的ejbFindBy()方法。ejbFindBy()方法分兩種:一種是以primary key為搜尋條件的ejbFindByPrimaryKey()方法,它回傳符合搜尋條件的EJB Object reference給client端(Code 8:line 64-89);另一種則是以entity bean的屬性為搜尋條件的ejbFindBy()方法,它回傳內含符合搜尋條件的EJB Object的Collection(Code 8:line 91-117)。兩種方法內部都實作了SELECT語法。
6. 所有findBy()方法都必須在BMP bean class中實作存取資料庫的程式碼,這點有別於開發CMP bean時無須在CMP bean class中實作任何findBy()方法。
7. 另外一個實作SELECT語法的call back方法是ejbLoad()方法(Code 8:line 130-157),我們已經知道EJB Container會在Activation過程中呼叫這個方法,以便就先前在Passivation過程中寫回資料庫的資料,將其最新狀態寫回新的bean instance,以繼續服務client端的請求。
8. 相對地,在ejbStore()方法(Code 8: line 159-182)中的UPDATE語法則可以在Passivation過程中將bean instance內的資料寫回資料庫。我們可以說,EJB Container主要就是利用ejbLoad()與ejbStore()這對方法,並且視系統當時的狀況,在entity bean與資料庫之間作資料同步的動作。
9. 最後,當client端呼叫interface上的remove方法時,EJB Container會呼叫bean class中的remove()方法,執行其中的DELETE語法,以刪除資料庫內的資料。
10. 在entity bean的生命週期中,EJB Container雖然也會呼叫ejbActivate()、ejbPassivate()、setEntityContext()、unsetEntityContext()方法,但這些方法只供EJB Container與entity nean溝通與生命週期有關的的各種狀態用,不建議在這些方法中實作存取資料庫的程式碼。
11. 開發人員必須實作BMP bean的bean class上各屬性的getter/setter方法(Code 8:line 17-28),並公開至component interface上供client端呼叫使用。
|
五、Client端如何使用entity bean
Entity bean的client端可以是JSP、servlet、java application、session bean等。不論是哪一種client端,使用entity bean時首先要做的是利用JNDI service找到entity bean的home interface,然後呼叫home interface上的create()方法,若沒有例外丟出,home interface會回傳代表component interface的reference。由於我們將bean class中的business方法公開至component interface上,因此我們可以呼叫component interface的public方法,即可使用entity bean。對client而言,只看得到home interface與component interface。以下是client端尋找與呼叫entity bean的範例:
Code 9:Client local lookup of home interface
3 import javax.naming.*;
…
10 InitialContext ctx = new InitialContext();
11 Object objHome = ctx.lookup(“java:comp/env/ejb/MemberHome”);
12 MemberLocalHome home = (MemberLocalHome)objHome;
以上是呼叫local home interface的範例。
Code 10:Client remote lookup of remote home interface
3 import javax.naming.*;
4 import javax.rmi.PortableRemoteObject;
…
10 InitialContext ctx = new InitialContext();
11 Object objHome = ctx.lookup(“java:comp/env/ejb/MemberHome”);
12 MemberRemoteHome home =
13 (MemberRemoteHome)PortableRemoteObject.narrow(objHome, MemberRemoteHome.class);
以上是呼叫remote home interface的範例。取得home interface reference後,再呼叫home interface上的create()方法,得到component interface reference,如下例所示:
Code 11:Creating an entity bean
14 String userid = “123”;
15 MemberLocal member = home.create(userid);
如果要尋找既存的資料,則可呼叫home interface上的findByPrimaryKey()方法:
Code 12:Finding an existing entity bean
14 String userid = “123”;
15 MemberLocal member = home.findByPrimaryKey(userid);
取得component interface後,我們就可以透過component interface使用bean class的business方法:
Code 13:Using an entity bean
16 String username = member.getUsername();
17 String password = request.GetParameter(“password”);
18 member.setPassword(password);
…
30 member.remove();
參考資料
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