Tomcat JDBC 數(shù)據(jù)源

2022-03-03 11:45 更新

概述

JNDI 數(shù)據(jù)源配置的相關(guān)內(nèi)容已經(jīng)在 JNDI 資源文檔中詳細(xì)介紹過(guò)。但從 Tomcat 用戶的反饋意見(jiàn)來(lái)看,有些配置的細(xì)節(jié)問(wèn)題非常棘手。

針對(duì)常用的數(shù)據(jù)庫(kù),我們已經(jīng)給 Tomcat 用戶提供了一些配置范例,以及關(guān)于數(shù)據(jù)庫(kù)使用的一些通用技巧。本章就將展示這些范例和技巧。

另外,雖然有些注意事項(xiàng)來(lái)自于用戶所提供的配置和反饋信息,但你可能也有不同的實(shí)踐。如果經(jīng)過(guò)試驗(yàn),你發(fā)現(xiàn)某些配置可能具有廣泛的助益作用,或者你覺(jué)得它們會(huì)使本章內(nèi)容更加完善,請(qǐng)務(wù)必不吝賜教。

請(qǐng)注意,對(duì)比 Tomcat 7.x 和 Tomcat 8.x,JNDI 資源配置多少有些不同,這是因?yàn)槭褂玫?Apache Commons DBCP 庫(kù)的版本不同所致。所以,為了在 Tomcat 8 中使用,你最好修改老版本的 JNDI 資源配置,以便能夠匹配下文范例中的格式。詳情可參看Tomcat 遷移文檔

另外還要提示的是,一般來(lái)說(shuō)(特別是對(duì)于本教程而言),JNDI 數(shù)據(jù)源配置會(huì)假定你已經(jīng)理解了 ContextHost 的配置偏好,其中包括在后者配置偏好中的應(yīng)用自動(dòng)部署的相關(guān)內(nèi)容。

DriverManager,服務(wù)提供者機(jī)制以及內(nèi)存泄露

java.sql.DriverManager 支持服務(wù)提供者機(jī)制。這項(xiàng)功能的實(shí)際作用在于:對(duì)于所有可用的 JDBC 驅(qū)動(dòng),只要它們聲明提供 META-INF/services/java.sql.Driver 文件,就會(huì)被自動(dòng)發(fā)現(xiàn)、加載并注冊(cè),從而減輕了我們?cè)趧?chuàng)建 JDBC 連接之前還需要顯式地加載數(shù)據(jù)庫(kù)驅(qū)動(dòng)的負(fù)擔(dān)。但在 servlet 容器環(huán)境的所有 Java 版本中,卻根本沒(méi)法實(shí)現(xiàn)這種功能。問(wèn)題在于 java.sql.DriverManager 只會(huì)掃描一次驅(qū)動(dòng)。

Tomcat 自帶的阻止 JRE 內(nèi)存泄漏偵聽(tīng)器可以在一定程度上解決這個(gè)問(wèn)題,它會(huì)在 Tomcat 啟動(dòng)時(shí)觸發(fā)驅(qū)動(dòng)掃描。該偵聽(tīng)器默認(rèn)是啟用的。只有可見(jiàn)于該偵聽(tīng)器的庫(kù)(比如 $CATALINA_BASE/lib 中的庫(kù))才能被數(shù)據(jù)庫(kù)驅(qū)動(dòng)所掃描。如果你想禁用該功能,那么一定要記?。菏紫仁褂? JDBC 的 Web 應(yīng)用會(huì)觸發(fā)掃描,從而當(dāng)該應(yīng)用重新加載時(shí)會(huì)出錯(cuò);對(duì)于其他依賴該功能的 Web 應(yīng)用來(lái)說(shuō)也會(huì)導(dǎo)致出錯(cuò)。

所以,假如應(yīng)用的 WEB-INF/lib 目錄中存在數(shù)據(jù)庫(kù)驅(qū)動(dòng),那么這些應(yīng)用就不能依賴服務(wù)提供者機(jī)制,而應(yīng)該顯式地注冊(cè)驅(qū)動(dòng)。

java.sql.DriverManager 中的驅(qū)動(dòng)已經(jīng)被認(rèn)為是內(nèi)存泄露之源。當(dāng) Web 應(yīng)用停止運(yùn)行時(shí),它所注冊(cè)的任何驅(qū)動(dòng)都必須重新注冊(cè)。當(dāng) Web 應(yīng)用停止運(yùn)行時(shí),Tomcat 會(huì)嘗試自動(dòng)尋找并重新注冊(cè)任何由 Web 應(yīng)用類加載器所加載的 JDBC 驅(qū)動(dòng)。但最好是由應(yīng)用通過(guò) ServletContextListener 來(lái)實(shí)現(xiàn)這一點(diǎn)。

