許多開發者對 EJB 回傳值感到困惑。本文提供兩種實用解決方案:傳統 Vector 與現代 RowSet(特別是 CachedRowSet),並附完整程式碼範例。
適用於尋找器方法回傳主鍵枚舉:
public Enumeration ejbFindBigAccounts(double balanceGreaterThan) {
log("ejbFindBigAccounts (balance > " + balanceGreaterThan + ")");
Connection con = null;
PreparedStatement ps = null;
try {
con = getConnection();
ps = con.prepareStatement("select id from ejbAccounts where bal > ?");
ps.setDouble(1, balanceGreaterThan);
ps.executeQuery();
ResultSet rs = ps.getResultSet();
Vector v = new Vector();
String pk;
while (rs.next()) {
pk = rs.getString(1);
v.addElement(pk);
}
return v.elements();
} catch (SQLException sqe) {
log("SQLException: " + sqe);
throw new EJBException(sqe);
} finally {
cleanup(con, ps);
}
}
結論:不夠優雅、使用不便。需手動迭代 ResultSet、填充 Vector、回傳 Enumeration,客戶端再迭代取值,類型安全性差。
RowSet 為介面,需實作類別。Sun 規範提供三個實作:CachedRowSet、JdbcRowSet、WebRowSet。JDK 1.4 與 J2EE 1.2 雖含 RowSet 介面,但無三實作類別,一般開發工具(如 WSAD)同樣缺失,需下載 JDBC 2.0 Optional Package:
https://developer.java.sun.com/developer/earlyAccess/crs/
rowset.jar,放置任意位置(如 d:\jdk1.4\jre\rowset.jar)ROWSET_PATH 對應 d:\jdk1.4\jre\rowset.jarrowset_pathimport sun.jdbc.rowset.*;CachedRowSet 真的是好東西,類似 MS ADO Recordset 與 Borland TClientDataSet,最大優勢在 快取功能——斷線操作資料,重連時批次更新回資料庫。
package example4;
import java.sql.*;
import javax.sql.*;
import sun.jdbc.rowset.*;
import javax.naming.*;
import javax.ejb.*;
public class CoffeesBean implements SessionBean {
private SessionContext sc = null;
private Context ctx = null;
private DataSource ds = null;
public CoffeesBean() {}
public void ejbCreate() throws CreateException {
try {
ctx = new InitialContext();
ds = (DataSource) ctx.lookup("jdbc/CoffeesDB");
} catch (Exception e) {
System.out.println(e.getMessage());
throw new CreateException();
}
}
public RowSet getCoffees() throws SQLException {
Connection con = null;
ResultSet rs;
CachedRowSet crs;
try {
con = ds.getConnection("webCustomer", "webPassword");
Statement stmt = con.createStatement();
rs = stmt.executeQuery("select * from coffees");
crs = new CachedRowSet();
crs.populate(rs);
crs.setTableName("coffees"); // JDBC 驅動不提供此中繼資料
rs.close();
stmt.close();
} finally {
if (con != null) con.close();
}
return crs; // 注意:原文變數名為 rset,此處修正為 crs
}
public void updateCoffees(RowSet rs) throws SQLException {
Connection con = null;
try {
CachedRowSet crs = (CachedRowSet) rs;
con = ds.getConnection("webCustomer", "webPassword");
crs.acceptChanges(con); // 將變更寫回資料庫
} finally {
if (con != null) con.close();
}
}
// SessionBean 介面方法
public void setSessionContext(SessionContext sc) { this.sc = sc; }
public void ejbRemove() {}
public void ejbPassivate() {}
public void ejbActivate() {}
}
package example4;
import java.sql.*;
import javax.sql.*;
import sun.jdbc.rowset.*;
import javax.naming.*;
import javax.ejb.*;
import javax.rmi.*;
class CoffeesClient {
public static void main(String[] args) {
try {
// 初始化 Bean
Context ctx = new InitialContext();
Object obj = ctx.lookup("ejb/Coffees");
CoffeesHome coffeesHome = (CoffeesHome)
PortableRemoteObject.narrow(obj, CoffeesHome.class);
Coffees coffees = coffeesHome.create();
// 從 Bean 取得 RowSet
CachedRowSet rset = (CachedRowSet) coffees.getCoffees();
// 找到 Columbian 咖啡,價格漲 10%
while (rset.next()) {
String coffeeName = rset.getString("COF_NAME");
if (coffeeName.equalsIgnoreCase("Columbian")) {
rset.updateFloat("PRICE",
(float)(rset.getFloat("PRICE") * 1.10));
rset.updateRow();
}
}
// 送回 Bean 更新資料庫
System.out.println("Calling update method");
coffees.updateCoffees((RowSet) rset);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
CachedRowSet,稍後一次性 acceptChanges() 寫回getString()、updateFloat() 等強型別存取setTableName() 協助寫回操作javax.sql.RowSet 為標準介面,未來可遷移至標準實作範例簡單明瞭,CachedRowSet 讓 EJB 資料傳輸更靈活強大。Cheers! — Robin