JNDI Datasource How-To

Table of Contents

Introduction

JNDI Datasource configuration is covered extensively in the JNDI-Resources-HOWTO. JNDI-Resources-HOWTO广泛介绍了JNDI数据源配置. However, feedback from tomcat-user has shown that specifics for individual configurations can be rather tricky. 但是,来自tomcat-user反馈表明,单个配置的细节可能非常棘手.

然后是一些已发布到tomcat-user的流行数据库的示例配置,以及一些有关db使用的常规提示.

您应该意识到,由于这些说明来自于配置和/或发布到tomcat-user YMMV :-)的反馈. 如果您认为其他任何经过测试的配置可能对更广泛的受众有用,请告诉我们,或者如果您认为我们仍然可以改进此部分,请告诉我们.

请注意,由于Tomcat 7.x和Tomcat 8.x使用不同版本的Apache Commons DBCP库,因此JNDI资源配置有所不同. 你很可能需要修改旧的JNDI资源配置,以使Tomcat的8看到他们工作的例子语法符合以下Tomcat的迁移指南了解详情.

另外,请注意,一般而言,尤其是本教程,JNDI DataSource配置都假定您已阅读并理解了ContextHost配置参考,包括后一参考中有关自动应用程序部署的部分.

DriverManager, the service provider mechanism and memory leaks

java.sql.DriverManager支持服务提供者机制. 此功能是,通过提供META-INF/services/java.sql.Driver文件来宣布自身的所有可用JDBC驱动程序都会被自动发现,加载和注册,从而使您无需在创建数据库驱动程序之前显式加载数据库驱动程序. JDBC连接. 但是,对于servlet容器环境,该实现在所有Java版本中都被破坏了. 问题是java.sql.DriverManager仅扫描一次驱动程序.

Apache Tomcat随附的JRE内存泄漏预防侦听器通过在Tomcat启动期间触发驱动程序扫描来解决此问题. 默认情况下启用. 这意味着将只扫描公共类加载器及其父级可见的库,以查找数据库驱动程序. 这包括$CATALINA_HOME/lib$CATALINA_BASE/lib驱动程序,类路径以及(如果JRE支持的话)认可目录. Web应用程序(位于WEB-INF/lib )和共享类加载器(已配置)中打包的驱动程序将不可见,也不会自动加载. 如果要考虑禁用此功能,请注意,扫描将由使用JDBC的第一个Web应用程序触发,从而导致在重新加载此Web应用程序以及其他依赖此功能的Web应用程序时失败.

因此,在其WEB-INF/lib目录中具有数据库驱动程序的Web应用程序不能依赖服务提供程序机制,而应显式注册驱动程序.

java.sql.DriverManager中的驱动程序列表也是内存泄漏的已知来源. Web应用程序停止时,必须注销该Web应用程序注册的所有驱动程序. 当Web应用程序停止时,Tomcat将尝试自动发现并注销由Web应用程序类加载器加载的所有JDBC驱动程序. 但是,期望应用程序通过ServletContextListener自行完成此操作.

Database Connection Pool (DBCP 2) Configurations

Apache Tomcat中的默认数据库连接池实现依赖于Apache Commons项目中的库. 使用以下库:

  • 公用DBCP 2
  • 公共游泳池2

这些库位于$CATALINA_HOME/lib/tomcat-dbcp.jar中的单个JAR中. 但是,仅包含连接池所需的类,并且已将包重命名以避免干扰应用程序.

DBCP 2提供了对JDBC 4.1的支持.

Installation

有关配置参数的完整列表,请参见DBCP 2文档 .

Preventing database connection pool leaks

数据库连接池创建和管理与数据库的连接池. 回收和重用数据库的现有连接比打开新连接更有效.

连接池存在一个问题. Web应用程序必须显式关闭ResultSet的,Statement的和Connection的. Web应用程序无法关闭这些资源可能导致它们再也无法重用,即数据库连接池"泄漏". 如果没有更多可用的连接,最终可能会导致Web应用程序数据库连接失败.

