内容


Apache Geronimo JNDI 命名和 Java 资源连接池,第 1 部分

数据源连接

利用 JNDI 访问数据源、Java 消息服务、邮件会话和 URL 连接的连接池

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: Apache Geronimo JNDI 命名和 Java 资源连接池,第 1 部分

敬请期待该系列的后续内容。

此内容是该系列的一部分:Apache Geronimo JNDI 命名和 Java 资源连接池,第 1 部分

敬请期待该系列的后续内容。

了解 JNDI

JNDI 是一种应用程序编程接口 (API) 或库,它为应用程序提供了将名称与对象关联起来以及根据对象的名称在目录中查找对象的方法。本文是系列教程的第一部分,阅读本文可以让您了解如何将 Geronimo、JNDI 与数据源连接池相互关联起来,如何构建数据源连接,以及如何利用 JNDI 在一个简单的 Geronimo 应用程序(称为 Customer Service 实用程序)内访问该连接。

数据源 是指对象或工厂,用于生成与应用程序内的实际数据源(通常是数据库)的连接。在 Java Platform, Enterprise Edition (Java EE) 中,此数据源是指数据库连接池,一组随时可用的数据库连接。创建数据源或数据库池的原因是它能够提高应用程序性能,因为即使打开一个连接也会十分占用 CPU。

Customer Service 实用程序(可在本文的 下载 部分获得)是一个简单的 Web 应用程序,它允许将基本的客户信息保存到数据库中。您将使用 Apache Ant 1.6.5 和 Java 1.4.2_10 构建该实用程序,将其部署到配有 Tomcat 的 Geronimo 1.1 上,并使用与 Geronimo 绑定的 Apache Derby 数据库(有关下载 Geronimo 的链接,请参阅本文末尾的 参考资料)。下一节将介绍如何使用 Geronimo 控制台创建此数据库和数据源。

设置 Geronimo

Geronimo 控制台可以以一种用户友好的方式管理 Java EE 组件。当 Geronimo 启动后,可以访问位于 http://localhost:8080/console 的 Geronimo 控制台。默认的用户名和密码分别是 systemmanager

开始步骤:

  1. 单击位于 Console Navigation 面板左下角的 DB Manager 链接。此链接表示 Derby 数据库管理器。
  2. 通过在 Create DB 字段中键入 CustomerServiceDatabase 创建客户服务数据库,然后单击 Create 按钮。
  3. 通过在 Use DB 下拉式菜单中选择 CustomerServiceDatabase 来创建数据库表。
  4. 然后将清单 1 中所示的 SQL 脚本中的内容粘贴到表中,并单击 Run SQL 按钮。请注意,源代码中附带了清单 1 中的 SQL 脚本。
