31.10. 连接池和数据源

JDBC 2 以一个附加的 API 的方式引入了标准连接池的特性,这个 附加的 API 叫做 JDBC 2.0 可选包(也称作 JDBC 2.0 标准扩展)。因此这些特性就被包含到核心的 JDBC 3 API 中了。 如果用 JDK 1.3.x 和 JDBC 2.0 可选包 一起编译 JDBC 驱动,那么 PostgreSQL JDBC 驱动 就支持这些特性,或者是用 JDK 1.4 或更高的版本 (JDBC 3)一起,也支持这个特性。大多数应用服务器包含 JDBC 2.0 可选包,但我们也可以从 SUN 单独获得这些东西: JDBC下载站

31.10.1. 概述

JDBC API 为连接池提供了一个客户端和一个服务器端的接口。 客户端接口是 javax.sql.DataSource, 通常就是应用代码用来请求一个缓冲了的数据库连接的东西。 服务器接口是 javax.sql.ConnectionPoolDataSource, 通常是大多数应用服务器和 PostgreSQL JDBC 驱动打交道的接口。

在应用服务器环境里,应用服务器配置通常将指向 PostgreSQL ConnectionPoolDataSource 实现, 而应用组件代码将通常要求一个由应用服务器实现的 DataSource(不是由 PostgreSQL)。

在一个没有应用服务器的环境里,PostgreSQL 提供了两种应用可以直接使用的 DataSource 的实现。 一种实现执行连接池,另外一种只是简单的通过 DataSource 接口提供访问数据库的连接, 而不使用任何缓冲池。同样,这些实现不应该在一个应用服务器环境里使用, 除非应用服务器不支持 ConnectionPoolDataSource 接口。

31.10.2. 应用服务器:ConnectionPoolDataSource

PostgreSQL 包含了一个用于 JDBC 2 的 ConnectionPoolDataSource 的实现,以及一个用于 JDBC 3 的实现:如 Table 31-1 所示。

Table 31-1. ConnectionPoolDataSource实现

JDBC实现类
2org.postgresql.jdbc2.optional.ConnectionPool
3org.postgresql.jdbc3.Jdbc3ConnectionPool

两种实现都使用了同样的配置模式。JDBC 要求一个 ConnectionPoolDataSource 通过 JavaBean 属性来实现, 在 Table 31-2 里显示, 因此每个这样的属性都存在获取和设置方法:

Table 31-2. ConnectionPoolDataSource 配置属性

属性类型描述
serverNameStringPostgreSQL 数据库服务器主机名
databaseNameStringPostgreSQL 数据库名
portNumberint PostgreSQL 数据库服务器监听的 TCP/IP 端口 (为 0 则使用缺省端口)
userString用来进行数据库连接的用户
passwordString用来进行数据库连接的口令
defaultAutoCommitboolean提交给调用者的时候连接是应该打开 autoCommit 还是应该关闭。 缺省是 false,关闭 autoCommit。

许多应用服务器使用属性风格的语法来配置这些属性, 因此把属性当作一块文本输入应该并不罕见。 如果应用服务器提供了单块的区域输入所有属性, 那么它们应该像下面这样列出:

serverName=localhost
databaseName=test
user=testuser
password=testpassword

或者,如果使用分号而不是换行做分隔符,那么看起来像:

serverName=localhost;databaseName=test;user=testuser;password=testpassword

31.10.3. 应用:DataSource

PostgreSQL 包含两个用于 JDBC 2 的 DataSource,还有两个用于 JDBC 3的, 如 Table 31-3 所示。 连接池的实现在客户端调用 close 方法的时候实际上并不关闭连接, 而是把连接返回到一个可用连接的连接池中给其它客户端使用。 这样就避免了任何重复打开和关闭连接造成的开销,并且允许大量的客户端分享相对较少的数据库连接。

这里提供的连接池数据源实现并非世上特性最丰富的。 比如,连接在池本身关闭之前绝对不会关闭;而且也没有办法缩小连接池。 同样,非缺省配置的用户的连接请求无法如池。许多应用服务器提供更加高级的连接池特性,并且使用的是 ConnectionPoolDataSource 实现。

