程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 循速漸進學用Session Bean(五)

循速漸進學用Session Bean(五)

編輯:關於JAVA

創建一個實用的Session Bean

HelloWorldSession例子的主要目的是幫助你熟悉一個session bean的整體結構。現在你已經熟悉了session bean的結構,你可以寫一個更實用的bean了。特別地,你可以寫一個由數據庫中接收數據的bean。

以下的例子,假定你擁有一個SQL表格,裡面包含有產品的代碼和價格,你也可以使用以下SQL命令建立它:

create table price
(product_code varchar(10) not null primary key,
price decimal(10,2) not null)

Pricing session bean可以列出全部有效的產品代碼,並且可以返回某個產品代碼的價格,該代碼由Remote接口指定,如6.9列表所示:

Listing 6.9 Source Code for Pricing.java
package usingj2ee.pricing;
import java.rmi.*;
import javax.ejb.*;
/** Defines the methods you can call on a Pricing session */
public interface Pricing extends EJBObject
{
/** Returns all the available product codes */
public String[] getProductCodes() throws RemoteException;
/** Returns the price for a specific product code */
public double getPrice(String productCode)
throws RemoteException, InvalidProductCodeException;
}

Pricing session bean並不需要記得某個客戶的任何信息,所以可以用一個無狀態的session bean實現。PricingHome接口如列表6.10所示,它僅需要一個create方法。

Listing 6.10 Source Code for PricingHome.java
package usingj2ee.pricing;
import java.rmi.*;
import javax.ejb.*;
/** Defines the methods for creating a Pricing session */
public interface PricingHome extends EJBHome
{
/** Creates a Pricing session bean */
public Pricing create() throws RemoteException, CreateException;
}

當一個session bean需要訪問一個數據庫連接時,它通常在setSessionContext方法中分配一個連接,最後在ejbRemote方法中釋放它。當然,如果你擁有一個數據庫的連接,在容器調用ejbPassivate方法時,你必須准備關閉它,在容器調用ejbActivate時,重新得到連接。

你將會發現大部分的EJB開發者使用一個方法來返回一個連接;這樣你就以後就可以修改得到連接的方法,而不會影響使用這些連接的代碼。你也應該使用DataSource對象來創建連接。DataSource讓修改數據庫的驅動變得容易,並且可以在需要的時候使用連接池。

列表6.11展示了Pricing session bean的實現類PricingImpl