有一个解决此问题的方法. 可以将Apache Commons DBCP 2配置为跟踪和恢复这些废弃的数据库连接. 它不仅可以恢复它们,而且还为打开这些资源而从未关闭它们的代码生成堆栈跟踪.

要配置DBCP 2数据源,以便删除和回收废弃的数据库连接,请在DBCP 2数据源的" Resource配置中添加以下一个或两个属性:

removeAbandonedOnBorrow=true
removeAbandonedOnMaintenance=true

这两个属性的默认值为false . 需要注意的是removeAbandonedOnMaintenance具有除非池维护没有效果是通过设置启用timeBetweenEvictionRunsMillis为正值. 有关这些属性的完整文档,请参阅DBCP 2文档 .

使用removeAbandonedTimeout属性可以设置数据库连接在被认为被放弃之前已经空闲的秒数.

removeAbandonedTimeout="60"

删除废弃连接的默认超时为300秒.

如果您希望DBCP 2记录放弃了数据库连接资源的代码的堆栈跟踪,则可以将logAbandoned属性设置为true .

logAbandoned="true"

默认值为false .

MySQL DBCP 2 Example

0. Introduction

据报告可以正常工作的MySQL和JDBC驱动程序版本:

  • MySQL 3.23.47,使用InnoDB的MySQL 3.23.47,MySQL 3.23.58,MySQL 4.0.1alpha
  • Connector / J 3.0.11-stable(官方JDBC驱动程序)
  • mm.mysql 2.0.14(旧的第三方JDBC驱动程序)

在继续之前,请不要忘记将JDBC驱动程序的jar复制到$CATALINA_HOME/lib .

1. MySQL configuration

确保您遵循这些说明,否则会引起问题.

创建一个新的测试用户,一个新的数据库和一个测试表. 您的MySQL用户必须分配密码. 如果尝试使用空密码连接,驱动程序将失败.

mysql> GRANT ALL PRIVILEGES ON *.* TO javauser@localhost
    ->   IDENTIFIED BY 'javadude' WITH GRANT OPTION;
mysql> create database javatest;
mysql> use javatest;
mysql> create table testdata (
    ->   id int not null auto_increment primary key,
    ->   foo varchar(25),
    ->   bar int);
注意:测试完成后,应删除上述用户!

接下来,将一些测试数据插入到testdata表中.

mysql> insert into testdata values(null, 'hello', 12345);
Query OK, 1 row affected (0.00 sec)

mysql> select * from testdata;
+----+-------+-------+
| ID | FOO   | BAR   |
+----+-------+-------+
|  1 | hello | 12345 |
+----+-------+-------+
1 row in set (0.00 sec)

mysql>
2. Context configuration

通过将资源的声明添加到Context中,在Tomcat中配置JNDI数据源.

例如:

<Context>

    <!-- maxTotal: Maximum number of database connections in pool. Make sure you
         configure your mysqld max_connections large enough to handle
         all of your db connections. Set to -1 for no limit.
         -->

    <!-- maxIdle: Maximum number of idle database connections to retain in pool.
         Set to -1 for no limit.  See also the DBCP 2 documentation on this
         and the minEvictableIdleTimeMillis configuration parameter.
         -->

    <!-- maxWaitMillis: Maximum time to wait for a database connection to become available
         in ms, in this example 10 seconds. An Exception is thrown if
         this timeout is exceeded.  Set to -1 to wait indefinitely.
         -->

    <!-- username and password: MySQL username and password for database connections  -->

    <!-- driverClassName: Class name for the old mm.mysql JDBC driver is
         org.gjt.mm.mysql.Driver - we recommend using Connector/J though.
         Class name for the official MySQL Connector/J driver is com.mysql.jdbc.Driver.
         -->

    <!-- url: The JDBC connection url for connecting to your MySQL database.
         -->

  <Resource name="jdbc/TestDB" auth="Container" type="javax.sql.DataSource"
               maxTotal="100" maxIdle="30" maxWaitMillis="10000"
               username="javauser" password="javadude" driverClassName="com.mysql.jdbc.Driver"
               url="jdbc:mysql://localhost:3306/javatest"/>

</Context>
3. web.xml configuration

