数据访问 API 的扩展
如果单个数据访问 API 未提供应用程序的完整解决方案,那么使用 WebSphere® Application Server 扩展来实现 JCA 与 JDBC API 之间的互操作性。
这些应用程序提取自多种复合的资源管理配置,它们可能需要使用 Java™ 平台企业修订版 (Java EE) 连接器体系结构 (JCA) API 和 Java 数据库连接 (JDBC) API。但是,在某些情况下,JDBC 编程模型不会与 JCA 完全集成(即使完全集成是 JCA 规范的基础)。这些矛盾可以限制使用这两个 API 的应用程序的数据访问选项。WebSphere Application Server 提供 API 扩展来解决兼容性问题。
例如:
如果对于可共享连接有其他句柄存在,那么在没有扩展的优势的情况下使用这两个 API 的应用程序,在进行连接请求后无法修改此连接的属性。(如果没有与此连接相关的其他句柄,那么可以更改连接属性。)此限制是由 API 的连接配置策略间的不兼容性引起的。
在您通过传递 ConnectionSpec 对象(使用 getConnection() 方法)请求连接时,连接器体系结构 (JCA) 规范支持为资源适配器重新进行特定属性设置。ConnectionSpec 对象包含用于获取连接所必需的连接属性。 在您从此环境中获取连接后,您的应用程序无需更改这些属性。然而,JDBC 编程模型没有用于指定连接属性的同一接口。而是它首先获取连接,然后设置连接的属性。
WebSphere Application Server 提供以下扩展来弥补 JDBC 和 JCA 规范间的此类差异:
- WSDataSource 接口 - 此接口扩展了 javax.sql.DataSource 类,并且通过 WebSphere Application Server JDBCConnectionSpec 类获取连接,使组件或应用程序能够指定连接属性。
- getConnection(JDBCConnectionSpec) - 此方法返回具有用 JDBCConnectionSpec 类指定的属性的连接。
- 有关更多信息,请参阅 WSDataSource API 文档主题(API 文档索引中列出了本主题)。
- JDBCConnectionSpec 接口 - 此接口扩展 com.ibm.websphere.rsadapter.WSConnectionSpec 类,此类扩展了 javax.resources.cci.ConnectionSpec 类。标准 ConnectionSpec 接口只提供接口标记,并不提供任何 get() 和 set() 方法。WSConnectionSpec 和 JDBCConnectionSpec 接口定义了一组由 WebSphere Application Server 运行时使用的 get() 和 set() 方法。 为了获取适当的连接,此接口允许应用程序指定所有必不可少的连接属性。您可以从 WebSphere WSRRAFactory 类创建此类。有关更多信息,请参阅 JDBCConnection API 文档主题(API 文档索引中列出了本主题)。
- WSRRAFactory 类 - 这是 WebSphere Relational Resource Adapter 的工厂类,它允许用户创建 JDBCConnectionSpec 对象或其他资源适配器相关的对象。有关更多信息,请参阅 WSRRAFactory API 文档主题(API 文档索引中列出了本主题)。
- WSConnection 接口 - 此接口允许用户通过 SQL 连接调用 WebSphere 专有的方法;这些方法包括:
- setClientInformation(Properties props) - 请参阅主题“示例:用 setClientInformation(Properties) API 设置客户机信息”,以获取设置客户机信息的更多信息和示例。
- Properties getClientInformation() - 此方法返回使用 setClientInformation(Properties) 设置的属性对象。注意,客户机信息的隐式设置不会影响返回的属性对象。
- WSSystemMonitor getSystemMonitor() - 如果数据库支持系统监视器,此方法从后端数据库连接返回 SystemMonitor 对象。后端数据库将提供 SystemMonitor 对象中的一些连接统计信息。返回的 SystemMonitor 对象被合并在 WebSphere 对
象 (com.ibm.websphere.rsadapter.WSSystemMonitor) 中,以使应用程序可以不依赖任何数据库供应商代码。请参阅 com.ibm.websphere.rsadapter.WSSystemMonitor Java 文档,以了解更多信息。以下代码是使用 WSSystemMonitor 类的示例:
import com.ibm.websphere.rsadapter.WSConnection; ... try{ InitialContext ctx=new InitialContext(); // Perform a naming service lookup to get the DataSource object. DataSource ds=(javax.sql.DataSource]ctx.lookup("java:comp/jdbc/myDS"); } catch (Exception e) {;} WSConnection conn=(WSConnection)ds.getConnection(); WSSystemMonitor sysMon=conn.getSystemMonitor(); if (sysMon!=null) // indicates that system monitoring is supported on the current backend database { sysMon.enable(true); sysMon.start(WSSystemMonitor.RESET_TIMES); // interact with the database sysMon.stop(); // collect data from the sysMon object } conn.close();
WSConnection 接口是 plugins_root/com.ibm.ws.runtime.jar 文件的部件。
示例:将 IBM 扩展 API 用于数据库连接
在获取连接之前,可以使用 WSDataSource 扩展 API 来编码 JDBC 应用程序,以通过对象定义连接属性。此行为增加了应用程序可以与另一个组件(如 CMP)共享连接的机会。
如果应用程序与可共享连接(可能与事务中其他容器管理的持久性 (CMP) bean 共享)一起运行,那么建议您使用 WebSphere Application Server 扩展 API 来获取连接。 当使用这些 API 时,您无法将您的应用程序迁移至其他应用程序服务器。
可以使用扩展 API 直接在 JDBC 应用程序中进行编码;请使用 WSDataSource 接口来获取连接,而不是使用 DataSource 接口。以下代码段说明了 WSDataSource:
import com.ibm.websphere.rsadapter.*;
...
// Create a JDBCConnectionSpec and set connection properties. If this connection
// is shared with the CMP bean, make sure that the isolation level is the same as
// the isolation level that is mapped by the Access Intent defined on the CMP bean.
JDBCConnectionSpec connSpec = WSRRAFactory.createJDBCConnectionSpec();
connSpec.setTransactionIsolation(CONNECTION.TRANSACTION_REPEATABLE_READ);
connSpec.setCatalog("DEPT407");
// Use WSDataSource to get the connection
Connection conn = ((WSDataSource)datasource).getConnection(connSpec);
示例:使用 IBM 扩展 API 在 CMP bean 与 BMP bean 之间共享连接
获取连接之前,在通过 JDBC 对象(如 bean 管理的持久性 (BMP) Bean)访问数据的应用程序组件中,可以使用 WebSphere 扩展 API 来通过对象定义连接属性。此行为增加了 BMP bean 可以与容器管理的持久性 (CMP) bean 共享连接的机会。
如果 BMP bean 与可共享连接(可能与事务中其他容器管理的持久性 (CMP) bean 共享)一起运行,那么建议您使用 WebSphere Application Server 扩展 API 来获取连接。 当使用这些 API 时,您无法将您的应用程序迁移至其他应用程序服务器。
在这种情况下,请使用扩展的 API WSDataSource 接口,而不是 DataSource 接口。要确保 CMP 和 bean 管理的持久性 (BMP) bean 共享同一物理连接,请在 CMP 和 BMP bean 上定义相同的访问意向概要文件。在 BMP 方法中,您可以从关系资源适配器辅助类中获取正确的隔离级别。
package fvt.example;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.ejb.CreateException;
import javax.ejb.DuplicateKeyException;
import javax.ejb.EJBException;
import javax.ejb.ObjectNotFoundException;
import javax.sql.DataSource;
// following imports are used by the IBM extended API
import com.ibm.websphere.appprofile.accessintent.AccessIntent;
import com.ibm.websphere.appprofile.accessintent.AccessIntentService;
import com.ibm.websphere.rsadapter.JDBCConnectionSpec;
import com.ibm.websphere.rsadapter.WSCallHelper;
import com.ibm.websphere.rsadapter.WSDataSource;
import com.ibm.websphere.rsadapter.WSRRAFactory;
/**
* Bean implementation class for Enterprise Bean: Simple
*/
public class SimpleBean implements javax.ejb.EntityBean {
private javax.ejb.EntityContext myEntityCtx;
// Initial context used for lookup.
private javax.naming.InitialContext ic = null;
// define a JDBCConnectionSpec as instance variable
private JDBCConnectionSpec connSpec;
// define an AccessIntentService which is used to get
// an AccessIntent object.
private AccessIntentService aiService;
// AccessIntent object used to get Isolation level
private AccessIntent intent = null;
// Persitence table name
private String tableName = "cmtest";
// DataSource JNDI name
private String dsName = "java:comp/env/jdbc/SimpleDS";
// DataSource
private DataSource ds = null;
// bean instance variables.
private int id;
private String name;
/**
* In setEntityContext method, you need to get the AccessIntentService
* object in order for the subsequent methods to get the AccessIntent
* object.
* Other ejb methods will call the private getConnection() to get the
* connection which has all specific connection properties
*/
public void setEntityContext(javax.ejb.EntityContext ctx) {
myEntityCtx = ctx;
try {
aiService =
(AccessIntentService) getInitialContext().lookup(
"java:comp/websphere/AppProfile/AccessIntentService");
ds = (DataSource) getInitialContext().lookup(dsName);
}
catch (javax.naming.NamingException ne) {
throw new javax.ejb.EJBException(
"Naming exception:" + ne.getMessage());
}
}
/**
* ejbCreate
*/
public fvt.example.SimpleKey ejbCreate(int newID)
throws javax.ejb.CreateException, javax.ejb.EJBException {
Connection conn = null;
PreparedStatement ps = null;
// Insert SQL String
String sql = "INSERT INTO" + tableName + "(id, name) VALUES (?, ?)";
id = newID;
name = "";
try {
// call the common method to get the specific connection
conn = getConnection();
}
catch (java.sql.SQLException sqle) {
throw new EJBException("SQLException caught:" + sqle.getMessage());
}
catch (javax.resource.ResourceException re) {
throw new EJBException(
"ResourceException caught:" + re.getMessage());
}
try {
ps = conn.prepareStatement(sql);
ps.setInt(1, id);
ps.setString(2, name);
if (ps.executeUpdate() != 1) {
throw new CreateException("Failed to add a row to the DB");
}
}
catch (DuplicateKeyException dke) {
throw new javax.ejb.DuplicateKeyException(
id + "has already existed");
}
catch (SQLException sqle) {
throw new javax.ejb.CreateException(sqle.getMessage());
}
catch (CreateException ce) {
throw ce;
}
finally {
if (ps != null) {
try {
ps.close();
}
catch (Exception e) {
}
}
}
return new SimpleKey(id);
}
/**
* ejbLoad
*/
public void ejbLoad() throws javax.ejb.EJBException {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
String loadSQL = null;
try {
// call the common method to get the specific connection
conn = getConnection();
}
catch (java.sql.SQLException sqle) {
throw new EJBException("SQLException caught:" + sqle.getMessage());
}
catch (javax.resource.ResourceException re) {
throw new EJBException(
"ResourceException caught:" + re.getMessage());
}
// You need to determine which select statement to be used based on the
// AccessIntent type:
// If READ, then uses a normal SELECT statement. Otherwise uses a
// SELECT...FORUPDATE statement
// If your backend is SQLServer, then you can use different syntax for
// the FOR UPDATE clause.
if (intent.getAccessType() == AccessIntent.ACCESS_TYPE_READ) {
loadSQL = "SELECT * FROM" + tableName + "WHERE id = ?";
}
else {
loadSQL = "SELECT * FROM" + tableName + "WHERE id = ? FOR UPDATE";
}
SimpleKey key = (SimpleKey) getEntityContext().getPrimaryKey();
try {
ps = conn.prepareStatement(loadSQL);
ps.setInt(1, key.id);
rs = ps.executeQuery();
if (rs.next()) {
id = rs.getInt(1);
name = rs.getString(2);
}
else {
throw new EJBException("Cannot load id =" + key.id);
}
}
catch (SQLException sqle) {
throw new EJBException(sqle.getMessage());
}
finally {
try {
if (rs != null)
rs.close();
}
catch (Exception e) {
}
try {
if (ps != null)
ps.close();
}
catch (Exception e) {
}
try {
if (conn != null)
conn.close();
}
catch (Exception e) {
}
}
}
/**
* This method will use the AccessIntentService to get the access intent;
* then gets the isolation level from the DataStoreHelper
* and sets it in the connection spec; then uses this connection
* spec to get a connection which has the specific connection
* properties.
**/
private Connection getConnection()
throws java.sql.SQLException, javax.resource.ResourceException, EJBException {
// get current access intent object using EJB context
intent = aiService.getAccessIntent(myEntityCtx);
// Assume this bean only supports the pessimistic concurrency
if (intent.getConcurrencyControl()
!= AccessIntent.CONCURRENCY_CONTROL_PESSIMISTIC) {
throw new EJBException("Bean supports only pessimistic concurrency");
}
// determine correct isolation level for currently configured database
// using DataStoreHelper
int isoLevel =
WSCallHelper.getDataStoreHelper(ds).getIsolationLevel(intent);
connSpec = WSRRAFactory.createJDBCConnectionSpec();
connSpec.setTransactionIsolation(isoLevel);
// Get connection using connection spec
Connection conn = ((WSDataSource) ds).getConnection(connSpec);
return conn;
}