數(shù)據(jù)庫(kù)連接池(DBCP 2)配置

Apache Tomcat 的默認(rèn)數(shù)據(jù)庫(kù)連接池實(shí)現(xiàn)基于的是 Apache Commons 項(xiàng)目的庫(kù),具體來(lái)說(shuō)是這兩個(gè)庫(kù):

  • Commons DBCP
  • Commons Pool

這兩個(gè)庫(kù)都位于一個(gè) JAR 文件中:$CATALINA_HOME/lib/tomcat-dbcp.jar。但該文件只包括連接池所需要的類,包名也已經(jīng)改變了,以避免與應(yīng)用沖突。

DBCP 2.0 支持 JDBC 4.1。

安裝

可參閱 DBCP 文檔了解完整的配置參數(shù)。

防止數(shù)據(jù)庫(kù)連接池泄露

數(shù)據(jù)庫(kù)連接池創(chuàng)建并管理著一些與數(shù)據(jù)庫(kù)的連接。與打開(kāi)新的連接相比,回收或重用現(xiàn)有的數(shù)據(jù)庫(kù)連接要更為高效一些。

連接池化還存在一個(gè)問(wèn)題。Web 應(yīng)用必須明確地關(guān)閉 ResultSet、Statement,以及 Connection。假如 Web 應(yīng)用無(wú)法關(guān)閉這些資源時(shí),會(huì)導(dǎo)致這些資源再也無(wú)法被重用,從而造成了數(shù)據(jù)庫(kù)連接池“泄露”。如果再也沒(méi)有可用連接時(shí),最終這將導(dǎo)致 Web 應(yīng)用數(shù)據(jù)庫(kù)連接失敗。

針對(duì)該問(wèn)題,有一個(gè)解決辦法:通過(guò)配置 Apache Commons DBCP,記錄并恢復(fù)這些廢棄的數(shù)據(jù)庫(kù)連接。它不僅能恢復(fù)這些連接,而且還能針對(duì)打開(kāi)這些連接而又永遠(yuǎn)不關(guān)閉它們的代碼生成堆棧跟蹤。

為了配置 DBCP 數(shù)據(jù)源來(lái)移除并回收廢棄的數(shù)據(jù)庫(kù)連接,將下列屬性(一個(gè)或全部)添加到你的 DBCP 數(shù)據(jù)源中的 Resource 配置中:

removeAbandonedOnBorrow=true

removeAbandonedOnMaintenance=true

以上屬性默認(rèn)都為 false。注意,只有當(dāng) timeBetweenEvictionRunsMillis 為正值,從而啟用池維護(hù)時(shí),removeAbandonedOnMaintenance 才能生效。關(guān)于這些屬性的詳情,可查看 DBCP 文檔

使用 removeAbandonedTimeout 屬性設(shè)置某個(gè)數(shù)據(jù)庫(kù)連接閑置的秒數(shù),超過(guò)此時(shí)段,即被認(rèn)為是廢棄連接。

removeAbandonedTimeout="60"

默認(rèn)的去除廢棄連接的超時(shí)為 300 秒。

logAbandoned 設(shè)為 true,可以讓 DBCP 針對(duì)那些拋棄數(shù)據(jù)庫(kù)連接資源的代碼,記錄堆棧跟蹤信息。

logAbandoned="true"

默認(rèn)為 false。

MySQL DBCP 范例

0. 簡(jiǎn)介

已報(bào)告的能夠正常運(yùn)作的 MySQL 與 JDBC 驅(qū)動(dòng)的版本號(hào)為:

  • MySQL 3.23.47、使用 InnoDB 的 MySQL 3.23.47、MySQL 3.23.58 以及 MySQL 4.0.1 alpha
  • Connector/J 3.0.11-stable (JDBC 官方驅(qū)動(dòng))
  • mm.mysql 2.0.14 (一個(gè)較老的 JDBC 第三方驅(qū)動(dòng))

在繼續(xù)下一步的操作之前,千萬(wàn)不要忘了將 JDBC 驅(qū)動(dòng)的 JAR 文件復(fù)制到 $CATALINA_HOME/lib 中。

1. MySQL 配置

一定要按照下面的說(shuō)明去操作,否則會(huì)出現(xiàn)問(wèn)題。

