连接池和数据源

JDBC 2 在一个名为 JDBC 2.0 可选包(也称为 JDBC 2.0 标准扩展)的附加 API 中引入了标准连接池功能。这些功能后来被包含在核心 JDBC 3 API 中。

JDBC API 为连接池提供客户端和服务器接口。客户端接口是 javax.sql.DataSource,应用程序代码通常会使用它来获取池化的数据库连接。服务器接口是 javax.sql.ConnectionPoolDataSource,大多数应用程序服务器将通过它与 PostgreSQL® JDBC 驱动程序交互。

在应用程序服务器环境中,应用程序服务器配置通常会引用 PostgreSQL® ConnectionPoolDataSource 实现,而应用程序组件代码通常会获取应用程序服务器(而不是 PostgreSQL®)提供的 DataSource 实现。

对于没有应用程序服务器的环境,PostgreSQL® 提供了两种 DataSource 实现,应用程序可以直接使用。一种实现执行连接池,而另一种只是通过 DataSource 接口提供对数据库连接的访问,而没有任何池化。同样,这些实现不应在应用程序服务器环境中使用,除非应用程序服务器不支持 ConnectionPoolDataSource 接口。

PostgreSQL® 包含一个名为 org.postgresql.ds.PGConnectionPoolDataSourceConnectionPoolDataSource 实现。

JDBC 要求通过 JavaBean 属性配置 ConnectionPoolDataSource,如 表 11.1,“ConnectionPoolDataSource 配置属性” 所示,因此每个属性都有 get 和 set 方法。

属性 类型 描述
serverName 字符串 PostgreSQL® 数据库服务器主机名
databaseName 字符串 PostgreSQL® 数据库名称
portNumber 整数 PostgreSQL® 数据库服务器正在监听的 TCP 端口(或 0 以使用默认端口)
user 字符串 用于建立数据库连接的用户
password 字符串 用于建立数据库连接的密码
ssl 布尔值 如果为 true,则使用 SSL 加密连接(默认值为 false)。
sslfactory 字符串 自定义 javax.net.ssl.SSLSocketFactory 类名(请参阅名为 “自定义 SSLSocketFactory” 的部分)。
defaultAutoCommit 布尔值 当连接提供给调用者时,是否应启用或禁用自动提交。默认值为 false,禁用自动提交。

许多应用程序服务器使用属性样式语法来配置这些属性,因此将属性输入为文本块并不罕见。如果应用程序服务器提供一个区域来输入所有属性,它们可能像这样列出:

serverName=localhost

databaseName=test

user=testuser

password=testpassword

或者,如果使用分号作为分隔符而不是换行符,它可能看起来像这样:

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

PostgreSQL® 包含两个 DataSource 实现,如 表 11.2,“DataSource 实现” 所示。

一个实现池化,另一个不实现池化。池化实现实际上不会在客户端调用 close() 方法时关闭连接,而是将连接返回到可用连接池中,供其他客户端使用。这避免了反复打开和关闭连接的开销,并允许大量客户端共享少量数据库连接。

这里提供的池化数据源实现并不是世界上功能最丰富的实现。除其他事项外,连接在池本身关闭之前永远不会关闭;没有办法缩小池。此外,为默认配置用户以外的用户请求的连接不会被池化。它的错误处理有时无法从池中删除损坏的连接。一般来说,不建议使用 PostgreSQL® 提供的连接池。检查您的应用程序服务器或查看优秀的 jakarta commons DBCP 项目。

连接池 实现类
`org.postgresql.ds. PGSimpleDataSource`
`org.postgresql.ds. PGPoolingDataSource`

两种实现使用相同的配置方案。JDBC 要求通过 JavaBean 属性配置 DataSource,如 表 11.3,“DataSource 配置属性” 中所示,因此每个属性都有 get 和 set 方法。

属性 类型 描述
serverName 字符串 PostgreSQL® 数据库服务器主机名
databaseName 字符串 PostgreSQL® 数据库名称
portNumber 整数 PostgreSQL® 数据库服务器正在监听的 TCP 端口(或 0 以使用默认端口)
user 字符串 用于建立数据库连接的用户
password 字符串 用于建立数据库连接的密码
ssl 布尔值 如果为真,则使用 SSL 加密连接(默认值为假)
sslfactory 字符串 自定义 javax.net.ssl. SSLSocketFactory 类名(参见“自定义 SSLSocketFactory”部分)

连接池实现需要一些额外的配置属性,如 表 11.4,“附加连接池 DataSource 配置属性” 中所示。

属性 类型 描述
dataSourceName 字符串 每个池化 DataSource 必须具有唯一的名称。
initialConnections 整数 池初始化时要创建的数据库连接数。
maxConnections 整数 允许的最大打开数据库连接数。当请求更多连接时,调用者将挂起,直到连接返回到池中。

