利用 JNDI 访问数据源、Java 消息服务、邮件会话和 URL 连接的连接池
了解JNDI两个知识点?
a .JNDI 是什么:
JNDI (Java naming and directory)是应用程序编程接口。提供的方法有两种主要类型
1.将名称和和对象关联起来的
2.根据对象的名称在目录中查找对象
b. 数据源 是什么?()
数据源指对象或工厂,用于生成与应用程序内的实际数据源(通常是数据库)的连接。
在 Java Platform, Enterprise Edition (Java EE) 中,此数据源是指数据库连接池,一组随时可用的数据库连接。
创建数据源或数据库池的原因是它能够提高应用程序性能,因为即使打开一个连接也会十分占用 CPU。
c. 设置Geronimo
1.创建CustomerServiceDatabase数据库,并建customers表 插入数据。
2. 建立数据源。通过软件鼠标点击操作建立数据源与数据库的连接(不是通过配置描述建立两者之间的连接的)。
d. 创建Geronimo部署描述符
1.用户首先需要访问 Web 界面,然后在字段中键入信息。那些字段再被传递给引用会话 bean 的 Java bean。此会话 bean 将作用于客户数据,
方法是引用实体 bean 执行基本数据库操作。实体 bean 是客户的表中某一行的 Java 对象表示,并且是引用 CustomerServicePool 来访问数据库的组件。
清单 2 包含了此实体 bean 的标准 Java 部署描述符。
<?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 的关联文件。
OpenEJB是一个嵌入式、轻量级EJB3.0实现的服务器。
既可以作为单独运行,也可以嵌入到Tomcat、JUnit、Eclipse、Intellij、Maven、Ant和其它任何IDE与应用程序中。
OpenEJB 被用于Apple的WebObjects与Apache的Geronimo应用服务器中。
<?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 的部署描述符的格式已经更改。先前的版本在标头中使用了 configID
和 parentID
属性来指定对象的 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。
<?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>
现在,我们继续在清单 4 中配置会话 bean。
<?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 的计划。
<?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 中显示了它的标准部署描述符。
<?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)。
<?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 实用程序就要完成了。下一步是调用这些使用 JNDI 的 EJB 来访问数据库。完整的 JNDI 字符串遵守此命名约定,如清单 8 所示。
# 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 查找。
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 执行查找。
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 上:
ant
。这将构建并部署应用程序,此应用程序可在 http://localhost:8080/service 访问。图 3 显示了数据输入页面的外观。
此时,只有最基本的客户信息存储在数据库中。
最后还有一个问题需要注意。开发人员可能会在通过 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 开始将突出显示这些更改。
<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>
标记相对应。
<module> <connector>tranql-connector-1.2.rar</connector> <alt-dd>CustomerServicePool-alt.xml</alt-dd> </module>
geronimo-application.xml 描述符将需要清单 12 中的附加标记。这对于清单 13 中所示的标准 application.xml 也是正确的。
<module> <connector>tranql-connector-1.2.rar</connector> </module>
application.xml 中也需要此附加标记用于替代的部署。清单 14 显示了描述符 CustomerEJB-openejb.xml 中所需的代码更改。
<dep:dependencies/>
可以将 CustomerServicePool 上的现有 <dependencies>
标记替换为清单 14 中所示的代码。
最后,tranql 驱动程序和修改后的数据库池计划需要被绑定到与应用程序归档文件(.ear 文件)内的 JAR 和 WAR 文件相同的级别。Geronimo 附带了 tranql 驱动程序,位于存储库文件夹 C 中。
回页首
希望本系列教程的第一部分已经帮助您了解了最常用的一种 JNDI 连接,它从 EJB 访问数据库池。您了解了如何使用 Geronimo 控制台来初始化数据库并配置数据库连接池。最后,Customer Service 实用程序向您展示了如何在代码内访问此连接池。请继续关注本系列教程的下一篇文章,在那里将讨论到 JMS 资源的 JNDI 连接,并将向您展示如何使用这些连接增强示例应用程序。