现在,为此测试应用程序创建一个WEB-INF/web.xml .

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
    version="2.4">
  <description>MySQL Test App</description>
  <resource-ref>
      <description>DB Connection</description>
      <res-ref-name>jdbc/TestDB</res-ref-name>
      <res-type>javax.sql.DataSource</res-type>
      <res-auth>Container</res-auth>
  </resource-ref>
</web-app>
4. Test code

现在创建一个简单的test.jsp页面,以供以后使用.

<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<sql:query var="rs" dataSource="jdbc/TestDB">
select id, foo, bar from testdata
</sql:query>

<html>
  <head>
    <title>DB Test</title>
  </head>
  <body>

  <h2>Results</h2>

<c:forEach var="row" items="${rs.rows}">
    Foo ${row.foo}<br/>
    Bar ${row.bar}<br/>
</c:forEach>

  </body>
</html>

该JSP页面使用了JSTL的SQL和Core标签库. 您可以从Apache Tomcat Taglibs-标准标记库项目中获得它-只需确保获得1.1.x或更高版本即可. 拥有JSTL后,将jstl.jarstandard.jar复制到Web应用程序的WEB-INF/lib目录中.

最后部署Web应用程序到$CATALINA_BASE/webapps无论是作为一个叫做warfile DBTest.war或成称为子目录DBTest

部署后,将浏览器指向http://localhost:8080/DBTest/test.jsp以查看辛勤工作的成果.

Oracle 8i, 9i & 10g

0. Introduction

除了通常的陷阱外,Oracle对MySQL配置的更改要求极低:-)

较早的Oracle版本的驱动程序可能以* .zip文件而不是* .jar文件的形式分发. Tomcat仅使用$CATALINA_HOME/lib安装的*.jar文件. 因此, classes111.zipclasses12.zip将需要使用.jar扩展名重命名. 由于jarfile是zipfile,因此无需解压缩和jar这些文件-一个简单的重命名就足够了.

对于Oracle 9i开始,你应该使用oracle.jdbc.OracleDriver而不是oracle.jdbc.driver.OracleDriver甲骨文已经表示, oracle.jdbc.driver.OracleDriver被弃用,该驱动程序类的支持将在下一主要版本中停止使用.

1. Context configuration

与上述mysql配置类似,您将需要在Context中定义数据源. 在这里,我们使用瘦驱动程序定义了一个名为myoracle的数据源,以用户scott的身份将密码Tiger连接到名为mysid的sid. (注意:对于瘦驱动程序,此sid与tnsname不同). 使用的模式将是用户scott的默认模式.

使用OCI驱动程序应该只涉及更改URL字符串中的oci到Thin.

<Resource name="jdbc/myoracle" auth="Container"
              type="javax.sql.DataSource" driverClassName="oracle.jdbc.OracleDriver"
              url="jdbc:oracle:thin:@127.0.0.1:1521:mysid"
              username="scott" password="tiger" maxTotal="20" maxIdle="10"
              maxWaitMillis="-1"/>
2. web.xml configuration

创建应用程序web.xml文件时,应确保遵守DTD定义的元素顺序.

<resource-ref>
 <description>Oracle Datasource example</description>
 <res-ref-name>jdbc/myoracle</res-ref-name>
 <res-type>javax.sql.DataSource</res-type>
 <res-auth>Container</res-auth>
</resource-ref>
3. Code example

您可以使用与上述相同的示例应用程序(假设您创建了所需的数据库实例,表等),将Datasource代码替换为类似的内容

Context initContext = new InitialContext();
Context envContext  = (Context)initContext.lookup("java:/comp/env");
DataSource ds = (DataSource)envContext.lookup("jdbc/myoracle");
Connection conn = ds.getConnection();
//etc.

PostgreSQL

0. Introduction

PostgreSQL的配置与Oracle类似.

1. Required files

将Postgres JDBC jar复制到$ CATALINA_HOME / lib. 与Oracle一样,这些jar必须位于此目录中,以便DBCP 2的Classloader找到它们. 无论接下来要执行哪个配置步骤,都必须执行此操作.

2. Resource configuration

