循速渐进学用Session Bean3

创建一个实用的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目录使用的真正数据源名。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值