創(chuàng)建一個(gè)新的測(cè)試用戶、一個(gè)新的數(shù)據(jù)庫(kù),以及一張新的測(cè)試表。必須為 MySQL 用戶指定一個(gè)密碼。如果密碼為空,那么在連接時(shí),就會(huì)無(wú)法正常驅(qū)動(dòng)。

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);

注意:一旦測(cè)試結(jié)束,就該把上例中的這個(gè)用戶刪除!

下面在 testdata 表中插入一些測(cè)試數(shù)據(jù):

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 中添加資源聲明,以便在 Tomcat 中配置 JNDI 數(shù)據(jù)源。

范例如下:

<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 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 配置

為該測(cè)試應(yīng)用創(chuàng)建一個(gè) 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. 測(cè)試代碼

創(chuàng)建一個(gè)簡(jiǎn)單的 test.jsp 頁(yè)面,稍后將用到它。

<%@ 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 頁(yè)面用到了 JSTL 的 SQL 和 Core taglibs。你可以從 Apache Tomcat Taglibs - Standard Tag Library 項(xiàng)目中獲取它,不過(guò)要注意應(yīng)該是 1.1.x 或之后的版本。下載 JSTL 后,將 jstl.jarstandard.jar 復(fù)制到 Web 應(yīng)用的 WEB-INF/lib 目錄中。

最后,將你的應(yīng)用部署到 $CATALINA_BASE/webapps,可以采用兩種方式:或者將應(yīng)用以名叫 DBTest.war 的 WAR 文件形式部署;或者把應(yīng)用放入一個(gè)叫 DBTest 的子目錄中。

部署完畢后,就可以在瀏覽器輸入 http://localhost:8080/DBTest/test.jsp,查看你的第一個(gè)勞動(dòng)成果了。

Oracle 8i、9i 與 10g

0. 簡(jiǎn)介

Oracle 需要的配置和 MySQL 差不多,只不過(guò)也存在一些常見(jiàn)問(wèn)題。

針對(duì)過(guò)去版本的 Oracle 的驅(qū)動(dòng)可能以 .zip 格式(而不是 .jar 格式)進(jìn)行分發(fā)的。Tomcat 只使用 *.jar 文件,而且它們還必須安裝在 $CATALINA_HOME/lib 中。因此,classes111.zipclasses12.zip 這樣的文件后綴應(yīng)該改成 .jar。因?yàn)?jar 文件本來(lái)就是一種 zip 文件,因此不需要將原 zip 文件解壓縮然后創(chuàng)建相應(yīng)的 jar 文件,只需改換后綴名即可。

對(duì)于 Oracle 9i 之后的版本,應(yīng)該使用 oracle.jdbc.OracleDriver 而不是 oracle.jdbc.driver.OracleDriver,因?yàn)?Oracle 規(guī)定開(kāi)始棄用 oracle.jdbc.driver.OracleDriver,下一個(gè)重大版本將不再支持這一驅(qū)動(dòng)類。

1. 上下文配置

跟前文 MySql 的配置一樣,你也需要在 Context 中定義數(shù)據(jù)源。下面定義一個(gè)叫做 myoracle 的數(shù)據(jù)源,使用上文說(shuō)的短驅(qū)動(dòng)來(lái)連接(用戶名為 scott,密碼為 tiger)到名為 mysid 的SID(Oracle 系統(tǒng)ID,標(biāo)識(shí)一個(gè)數(shù)據(jù)庫(kù)的唯一標(biāo)示符)。 用戶 scott 使用的 Schema 就是默認(rèn)的 schema。

使用 OCI 驅(qū)動(dòng),只需在 URL 字符串中將 thin 變?yōu)?oci 即可。

<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 配置

在創(chuàng)建 Web 應(yīng)用的 web.xml 文件時(shí),一定要遵從 Web 應(yīng)用部署描述符文件中 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. 代碼范例

可以使用上文所列的范例應(yīng)用(假如你創(chuàng)建了所需的 DB 實(shí)例和表,等等),將數(shù)據(jù)源代碼用下面的代碼替換:

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. 簡(jiǎn)介

PostgreSQL 配置與 Oracle 基本相似。

1. 所需文件

將 Postgres 的 JDBC jar 文件復(fù)制到 $CATALINA_HOME/lib 中。和 Oracle 配置一樣,jar 文件必須放在這個(gè)目錄中,DBCP 類加載器才能找到它們。不管接下來(lái)如何配置,這是首先必須要做的。

2. 資源配置

目前有兩種選擇:定義一個(gè)能夠被 Tomcat 所有應(yīng)用所共享的數(shù)據(jù)源,或者定義只能被單個(gè)應(yīng)用所使用的數(shù)據(jù)源。