You have two choices here: define a datasource that is shared across all Tomcat applications, or define a datasource specifically for one application.

2a. 共享资源配置

如果您希望定义一个在多个Tomcat应用程序之间共享的数据源,或者仅希望在此文件中定义数据源,请使用此选项.

尽管没有其他人报道,但该作者在这里没有取得成功. 澄清将不胜感激.

<Resource name="jdbc/postgres" auth="Container"
          type="javax.sql.DataSource" driverClassName="org.postgresql.Driver"
          url="jdbc:postgresql://127.0.0.1:5432/mydb"
          username="myuser" password="mypasswd" maxTotal="20" maxIdle="10" maxWaitMillis="-1"/>
2b. 特定于应用程序的资源配置

如果您希望定义特定于您的应用程序的数据源,而其他Tomcat应用程序不可见,请使用此选项. 此方法对Tomcat安装的侵入性较小.

为您的Context创建资源定义. Context元素应类似于以下内容.

<Context>

<Resource name="jdbc/postgres" auth="Container"
          type="javax.sql.DataSource" driverClassName="org.postgresql.Driver"
          url="jdbc:postgresql://127.0.0.1:5432/mydb"
          username="myuser" password="mypasswd" maxTotal="20" maxIdle="10"
maxWaitMillis="-1"/>
</Context>
3. web.xml configuration
<resource-ref>
 <description>postgreSQL Datasource example</description>
 <res-ref-name>jdbc/postgres</res-ref-name>
 <res-type>javax.sql.DataSource</res-type>
 <res-auth>Container</res-auth>
</resource-ref>
4. Accessing the datasource

以编程方式访问数据源时,请记住在java:/comp/env添加JNDI查找,如下面的代码片段所示. 还请注意," jdbc / postgres"可以替换为您喜欢的任何值,前提是您也可以在上述资源定义文件中对其进行更改.

InitialContext cxt = new InitialContext();
if ( cxt == null ) {
   throw new Exception("Uh oh -- no context!");
}

DataSource ds = (DataSource) cxt.lookup( "java:/comp/env/jdbc/postgres" );

if ( ds == null ) {
   throw new Exception("Data source not found!");
}

Non-DBCP Solutions

这些解决方案或者利用到数据库的单一连接(除了测试之外,不建议使用其他任何连接!)或采用其他某种池化技术.

Oracle 8i with OCI client

Introduction

尽管不严格解决使用OCI客户端创建JNDI数据源的问题,但这些说明可以与上述Oracle和DBCP 2解决方案结合使用.

为了使用OCI驱动程序,您应该安装了Oracle客户端. 您应该已经从cd安装了Oracle8i(8.1.7)客户端,并从otn.oracle.com下载了合适的JDBC / OCI驱动程序(Oracle8i 8.1.7.1 JDBC / OCI驱动程序).

将Tomcat的classes12.zip文件重命名为classes12.jar ,将其复制到$CATALINA_HOME/lib . 您可能还必须从此文件中删除javax.sql.*类,具体取决于所使用的Tomcat和JDK的版本.

Putting it all together

确保$PATHLD_LIBRARY_PATH可能包含ocijdbc8.dll.so (可能在$ORAHOME\bin ),并确认可以通过使用System.loadLibrary("ocijdbc8");的简单测试程序来加载本机库System.loadLibrary("ocijdbc8");

接下来,您应该创建一个具有以下关键行的简单测试servlet或JSP:

DriverManager.registerDriver(new
oracle.jdbc.driver.OracleDriver());
conn =
DriverManager.getConnection("jdbc:oracle:oci8:@database","username","password");

现在数据库的格式为host:port:SID现在,如果您尝试访问测试servlet / JSP的URL,并且得到的是一个ServletException ,其根本原因是java.lang.UnsatisfiedLinkError:get_env_handle .

首先, UnsatisfiedLinkError表示您有

  • JDBC类文件和Oracle客户端版本之间不匹配. 这里的赠品是一条消息,指出找不到所需的库文件. 例如,您可能将Oracle版本8.1.6中的classes12.zip文件与版本8.1.5 Oracle客户端一起使用. classesXXX.zip文件和Oracle客户端软件版本必须匹配.
  • A $PATH, LD_LIBRARY_PATH problem.
  • 据报道,也可以忽略从otn下载的驱动程序,并使用目录$ORAHOME\jdbc\lib的classes12.zip文件.