Table 31-3. DataSource 实现

JDBC连接池实现类
2org.postgresql.jdbc2.optional.SimpleDataSource
2org.postgresql.jdbc2.optional.PoolingDataSource
3org.postgresql.jdbc3.Jdbc3SimpleDataSource
3org.postgresql.jdbc3.Jdbc3PoolingDataSource

所有实现使用同样的配置模式。JDBC 要求 DataSource 通过 JavaBean 属性配置, 在 Table 31-4 里列出。 因此每种这个属性都有获取和设置属性的方法。

Table 31-4. DataSource 配置属性

属性类型描述
serverNameStringPostgreSQL 数据库服务器主机名
databaseNameStringPostgreSQL 数据库名
portNumberint PostgreSQL 数据库服务器监听的 TCP 端口 (或者 0 表示使用缺省端口)
userString用于连接数据库的用户
passwordString用于连接数据库的口令

连接池实现要求一些额外的配置属性,它们在 Table 31-5 显示。

Table 31-5. 额外的连接池DataSource配置属性

属性类型描述
dataSourceNameString每一个连接池 DataSource 必须有一个唯一的名字
initialConnectionsint连接池初始化的时候要创建的数据库连接数目
maxConnectionsint允许打开的最大数据库连接个数。如果请求了更多的连接, 那么调用者将挂起,直到有一个连接返回给连接池。

Example 31-9 显示了一个使用连接池 DataSource 的典型应用的代码:

Example 31-9. DataSource 代码例子

初始化连接池DataSource的代码例子看起来会象这样:

Jdbc3PoolingDataSource source = new Jdbc3PoolingDataSource();
source.setDataSourceName("A Data Source");
source.setServerName("localhost");
source.setDatabaseName("test");
source.setUser("testuser");
source.setPassword("testpassword");
source.setMaxConnections(10);

然后使用来自连接池的代码看起来会象这样。 请注意最终要关闭连接是非常关键的,否则这个池子就会"泄漏"连接, 最后把所有客户端都锁在外面。

Connection con = null;
try {
    con = source.getConnection();
    // 使用连接
} catch (SQLException e) {
    // 记录错误
} finally {
    if (con != null) {
        try { con.close(); } catch (SQLException e) {}
    }
}

31.10.4. 数据源和 JNDI

所有 ConnectionPoolDataSourceDataSource 实现都可以存储在 JNDI 里。在非连接池的实现中, 每次从 JNDI 中检索对象都将创建一个新的实例, 带有和存储的实例同样的设置。对于连接池实现而言,同一个实例是在可得的情况下检索出来的(也就是说,没有其它 JVMJNDI 中检索连接池),否则就创建同样设置的新的实例。

在应用服务器环境,通常是应用服务器的 DataSource 实例将存储在 JNDI 中,而不是 PostgreSQL ConnectionPoolDataSource 的实现。

在应用服务器环境,应用可以在 JNDI 中存储 DataSource, 这样它就不用制作一个指向 DataSource 的引用提供给 所有可能需要的应用组件使用它。一个这方面的例子在 Example 31-10 给出。

Example 31-10. DataSource JNDI 代码例子

初始化连接池DataSource并且把它加到 JNDI 的代码看起来可能象这样:

Jdbc3PoolingDataSource source = new Jdbc3PoolingDataSource();
source.setDataSourceName("A Data Source");
source.setServerName("localhost");
source.setDatabaseName("test");
source.setUser("testuser");
source.setPassword("testpassword");
source.setMaxConnections(10);
new InitialContext().rebind("DataSource", source);

然后,使用来自连接池的代码看起来像这个样子:

Connection con = null;
try {
    DataSource source = (DataSource)new InitialContext().lookup("DataSource");
    con = source.getConnection();
    // 使用连接
} catch (SQLException e) {
    // 记录错误
} catch (NamingException e) {
    // 在 JNDI 里没有找到 DataSource
} finally {
    if (con != null) {
        try { con.close(); } catch (SQLException e) {}
    }
}