2a. 共享數(shù)據(jù)源配置

如果想定義能夠被多個(gè) Tomcat 應(yīng)用所共享的數(shù)據(jù)源,或者只想在文件中定義自己的數(shù)據(jù)源,則采用如下配置:

盡管有些用戶反饋說(shuō)這樣可行,但本文檔作者卻沒(méi)有成功,希望有人能闡述清楚。

<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. 應(yīng)用專屬的資源配置

如果希望專門為某一應(yīng)用定義數(shù)據(jù)源,其他 Tomcat 應(yīng)用無(wú)法使用,可以使用如下配置。這種方法對(duì) Tomcat 安裝的損害性要小一些。

在你的應(yīng)用的 Context 中創(chuàng)建一個(gè)資源定義,如下所示:

<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 配置

<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. 訪問(wèn)數(shù)據(jù)庫(kù)

在利用程序訪問(wèn)數(shù)據(jù)庫(kù)時(shí),記住把 java:/comp/env 放在你的 JNDI lookup 方法參數(shù)的前部,如下面這段代碼所示。另外,可以用任何你想用的值來(lái)替換 jdbc/postgres,不過(guò)記得也要用同樣的值來(lái)修改上面的資源定義文件。

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!");
}

非 DBCP 的解決方案

這些方案或者使用一個(gè)單獨(dú)的數(shù)據(jù)庫(kù)連接(建議僅作測(cè)試用?。蛘呤褂闷渌恍┏鼗夹g(shù)。

Oracle 8i 與 OCI 客戶端

簡(jiǎn)介

雖然并不能嚴(yán)格地解決如何使用 OCI 客戶端來(lái)創(chuàng)建 JNDI 數(shù)據(jù)源的問(wèn)題,但這些注意事項(xiàng)卻能和上文提到的 Oracle 與 DBCP 解決方案結(jié)合起來(lái)使用。

為了使用 OCI 驅(qū)動(dòng),應(yīng)該先安裝一個(gè) Oracle 客戶。你應(yīng)該已經(jīng)通過(guò)光盤安裝好了 Oracle 8i(8.1.7)客戶端,并從 otn.oracle.com 下載了適用的 JDBC/OCI 驅(qū)動(dòng)(Oracle8i 8.1.7.1 JDBC/OCI 驅(qū)動(dòng))。

classes12.zip 重命名為 classes12.jar 后,將其復(fù)制到 $CATALINA_HOME/lib 中。根據(jù) Tomcat 的版本以及你所使用的 JDK,你可能還必須該文件中的刪除 javax.sql.* 類。

連接起來(lái)

確保在 $PATHLD_LIBRARY_PATH(可能在 $ORAHOME\bin)目錄下存在 ocijdbc8.dll.so 文件,另外還要確認(rèn)能否使用 System.loadLibrary("ocijdbc8"); 這樣的簡(jiǎn)單測(cè)試程序加載本地庫(kù)。

下面你應(yīng)該創(chuàng)建一個(gè)簡(jiǎn)單測(cè)試用 servlet 或 jsp,其中應(yīng)該包含以下關(guān)鍵代碼:

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

目前數(shù)據(jù)庫(kù)是 host:port:SID 形式,如果你試圖訪問(wèn)測(cè)試用servlet/jsp,那么你會(huì)得到一個(gè) ServletException 異常,造成異常的根本原因在于 java.lang.UnsatisfiedLinkError:get_env_handle。

分析一下,首先 UnsatisfiedLinkError 表明:

  • JDBC 類文件和 Oracle 客戶端版本不匹配。消息中透露出的意思是沒(méi)有找到需要的庫(kù)文件。比如,你可能使用 Oracle 8.1.6 的 class12.zip 文件,而 Oracle 客戶端版本則是 8.1.5。classeXXXs.zip 文件必須與 Oracle 客戶端文件版本相一致。

  • 出現(xiàn)了一個(gè) $PATH, LD_LIBRARY_PATH 問(wèn)題。

  • 有報(bào)告稱,忽略從 otn 網(wǎng)站下載的驅(qū)動(dòng),使用 $ORAHOME\jdbc\lib 目錄中的 class12.zip 文件,同樣能夠正常運(yùn)作。

接下來(lái),你可能還會(huì)遇到另一個(gè)錯(cuò)誤消息:ORA-06401 NETCMN: invalid driver designator。

Oracle 文檔是這么說(shuō)的:“異常原因:登錄(連接)字符串包含一個(gè)不合法的驅(qū)動(dòng)標(biāo)識(shí)符。解決方法:修改字符串,重新提交?!彼裕缦旅孢@樣來(lái)修改數(shù)據(jù)庫(kù)(host:port:SID)連接字符串:

(description=(address=(host=myhost)(protocol=tcp)(port=1521))(connect_data=(sid=orcl)))

常見(jiàn)問(wèn)題

下面是一些 Web 應(yīng)用在使用數(shù)據(jù)庫(kù)時(shí)經(jīng)常會(huì)遇到的問(wèn)題,以及一些應(yīng)對(duì)技巧。

數(shù)據(jù)庫(kù)連接間歇性失敗

Tomcat 運(yùn)行在 JVM 中。JVM 周期性地會(huì)執(zhí)行垃圾回收(GC),清除不再使用的 Java 對(duì)象。當(dāng) JVM 執(zhí)行 GC 時(shí),Tomcat 中的代碼執(zhí)行就會(huì)終止。如果配置好的數(shù)據(jù)庫(kù)連接建立的最長(zhǎng)時(shí)間小于垃圾回收的時(shí)間,數(shù)據(jù)庫(kù)連接就會(huì)失敗。

在啟動(dòng) Tomcat 時(shí),將 -verbose:gc 參數(shù)添加到 CATALINA_OPTS 環(huán)境變量中,就能知道垃圾回收所占用的時(shí)間了。在啟用 verbose:gc 后, $CATALINA_BASE/logs/catalina.out 日志文件就能包含每次垃圾回收的數(shù)據(jù),其中也包括它所占用的時(shí)間。

正確調(diào)整 JVM 后,垃圾回收可以做到在 99% 的情況下占用時(shí)間不超過(guò) 1 秒。剩余的情況則只占用幾秒鐘的時(shí)間,只有極少數(shù)情況下 GC 會(huì)占用超過(guò) 10 秒鐘的時(shí)間。

保證讓數(shù)據(jù)庫(kù)連接超時(shí)設(shè)定在 10~15 秒。對(duì)于 DBCP,可以使用 maxWaitMillis 參數(shù)來(lái)設(shè)置。

隨機(jī)性的連接關(guān)閉異常

當(dāng)某一請(qǐng)求從連接池中獲取了一個(gè)數(shù)據(jù)庫(kù)連接,然后關(guān)閉了它兩次時(shí),往往會(huì)出現(xiàn)這樣的異常消息。使用連接池時(shí),關(guān)閉連接,就會(huì)把它歸還給連接池,以便之后其他的請(qǐng)求能夠重用該連接,而并不會(huì)關(guān)閉連接。Tomcat 使用多個(gè)線程來(lái)處理并發(fā)請(qǐng)求。下面這個(gè)范例就演示了,在 Tomcat 中,一系列事件導(dǎo)致了這種錯(cuò)誤。

運(yùn)行在線程 1 中的請(qǐng)求 1 獲取了一個(gè)連接。

請(qǐng)求 1 關(guān)閉了數(shù)據(jù)庫(kù)連接。

JVM 將運(yùn)行的線程切換為線程 2。

線程 2 中運(yùn)行的請(qǐng)求 2 獲取了一個(gè)數(shù)據(jù)庫(kù)連接。
(同一個(gè)數(shù)據(jù)庫(kù)連接剛被請(qǐng)求 1 關(guān)閉)

JVM 又將運(yùn)行的線程切換回為線程 1。

請(qǐng)求 1 第二次關(guān)閉了數(shù)據(jù)庫(kù)連接。

JVM 將運(yùn)行的線程切換回線程 2。

請(qǐng)求 2線程 2 試圖使用數(shù)據(jù)庫(kù)連接,但卻失敗了。因?yàn)?strong>請(qǐng)求 1 已經(jīng)關(guān)閉了它。

  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;
    }
  }

上下文與全局命名資源

注意,雖然在上面的說(shuō)明中,把 JNDI 聲明放在一個(gè) Context 元素里面,但還是有可能(而且有時(shí)更需要)把這些聲明放在服務(wù)器配置文件的 GlobalNamingResources 區(qū)域。被放置在 GlobalNamingResources 區(qū)域的資源將會(huì)被服務(wù)器的各個(gè)上下文所共享。

JNDI 資源命名和 Realm 交互

為了讓 Realm 能運(yùn)作,realm 必須指向定義在 <GlobalNamingResources><Context> 區(qū)域中的數(shù)據(jù)源,而不是<ResourceLink> 重新命名的數(shù)據(jù)源。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)