接下来,您可能会遇到错误ORA-06401 NETCMN: invalid driver designator

Oracle文档说:"原因:登录(连接)字符串包含无效的驱动程序指示符.操作:更正该字符串并重新提交." 更改数据库连接字符串(格式为host:port:SID ):( (description=(address=(host=myhost)(protocol=tcp)(port=1521))(connect_data=(sid=orcl)))

埃德 嗯,如果您整理出TNSName,我认为这不是真的需要-但我不是Oracle DBA :-)

Common Problems

这是使用数据库的Web应用程序遇到的一些常见问题,以及解决这些问题的技巧.

Intermittent Database Connection Failures

Tomcat在JVM中运行. JVM定期执行垃圾回收(GC)来删除不再使用的Java对象. 当JVM执行GC时,Tomcat中的代码将冻结. 如果配置用于建立数据库连接的最大时间少于垃圾回收所花费的时间,则可能会导致数据库连接失败.

To collect data on how long garbage collection is taking add the -verbose:gc argument to your CATALINA_OPTS environment variable when starting Tomcat. When verbose gc is enabled your $CATALINA_BASE/logs/catalina.out log file will include data for every garbage collection including how long it took.

当您的JVM正确地调整了99%的时间时,GC将花费不到一秒钟的时间. 其余的只需几秒钟. 很少,如果有的话,GC需要花费超过10秒的时间.

确保数据库连接超时设置为10-15秒. 对于DBCP 2,可以使用参数maxWaitMillis .

Random Connection Closed Exceptions

These can occur when one request gets a db connection from the connection pool and closes it twice. When using a connection pool, closing the connection just returns it to the pool for reuse by another request, it doesn't close the connection. And Tomcat uses multiple threads to handle concurrent requests. Here is an example of the sequence of events which could cause this error in Tomcat:

  Request 1 running in Thread 1 gets a db connection.

  Request 1 closes the db connection.

  The JVM switches the running thread to Thread 2

  Request 2 running in Thread 2 gets a db connection
  (the same db connection just closed by Request 1).

  The JVM switches the running thread back to Thread 1

  Request 1 closes the db connection a second time in a finally block.

  The JVM switches the running thread back to Thread 2

  Request 2 Thread 2 tries to use the db connection but fails
  because Request 1 closed it.

这是使用从连接池获得的数据库连接的正确编写的代码示例:

  Connection conn = null;
  Statement stmt = null;  // Or PreparedStatement if needed
  ResultSet rs = null;
  try {
    conn = ... get connection from connection pool ...
    stmt = conn.createStatement("select ...");
    rs = stmt.executeQuery();
    ... iterate through the result set ...
    rs.close();
    rs = null;
    stmt.close();
    stmt = null;
    conn.close(); // Return to connection pool
    conn = null;  // Make sure we don't close it twice
  } catch (SQLException e) {
    ... deal with errors ...
  } finally {
    // Always make sure result sets and statements are closed,
    // and the connection is returned to the pool
    if (rs != null) {
      try { rs.close(); } catch (SQLException e) { ; }
      rs = null;
    }
    if (stmt != null) {
      try { stmt.close(); } catch (SQLException e) { ; }
      stmt = null;
    }
    if (conn != null) {
      try { conn.close(); } catch (SQLException e) { ; }
      conn = null;
    }
  }

Context versus GlobalNamingResources

请注意,尽管以上说明将JNDI声明放置在Context元素中,但有时甚至可能希望将这些声明放置在服务器配置文件的GlobalNamingResources部分中. 放置在GlobalNamingResources部分中的资源将在服务器的上下文之间共享.

JNDI Resource Naming and Realm Interaction

为了使领域工作,领域必须引用<GlobalNamingResources>或<Context>部分中定义的数据源,而不是使用<ResourceLink>重命名的数据源.

by  ICOPY.SITE