清单 1. SQL 脚本
CustomerService-Part1.sql
create table customers (
   customerid varchar(10) primary key,
   fullname varchar(30),
   emailaddress varchar(30),
   interests varchar(100)
);
insert into customers values ('A100','John Doe10','Doe10@work.com','Java,Open Source, 
Computer Graphics');
insert into customers values ('b100','Jane Doe20','Doe20@home.net','Budget Travel, New 
Zealand, Martial Arts');

现在应当已在 Derby 中创建了客户服务数据库。该数据库只有一张表 Customers,该表中只有两个条目。图 1 显示了单击 Application 链接,再单击 View Contents 链接后出现的经正常初始化的表。

图 1. 初始化后的客户服务数据库
初始化后的客户服务数据库
初始化后的客户服务数据库

接下来将为新创建的数据库创建连接池:

  1. 在左侧的 Console Navigation 面板中单击 Database Pools 链接。
  2. 然后单击 Using the Geronimo database pool wizard 链接,完成以下步骤:
    • 在名为 Step 1: Select Name and Database 的页面的 Name of Database Pool 字段中输入 CustomerServicePool。从 Database Type 下拉式菜单中选择 Derby embedded,然后单击 Next
    • 在名为 Step 2: Select Driver, JAR, Parameters 的页面中,从 Driver JAR 下拉式菜单中选择 org.apache.derby/derby/10.1.1.0/jar。在 DB User Name 字段中,键入 app。将 DB Password 留空。在 Database 字段中输入 CustomerServiceDatabase,然后单击 Next
    • 在名为 Step 3: Final Pool Configuration 的页面中,确保 Driver Status 字段显示 Loaded Successfully。可以将 Connection Pool Parameters 留空。单击 Test Connection 按钮前进到下一页。
    • 在名为 Step 4: Test Connection 的页面中,Test Result 字段应当显示 Connected to Apache Derby 10.1.1.0。单击 Deploy。图 2 将显示创建后的新连接池。
图 2. 新创建的 CustomerServicePool 数据源
新创建的 CustomerServicePool 数据源
新创建的 CustomerServicePool 数据源

至此,数据库 (CustomerServiceDatabase) 和数据源 (CustomerServicePool) 应当已经配置并部署到 Geronimo 上并可供使用。接下来,看一看如何在 JNDI 中声明这些对象。

创建 Geronimo 部署描述符

部署到 Geronimo 的所有组件都需要使用部署描述符。甚至连先前创建的 CustomerServicePool 都要使用描述符文件,这可以通过在名为 Step 4: Test Connection 的页面中单击 Show Plan 按钮得以证实。应用程序开发人员将创建的最常见组件包括 EJB(JAR 文件)、Web 归档文件(WAR 文件)以及应用程序归档文件(EAR 文件)。Customer Service 实用程序将利用以上每种类型的组件。

对描述符文件进行配置是十分重要的,因为它是使这些组件可用于 Geronimo 的机制。另外还决定了 JNDI 名称该如何与给定的 Java 对象关联起来。部署到 Geronimo 中的组件通常有两个部署文件:标准的 Java 部署描述符和特定于 Geronimo 的部署计划。主要资源是 CustomerServiceDatabase。数据源 CustomerServicePool 提供了许多用于访问此数据库的连接。要存储客户信息,用户首先需要访问 Web 界面,然后在字段中键入信息。那些字段再被传递给引用会话 bean 的 Java bean。此会话 bean 将作用于客户数据,方法是引用实体 bean 执行基本数据库操作。实体 bean 是客户的表中某一行的 Java 对象表示,并且是引用 CustomerServicePool 来访问数据库的组件。清单 2 包含了此实体 bean 的标准 Java 部署描述符。

清单 2. CustomerEJB-ejb.xml 的部分清单
<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN" 
"http://java.sun.com/dtd/ejb-jar_2_0.dtd">

<ejb-jar>
   <enterprise-beans>
      <entity>
         <ejb-name>CustomerEntityBean</ejb-name>
         <home>com.service.customer.ejb.CustomerHome</home>
         <remote>com.service.customer.ejb.Customer</remote>
         <ejb-class>com.service.customer.ejb.CustomerEntityBean</ejb-class>
         <persistence-type>Container</persistence-type>
         <resource-ref>
            <res-ref-name>jdbc/CustomerServiceDataSource</res-ref-name>
            <res-type>javax.sql.DataSource</res-type>
            <res-auth>Container</res-auth>
         </resource-ref>
      </entity>
   </enterprise-beans>
</ejb-jar>

此处有四个有趣的标记。<ejb-name> 标记用于指定此实体 bean 的名称。<persistence-type> 标记用于指示此实体 bean 使用了容器管理的持久性 (CMP)。这意味着所有数据库操作都将由 Geronimo 自动处理。<res-ref-name> 标记用于指定执行查找时所使用的 JNDI 名称。使用 CMP 的实体 bean 的实现很小,并且没有一个 JNDI 查找是被显式编码的。<res-ref-name> 标记中的声明仍然是必需的。最后,<res-type> 标记用于指示与 JNDI 名称关联的对象类型。清单 3 显示了此实体 bean 的特定于 Geronimo 的关联文件。

清单 3. CustomerEJB-openejb.xml 的部分清单
<?xml version="1.0" encoding="UTF-8"?>

<openejb-jar xmlns="http://www.openejb.org/xml/ns/openejb-jar-2.1"
          xmlns:naming="http://geronimo.apache.org/xml/ns/naming-1.1">
   <dep:environment xmlns:dep="http://geronimo.apache.org/xml/ns/deployment-1.1">
      <dep:moduleId>
         <dep:groupId>default</dep:groupId>
         <dep:artifactId>CustomerEJB</dep:artifactId>
         <dep:version>1.0</dep:version>
         <dep:type>jar</dep:type>
      </dep:moduleId>

      <dep:dependencies>
         <dep:dependency>
            <dep:groupId>console.dbpool</dep:groupId>
            <dep:artifactId>CustomerServicePool</dep:artifactId>
            <dep:version>1.0</dep:version>
            <dep:type>rar</dep:type>
         </dep:dependency>
      </dep:dependencies>

      <dep:hidden-classes/>
      <dep:non-overridable-classes/>
   </dep:environment>

   <cmp-connection-factory>
      <resource-link>CustomerServicePool</resource-link>
   </cmp-connection-factory>

   <enterprise-beans>
      <entity>
         <ejb-name>CustomerEntityBean</ejb-name>
         <jndi-name>CustomerRemoteEntity</jndi-name>

         <resource-ref>
            <ref-name>jdbc/CustomerServiceDataSource</ref-name>
            <resource-link>CustomerServicePool</resource-link>
         </resource-ref>
      </entity>
   </enterprise-beans>
</openejb-jar>

<ejb-name> 的值必须与关联描述符文件中的 <ejb-name> 的值相对应。<jndi-name> 标记用于指定只有应用程序客户机才能使用的 JNDI 名称。这些客户机没有与应用程序归档 (.ear) 文件绑定。<ref-name> 标记与 清单 2<res-ref-name> 标记中的 JNDI 名称相对应。<resource-link> 标记用于将在 Geronimo 控制台中创建的 CustomerServicePool 与 <ref-name> 中的名称关联起来。

值得注意的是,1.1 版中特定于 Geronimo 的部署描述符的格式已经更改。先前的版本在标头中使用了 configIDparentID 属性来指定对象的 ID 和依赖性。这种格式现已替换为基于 Apache Maven 式样的命名约定的 moduleId 结构。它包含以下模式的四个元素:groupID/artifactID/version/type。此命名模式与 geronimo-1.1/repository 中的目录结构相对应。

上面的描述符文件中的 <dep:XXX> 标记与此约定相对应。例如,依赖性 CustomerServicePool 位于 console/dbpool/CustomerServicePool/1.0 的 Geronimo 存储库中。这个新约定意味着部署到 Geronimo 1.0 的应用程序必须更新。1.1 用户手册有一部分就是介绍如何执行更新操作的。不过,Geronimo 的未来版本都将支持此约定,因此部署到 1.1 的应用程序应当部署到那些最新版本上,而无需对描述符做任何更新。

现在,我们继续在清单 4 中配置会话 bean。

清单 4. ProcessCustomerEJB-ejb.xml 的部分清单
<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 
2.0//EN" "http://java.sun.com/dtd/ejb-jar_2_0.dtd">

<ejb-jar>
   <enterprise-beans>
      <session>
         <ejb-name>ProcessCustomerSessionBean</ejb-name>
         <home>com.service.customer.ejb.ProcessCustomerHome</home>
         <remote>com.service.customer.ejb.ProcessCustomer</remote>
         
<ejb-class>com.service.customer.ejb.ProcessCustomerSessionBean</ejb-class>
         <session-type>Stateless</session-type>
         <transaction-type>Container</transaction-type>

         <ejb-ref>
            <ejb-ref-name>ejb/CustomerEntityBean</ejb-ref-name>
            <ejb-ref-type>Entity</ejb-ref-type>
            <home>com.service.customer.ejb.CustomerHome</home>
            <remote>com.service.customer.ejb.Customer</remote>
         </ejb-ref>
      </session>
   </enterprise-beans>
</ejb-jar>

<ejb-name> 标记用于指定会话 bean ProcessCustomerSessionBean 的名称。<ejb-ref-name> 标记用于指定应用程序内的组件将使用的 JNDI 名称。在这种情况下,会话 bean 将对实体 bean 执行查找。清单 5 中列出了对应的特定于 Geronimo 的计划。

清单 5. ProcessCustomerEJB-openejb.xml 的清单
<?xml version="1.0" encoding="UTF-8"?>

<openejb-jar xmlns="http://www.openejb.org/xml/ns/openejb-jar-2.1"
        xmlns:naming="http://geronimo.apache.org/xml/ns/naming-1.1"
        xmlns:security="http://geronimo.apache.org/xml/ns/security-1.1"
        xmlns:dep="http://geronimo.apache.org/xml/ns/deployment-1.1">
   <dep:environment>
      <dep:moduleId>
         <dep:groupId>default</dep:groupId>
         <dep:artifactId>ProcessCustomerSessionBean</dep:artifactId>
         <dep:version>1.0</dep:version>
         <dep:type>jar</dep:type>
      </dep:moduleId>

      <dep:dependencies/>
      <dep:hidden-classes/>
      <dep:non-overridable-classes/>
   </dep:environment>

   <enterprise-beans>
      <session>
         <ejb-name>ProcessCustomerSessionBean</ejb-name>
         <jndi-name>ProcessCustomerRemoteSessionBean</jndi-name>

         <ejb-ref>
            <ref-name>ejb/CustomerEntityBean</ref-name>
            <ejb-link>CustomerEntityBean</ejb-link>
         </ejb-ref>
      </session>
   </enterprise-beans>
</openejb-jar>

<dep:moduleId> 用于指定此组件的全名。同实体 bean 一样,<ejb-name> 标记必须与先前清单的标准描述符中的 <ejb-name> 相对应。并且 <jndi-name> 标记包含仅由应用程序客户机使用的 JNDI 名称。最后,<ejb-ref> 标记用于把 JNDI 名称与 EJB 的名称关联起来。接下来是 Web 归档文件,该归档文件将从 Java bean 中引用会话 bean。清单 6 中显示了它的标准部署描述符。

清单 6. web.xml 的部分清单
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
         version="2.4">

  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>

  <ejb-ref>
    <ejb-ref-name>ejb/ProcessCustomerSessionBean</ejb-ref-name>
    <ejb-ref-type>Session</ejb-ref-type>
    <home>com.service.customer.ejb.ProcessCustomerHome</home>
    <remote>com.service.customer.ejb.ProcessCustomer</remote>
  </ejb-ref>
</web-app>

值得注意的主要条目是 <ejb-ref> 标记。它用于显示如何声明用于查找会话 bean 的 JNDI 名称。

Web 归档文件的特定于 Geronimo 的计划十分简单。它用于定义 Web 归档文件的全名并用于指定通过 Web 浏览器访问时可以找到应用程序的上下文根(参见清单 7)。

清单 7. geronimo-web.xml 的清单
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://geronimo.apache.org/xml/ns/j2ee/web-1.1"
         xmlns:naming="http://geronimo.apache.org/xml/ns/naming-1.1">
   <dep:environment xmlns:dep="http://geronimo.apache.org/xml/ns/deployment-1.1">
      <dep:moduleId>
         <dep:groupId>default</dep:groupId>
         <dep:artifactId>CustomerService-web</dep:artifactId>
         <dep:version>1.0</dep:version>
         <dep:type>war</dep:type>
      </dep:moduleId>

      <dep:dependencies/>
      <dep:hidden-classes/>
      <dep:non-overridable-classes/>
   </dep:environment>

   <context-root>/service</context-root>
</web-app>

Customer Service 实用程序被部署为 .ear 文件。因此,它有两个描述符文件 application.xml 和 geronimo-application.xml。但是,这两个文件都不包含任何 JNDI 声明,因此在这里不介绍这两个文件。至此声明 JNDI 名称并把它们与 EJB 组件关联起来就全部完成了!下一节将介绍如何在代码中查找这些对象。

Customer Service 实用程序

Customer Service 实用程序就要完成了。下一步是调用这些使用 JNDI 的 EJB 来访问数据库。完整的 JNDI 字符串遵守此命名约定,如清单 8 所示。

清单 8. customer.properties 中的 JNDI 名称清单
# Specify JNDI names here
jndi.customer.ejb=java:/comp/env/ejb/CustomerEntityBean
jndi.process.ejb=java:/comp/env/ejb/ProcessCustomerSessionBean

先前在 EJB 描述符的 <ejb-ref-name> 标记中声明的名称现在列在其 JNDI 全名上下文中。建议的命名约定说明对象的前缀应当是其对应的域。因此,实体 bean 和会话 bean 的前缀都是 ejb/。实体 bean 的描述符文件中声明的 CustomerServiceDataSource 使用 jdbc/,因为它是与数据库相关的。清单 9 中的代码将显示如何使用全名在会话 bean 内执行 CustomerEntityBean 的 JNDI 查找。

清单 9. ProcessCustomerSessionBean.java 的部分清单
package com.service.customer.ejb;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.rmi.PortableRemoteObject;

public class ProcessCustomerSessionBean implements SessionBean
{
   private ResourceBundle    bundle = null;
   private SessionContext    context = null;
   private CustomerHome      customerHome = null;
   private String            JNDI_CUSTOMER_EJB = null;

   // Create method(s)
   public void ejbCreate()
      throws CreateException
   {
      try
      {
         doJNDILookups();
      } // end try

      catch (Exception e)
      {
         throw new CreateException("ejbCreate: " + e.getMessage());
      } // end catch
   } // end ejbCreate

   // Support methods
   private void doJNDILookups()
      throws NamingException, ClassCastException
   {
      Object objref = null;
      InitialContext initial = null;

      bundle = ResourceBundle.getBundle("customer", Locale.getDefault(), 
ProcessCustomerSessionBean.class.getClassLoader());
      JNDI_CUSTOMER_EJB = bundle.getString("jndi.customer.ejb");

      initial = new InitialContext();
      objref = initial.lookup(JNDI_CUSTOMER_EJB);
      customerHome = (CustomerHome)PortableRemoteObject.narrow(objref, 
CustomerHome.class);
      System.out.println("looking up: " + JNDI_CUSTOMER_EJB);
   } // end doJNDILookups
} // end ProcessCustomerSessionBean

清单 9 中值得注意的主要条目是 doJNDILookups 方法。首先,从 customer.properties 文件中检索完整的 JNDI 名称。接下来,将创建 InitialContext,并将对 JNDI 名称执行查找。最后,严密的方法检查用于确保从 JNDI 检索到的对象可以被映射为所需的对象类型 CustomerHome。清单 10 将显示如何从 Java bean 执行查找。

清单 10. CustomerServiceJavaBean.java 的部分清单
public class CustomerServiceJavaBean
{
   private ProcessCustomerHome processHome = null;
   private ResourceBundle      bundle = null;
   private String              JNDI_PROCESS_EJB = null;

   public CustomerServiceJavaBean()
   {
      InitialContext initial = null;
      Object objref = null;

      bundle = ResourceBundle.getBundle("customer", Locale.getDefault(), 
CustomerServiceJavaBean.class.getClassLoader());
      JNDI_PROCESS_EJB = bundle.getString("jndi.process.ejb");

      try
      {
         initial = new InitialContext();
         objref = initial.lookup(JNDI_PROCESS_EJB);
         processHome = (ProcessCustomerHome)PortableRemoteObject.narrow(objref, 
ProcessCustomerHome.class);
         System.out.println("looking up: " + JNDI_PROCESS_EJB);
      } // end try

      catch (Exception e)
      {
         e.printStackTrace();
      } // end catch
   } // end CustomerServiceJavaBean
} // end CustomerServiceJavaBean

检索 ProcessCustomerSessionBean 的步骤与实体 bean 查找类似。只是这时,JNDI 名称不同,且 ProcessCustomerHome 类用于确认从 JNDI 检索到的对象。

Customer Service 实用程序现在已经准备好部署到 Geronimo 上:

  1. 修改 CustomerService/resources/build.properties 文件,并确保目录位置已正确定义。
  2. 接下来,打开控制台,然后将目录切换到 CustomerService/build.xml 的位置。然后键入 ant。这将构建并部署应用程序,此应用程序可在 http://localhost:8080/service 访问。

图 3 显示了数据输入页面的外观。

图 3. Customer Service 实用程序的数据输入页面
Customer Service 实用程序的数据输入页面
Customer Service 实用程序的数据输入页面

此时,只有最基本的客户信息存储在数据库中。

最后还有一个问题需要注意。开发人员可能会在通过 Geronimo 的控制台访问了 Derby 数据后再尝试访问 Customer Service 实用程序时遇到 SQL Exception 错误,反之亦然。重新引导 Geronimo 将更正一次错误,但随后再次访问数据库时又会生成异常。这个问题可能与 Geronimo 1.1 用户手册中 1.0 迁移部分的末尾所记录的条件有关(有关链接,请参阅 参考资料)。

当数据库连接池与 Derby (org.apache.derby/derby/10.1.1.0/jar) 或 Derbynet (org.apache.derby/derbynet/10.1.1.0/jar) 驱动程序具有依赖性时就会引发此条件。像这样保持依赖性将把 Derby 的另一个副本装入另一个类载入器从而导致 SQL Exception。Geronimo 数据库池向导将在使用嵌入式 Derby 时创建一个具有此类依赖性的计划。替代方法是剪切并粘贴生成的计划并将其作为应用程序归档文件的一部分部署。在这种方法中,数据库池将变成 Customer Service 实用程序专用的。现有的描述符计划需要修改,后续的清单从清单 11 开始将突出显示这些更改。

清单 11. 修改后的 CustomerServicePool-alt.xml
<dep:moduleId>
    <dep:groupId>default</dep:groupId>
    <dep:artifactId>CustomerServicePool</dep:artifactId>
    <dep:version>1.0</dep:version>
    <dep:type>rar</dep:type>
</dep:moduleId>

生成的计划是通过数据库池向导获得的。在名为 Step 4: Test Connection 的页面中,单击 Show Plan 按钮而不是 Deploy。在将生成的计划剪切、粘贴并保存到另一个文件(例如 CustomerServicePool-alt.xml)中后,必须修改 <groupId> 标记以匹配清单 11 的内容,因为 CustomerServicePool 现在将作为应用程序的一部分进行部署。因此,此 <groupId> 标记将与 geronimo-application.xml(清单 12 中显示了它的部分清单)中的 <groupId> 标记相对应。

清单 12. 替代的 geronimo-application.xml
<module>
    <connector>tranql-connector-1.2.rar</connector>
    <alt-dd>CustomerServicePool-alt.xml</alt-dd>
</module>

geronimo-application.xml 描述符将需要清单 12 中的附加标记。这对于清单 13 中所示的标准 application.xml 也是正确的。

清单 13. 替代的 application.xml
<module>
    <connector>tranql-connector-1.2.rar</connector>
</module>

application.xml 中也需要此附加标记用于替代的部署。清单 14 显示了描述符 CustomerEJB-openejb.xml 中所需的代码更改。

清单 14. 修改后的 CustomerEJB-openejb.xml
<dep:dependencies/>

可以将 CustomerServicePool 上的现有 <dependencies> 标记替换为清单 14 中所示的代码。

最后,tranql 驱动程序和修改后的数据库池计划需要被绑定到与应用程序归档文件(.ear 文件)内的 JAR 和 WAR 文件相同的级别。Geronimo 附带了 tranql 驱动程序,位于存储库文件夹 C 中。

结束语

希望本系列教程的第一部分已经帮助您了解了最常用的一种 JNDI 连接,它从 EJB 访问数据库池。您了解了如何使用 Geronimo 控制台来初始化数据库并配置数据库连接池。最后,Customer Service 实用程序向您展示了如何在代码内访问此连接池。请继续关注本系列教程的下一篇文章,在那里将讨论到 JMS 资源的 JNDI 连接,并将向您展示如何使用这些连接增强示例应用程序。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source, Java technology, WebSphere
ArticleID=261454
ArticleTitle=Apache Geronimo JNDI 命名和 Java 资源连接池,第 1 部分: 数据源连接
publish-date=06072007