示例 11.1, “DataSource 代码示例” 显示了使用池化 DataSource 的典型应用程序代码示例。

初始化池化 DataSource 的代码可能如下所示

PGPoolingDataSource source = new PGPoolingDataSource();
source.setDataSourceName("A Data Source");
source.setServerNames(new String[] {
    "localhost"
});
source.setDatabaseName("test");
source.setUser("testuser");
source.setPassword("testpassword");
source.setMaxConnections(10);

注意

setServerName 已被弃用,取而代之的是 setServerNames。这样做是为了支持多个主机。

然后,使用池中连接的代码可能如下所示。

注意

至关重要的是,连接最终要关闭。否则,池将“泄漏”连接,并最终锁定所有客户端。

try (Connection conn = source.getConnection()) {
    // use connection
} catch (SQLException e) {
    // log error
}

所有 ConnectionPoolDataSourceDataSource 实现都可以存储在 JNDI 中。对于非池化实现,每次从 JNDI 中检索对象时都会创建一个新实例,其设置与存储的实例相同。对于池化实现,只要实例可用(例如,不是不同的 JVM 从 JNDI 中检索池),就会检索相同的实例,否则将创建一个具有相同设置的新实例。

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

在应用环境中,应用可以将 DataSource 存储在 JNDI 中,这样它就不必对所有可能需要使用它的应用组件提供对 DataSource 的引用。一个示例如 示例 11.2,“DataSource JNDI 代码示例” 所示。

初始化连接池 DataSource 并将其添加到 JNDI 的应用程序代码可能如下所示

PGPoolingDataSource source = new PGPoolingDataSource();
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 conn = null;
try {
    DataSource source = (DataSource) new InitialContext().lookup("DataSource");
    conn = source.getConnection();
    // use connection
} catch (SQLException e) {
    // log error
} catch (NamingException e) {
    // DataSource wasn't found in JNDI
} finally {
    if (con != null) {
        try {
            conn.close();
        } catch (SQLException e) {}
    }
}

注意

postgresql.jar 文件必须放在 Tomcat 4 和 5 的 $CATALINA_HOME/common/lib 中。

在任何一个 Tomcat 实例中设置此项的最简单方法是使用 Tomcat 附带的管理员 Web 应用程序,只需将数据源添加到您想要使用的上下文中。

Tomcat 4 的设置,将以下内容放在 conf/server.xml 中的 < Context> 标签内

<Resource name="jdbc/postgres" scope="Shareable" type="javax.sql.DataSource"/>
<ResourceParams name="jdbc/postgres">
	<parameter>
		<name>validationQuery</name>
		<value>select version();</value>
	</parameter>
	<parameter>
		<name>url</name>
		<value>jdbc:postgresql://localhost/davec</value>
	</parameter>
	<parameter>
		<name>password</name>
		<value>davec</value>
	</parameter>
	<parameter>
		<name>maxActive</name>
		<value>4</value>
	</parameter>
	<parameter>
		<name>maxWait</name>
		<value>5000</value>
	</parameter>
	<parameter>
		<name>driverClassName</name>
		<value>org.postgresql.Driver</value>
	</parameter>
	<parameter>
		<name>username</name>
		<value>davec</value>
	</parameter>
	<parameter>
		<name>maxIdle</name>
		<value>2</value>
	</parameter>
</ResourceParams>

Tomcat 5 的设置,您可以使用上述方法,只是它位于 < Host> 标签内的 < DefaultContext> 标签内。例如 < Host> … < DefaultContext> …

或者,有一个 conf/Catalina/hostname/context.xml 文件。例如,http://localhost:8080/servlet-example 有一个目录 $CATALINA_HOME/conf/Catalina/localhost/servlet-example.xml 文件。在这个文件中,将上面的 xml 代码放在 < Context> 标签内

然后,您可以使用以下代码访问连接。

import javax.naming.*;
import javax.sql.*;
import java.sql.*;
public class DBTest {

    String foo = "Not Connected";
    int bar = -1;

    public void init() {
        try {
            Context ctx = new InitialContext();
            if (ctx == null)
                throw new Exception("Boom - No Context");

            // /jdbc/postgres is the name of the resource above
            DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/postgres");

            if (ds != null) {
                Connection conn = ds.getConnection();

                if (conn != null) {
                    foo = "Got Connection " + conn.toString();
                    Statement stmt = conn.createStatement();
                    ResultSet rst = stmt.executeQuery("select id, foo, bar from testdata");

                    if (rst.next()) {
                        foo = rst.getString(2);
                        bar = rst.getInt(3);
                    }
                    conn.close();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public String getFoo() {
        return foo;
    }

    public int getBar() {
        return bar;
    }
}