Listing 6.11 Source Code for PricingImpl.java
package usingj2ee.pricing;
import java.rmi.*;
import java.util.*;
import javax.ejb.*;
import java.sql.*;
import javax.sql.*;
import javax.naming.*;
/** The implementation class for the Pricing bean */
public class PricingImpl implements SessionBean
{
/** The session context provided by the EJB container. A session bean must
hold on to the context it is given. */
private SessionContext context;
/** The database connection used by this session */
private Connection conn;
/** An EJB must have a public, parameterless constructor */
public PricingImpl()
{
}
/** Called by the EJB container to set this session's context */
public void setSessionContext(SessionContext aContext)
{
context = aContext;
}
/** Called by the EJB container when a client calls the create() method in
the Home interface */
public void ejbCreate()
throws CreateException
{
try
{
// Allocate a database connection
conn = getConnection();
}
catch (Exception exc)
{
throw new CreateException(
"Unable to access database: "+exc.toString());
}
}
/** Called by the EJB container to tell this session bean that it is being
suspended from use (it's being put to sleep). */
public void ejbPassivate()
throws EJBException
{
try
{
// Shut down the current database connection
conn.close();
conn = null;
}
catch (Exception exc)
{
throw new EJBException("Unable to close database connection: "+
exc.toString());
}
}
/** Called by the EJB container to wake this session bean up after it
has been put to sleep with the ejbPassivate method. */
public void ejbActivate()
throws EJBException
{
try
{
// When the bean wakes back up, get a database connection again
conn = getConnection();
}
catch (Exception exc)
{
throw new EJBException(
"Unable to access database: "+exc.toString());
}
}
/** Called by the EJB container to tell this session bean that it has been
removed, either because the client invoked the remove() method or the
container has timed the session out. */
public void ejbRemove()
throws EJBException
{
try
{
// Shut down the current database connection
conn.close();
conn = null;
}
catch (Exception exc)
{
throw new EJBException("Unable to close database connection: "+
exc.toString());
}
}
/** Returns a list of the available product codes */
public String[] getProductCodes()
throws EJBException
{
Statement s = null;
try
{
s = conn.createStatement();
ResultSet results = s.executeQuery(
"select product_code from price");
Vector v = new Vector();
// Copy the results into a temporary vector
while (results.next())
{
v.addElement(results.getString("product_code"));
}
// Copy the vector into a string array
String[] productCodes = new String[v.size()];
v.copyInto(productCodes);
return productCodes;
}
catch (Exception exc)
{
throw new EJBException("Unable to get product codes: "+
exc.toString());
}
finally
{
// Close down the statement in a finally block to guarantee that it gets
// closed, whether an exception occurred or not
try
{
s.close();
}
catch (Exception ignore)
{
}
}
}
/** Gets the price for a particular product code */
public double getPrice(String productCode)
throws EJBException, InvalidProductCodeException
{
PreparedStatement ps = null;
try
{
// It's always better to use a prepared statement than to try to insert
// a string directly into the query string. This way you don't have to
// worry if there's a quote in the product code
ps = conn.prepareStatement(
"select price from price where product_code = ?");
// Store the product code in the prepared statement
ps.setString(1, productCode);
ResultSet results = ps.executeQuery();
// If there are any results, get the first one (there should only be one)
if (results.next())
{
return results.getDouble("price");
}
else
{
// Otherwise, if there were no results, this product code doesn't exist
throw new InvalidProductCodeException(productCode);
}
}
catch (SQLException exc)
{
throw new EJBException("Unable to get price: "+
exc.toString());
}
finally
{
// Close down the statement in a finally block to guarantee that it gets
// closed, whether an exception occurred or not
try
{
ps.close();
}
catch (Exception ignore)
{
}
}
}
protected Connection getConnection()
throws SQLException, NamingException
{
// Get a reference to the naming service
InitialContext context = new InitialContext();
// Get the data source for the pricing database
DataSource ds = (DataSource) context.lookup(
"java:comp/env/jdbc/PriceDB");
// Ask the data source to allocate a database connection
return ds.getConnection();
}
}

PricingImpl中的getConnection值得留意一下。它使用JNDI(命名服務)來定位到一個名字為java:comp/env/jdbc/PriceDB的數據源。java:comp/env指的是你的session bean使用的JNDI naming context。當session bean被配置在一個EJB容器時,容器為你的bean設置了一個帶有各個項目的naming context,這些項目是在你配置bean時建立的。java:comp/env naming context讓你將邏輯名字和各種不同資源聯系起來。這樣你就可以在編寫bean時,無需知道數據源或者Home接口的准確名字。當在配置bean到容器中時,設置bean使用的名字和真正資源名相關聯。這樣bean就並不綁定到某個特別的資源名字,可以提升bean的移植性。

在配置Pricing bean時,必須指定jdbc/PriceDB的一個別名。如果使用J2EE SDK帶有的Cloudscape數據庫,這個別名必須是jdbc/Cloudscape。否則,必須在EJB服務器中建立一個數據源,指向要使用的數據庫。當配置Pricing bean時,指定jdbc/PriceDB引用的數據源名字。jdbc/PriceDB是一個邏輯名,Pricing bean可以使用多種不同的數據庫,只要在配置bean時修改一下關聯就可以了。

如果使用不同的數據源,可以在配置的時候修改它。還可以建立一個default.properties文件,裡面包含有你需要使用的驅動和數據庫信息。例如,對於Oracle數據庫,可以使用以下的default.properties文件:

jdbc.drivers=oracle.jdbc.driver.OracleDriver
jdbc.datasources=jdbc/Oracle|jdbc:oracle:thin:@localhost:1521:orcl

