连接池和数据源
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.PGConnectionPoolDataSource
的 ConnectionPoolDataSource
实现。
JDBC 要求通过 JavaBean 属性配置 ConnectionPoolDataSource
,如 表 11.1,“ConnectionPoolDataSource
配置属性” 所示,因此每个属性都有 get 和 set 方法。
表 11.1. ConnectionPoolDataSource
配置属性
属性 | 类型 | 描述 |
---|---|---|
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 项目。
表 11.2. DataSource
实现
连接池 | 实现类 |
---|---|
否 | `org.postgresql.ds. PGSimpleDataSource` |
是 | `org.postgresql.ds. PGPoolingDataSource` |
两种实现使用相同的配置方案。JDBC 要求通过 JavaBean 属性配置 DataSource
,如 表 11.3,“DataSource
配置属性” 中所示,因此每个属性都有 get 和 set 方法。
表 11.3. DataSource
配置属性
属性 | 类型 | 描述 |
---|---|---|
serverName | 字符串 | PostgreSQL® 数据库服务器主机名 |
databaseName | 字符串 | PostgreSQL® 数据库名称 |
portNumber | 整数 | PostgreSQL® 数据库服务器正在监听的 TCP 端口(或 0 以使用默认端口) |
user | 字符串 | 用于建立数据库连接的用户 |
password | 字符串 | 用于建立数据库连接的密码 |
ssl | 布尔值 | 如果为真,则使用 SSL 加密连接(默认值为假) |
sslfactory | 字符串 | 自定义 javax.net.ssl. SSLSocketFactory 类名(参见“自定义 SSLSocketFactory”部分) |
连接池实现需要一些额外的配置属性,如 表 11.4,“附加连接池 DataSource
配置属性” 中所示。
表 11.4. 附加池 DataSource
配置属性
属性 | 类型 | 描述 |
---|---|---|
dataSourceName | 字符串 | 每个池化 DataSource 必须具有唯一的名称。 |
initialConnections | 整数 | 池初始化时要创建的数据库连接数。 |
maxConnections | 整数 | 允许的最大打开数据库连接数。当请求更多连接时,调用者将挂起,直到连接返回到池中。 |
示例 11.1, “DataSource
代码示例” 显示了使用池化 DataSource
的典型应用程序代码示例。
示例 11.1. 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
}
数据源和 JNDI
所有 ConnectionPoolDataSource
和 DataSource
实现都可以存储在 JNDI 中。对于非池化实现,每次从 JNDI 中检索对象时都会创建一个新实例,其设置与存储的实例相同。对于池化实现,只要实例可用(例如,不是不同的 JVM 从 JNDI 中检索池),就会检索相同的实例,否则将创建一个具有相同设置的新实例。
在应用服务器环境中,通常应用服务器的 DataSource
实例将存储在 JNDI 中,而不是 PostgreSQL® ConnectionPoolDataSource
实现。
在应用环境中,应用可以将 DataSource
存储在 JNDI 中,这样它就不必对所有可能需要使用它的应用组件提供对 DataSource
的引用。一个示例如 示例 11.2,“DataSource
JNDI 代码示例” 所示。
示例 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) {}
}
}
Tomcat 设置
注意
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;
}
}