這樣就建立了一個可選的數據源,它的名字是jdbc/Oracle,jdbc/PriceDB關聯可以修改以便使用Oracle數據源。再次說明的是,你並不需要修改Pricing bean,你只要修改配置屬性。

假定正在運行J2EE SDK配置工具,在配置工具的資源引用部分(Resource References section)設置了jdbc/PriceDB naming項目,如圖6.8所示。

********圖6.8**************

資源引用對話框允許你設置一個session bean的naming context

Prcing bean和HelloWorldSession bean在配置上只有一點不同,這就是你必須在JNDI Names的標記頁中指定jdbc/PriceDB的別名,如圖6.9所示。

********圖6.9**************

JNDI names頁讓你設置bean使用的各種JNDI別名

寫一個客戶來測試Pricing bean是很簡單的,這個程序和你已經看到過的其它客戶程序類似。列表6.12展示了Pricing的測試客戶程序。

Listing 6.12 Source Code for TestPricing.java
package usingj2ee.pricing;
import java.util.*;
import javax.naming.*;
import javax.rmi.*;
public class TestPricing
{
public static void main(String[] args)
{
try
{
/** Creates a JNDI naming context for location objects */
Context context = new InitialContext();
/** Asks the context to locate an object named "Pricing" and expects the
object to implement the PricingHome interface */
PricingHome home = (PricingHome)
PortableRemoteObject.narrow(
context.lookup("Pricing"),
PricingHome.class);
/** Asks the Home interface to create a new session bean */
Pricing session = (Pricing) home.create();
/** Get a list of valid product codes */
String[] codes = session.getProductCodes();
for (int i=0; i < codes.length; i++)
{
System.out.println(codes[i]+": "+
session.getPrice(codes[i]));
}
try
{
session.getPrice("f00b4r");
}
catch (InvalidProductCodeException exc)
{
System.out.println("Got invalid product code exception: "+
exc.toString());
}
/** Destroy this session */
session.remove();
}
catch (Exception exc)
{
exc.printStackTrace();
}
}
}

最後,列表6.10展示了pricing測試客戶的輸出。要注意的是,無論是在源代碼或者輸出中,都沒有地方顯示該bean由一個數據庫中得到數據。

***********圖6.10*******************

客戶並不知道session bean由數據庫中得到數據

注意

可以使用以下命令為pricing數據庫加入數據

INSERT INTO price (product_code, price) VALUES ('A1', 1.59);

問題解答

配置問題

Q:為什麼配置工具不產生EAR或者JAR文件?

A:在實現類中實現的方法,有可能違反了EJB的限制或者要求。許多配置工具都帶有一個選項,可測試兼容性。例如,在J2EE SDK中,這個選項被稱為Verifier;在WebLogic的配置工具中,它被稱為Check Compliance。而且對windows的檢查也可能包含有錯誤的信息。某些工具並不會一直彈出窗口告訴你哪裡錯了,你可以檢查各種的log文件,它們放在J2EE SDK的子目錄logs中。

Q:為什麼工具不產生客戶JAR文件?

A:除了Remote和Home接口類外,可以不需要其它的東西,所以無需要工具創建JAR文件。不過在大多數的情況下,客戶端的JAR文件是必需的,這是因為配置工具會產生一些用作客戶端開發的實用類。

運行時的問題

Q:為什麼我的客戶端程序不能定位JNDI naming服務?

A:首先,要確認你的EJB服務器正在運行。接著,需要在命令行定義初始的naming context factory 類。查看你的EJB服務器的文檔,看你是否需要其它的命令行選項。還有,確定bean是被配置了的。如果使用配置工具來配置bean,然後重新啟動服務器,你的服務器可能已經忘掉了那個bean。這時你可以嘗試重新配置。還有,確認客戶程序請求的名字和你為bean配置的JNDI名字是一樣的,必須完全一致,包括大小寫。

Q:為什麼在訪問數據源時出現錯誤?

A:可能沒有為你的EJB服務器設置好數據源,也可能是沒有正確地設置關聯,即EJB使用的邏輯名和JNDI目錄使用的真正數據源名。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved