连接池和数据源
JDBC 2 在称为 JDBC 2.0 可选包的附加 API 中引入了标准连接池功能 (也称为 JDBC 2.0 标准扩展)。此后,这些功能已包含在核心 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
配置属性
Property | Type | Description |
---|---|---|
serverName | STRING | PostgreSQL database server host name |
databaseName | STRING | PostgreSQL database name |
portNumber | INT | TCP port which the PostgreSQL database server is listening on (or 0 to use the default port) |
user | STRING | User used to make database connections |
password | STRING | Password used to make database connections |
ssl | BOOLEAN | If true , use SSL encrypted connections (default false ) |
sslfactory | STRING | 自定义的javax.net.ssl.SSLSocketFactory 类名(请参阅名为“自定义 SSLSocketFactory”的部分) |
defaultAutoCommit | BOOLEAN | Whether connections should have autocommit enabled or disabled when they are supplied to the caller. The default is false , to disable autocommit. |
许多应用服务器使用属性样式的语法来配置这些属性,因此作为文本块输入这些属性并不罕见。如果应用服务器提供单个区域来输入所有属性,那么它们可能会像这样列出:
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
配置属性
Property | Type | Description |
---|---|---|
serverName | STRING | PostgreSQL database server host name |
databaseName | STRING | PostgreSQL database name |
portNumber | INT | TCP port which the PostgreSQL database server is listening on (or 0 to use the default port) |
user | STRING | User used to make database connections |
password | STRING | Password used to make database connections |
ssl | BOOLEAN | If true, use SSL encrypted connections (default false) |
sslfactory | STRING | 自定义的javax.net.ssl.SSLSocketFactory 类名(请参阅名为“自定义 SSLSocketFactory”的部分) |
池化实现需要一些其他配置属性,如表 11.4 “其他池化DataSource
配置属性” 所示。
表 11.4.其他池化DataSource
配置属性
Property | Type | Description |
---|---|---|
dataSourceName | STRING | Every pooling DataSource must have a unique name. |
initialConnections | INT | The number of database connections to be created when the pool is initialized. |
maxConnections | INT | The maximum number of open database connections to allow. When more connections are requested, the caller will hang until a connection is returned to the pool. |
例 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);
Note
setServerName has been deprecated in favour of setServerNames. This was done to support multiple hosts.
Then code to use a connection from the pool might look like this.
Note
it is critical that the connections are eventually closed. Otherwise, the pool will “leak” connections and will eventually lock all the clients out.
try (Connection conn = source.getConnection()) {
// use connection
} catch (SQLException e) {
// log error
}
All the ConnectionPoolDataSource
and DataSource
implementations can be stored in JNDI. In the case of the non-pooling
implementations, a new instance will be created every time the object is retrieved from JNDI, with the same settings as
the instance that was stored. For the pooling implementations, the same instance will be retrieved as long as it is available
(e.g., not a different JVM retrieving the pool from JNDI), or a new instance with the same settings created otherwise.
In the application server environment, typically the application server’s DataSource
instance will be stored in JNDI,
instead of the PostgreSQL ConnectionPoolDataSource
implementation.
在应用程序环境中,应用程序可以将DataSource
存储在 JNDI 中,这样它就不必引用可能需要使用到的DataSource
,这些DataSource
可供所有应用程序组件使用。例 11.2 “DataSource
JNDI 代码示例” 中显示了这方面的一个示例。
例 11.2.DataSource
JNDI 代码示例
Application code to initialize a pooling DataSource
and add it to JNDI might look like this:
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);
Then code to use a connection from the pool might look like this:
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) {}
}
}
NOTE
The postgresql.jar file must be placed in $CATALINA_HOME/common/lib in both Tomcat 4 and 5.
The absolute easiest way to set this up in either tomcat instance is to use the admin web application that comes with Tomcat, simply add the datasource to the context you want to use it in.
Setup for Tomcat 4 place the following inside the < Context> tag inside conf/server.xml
<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>
Setup for Tomcat 5, you can use the above method, except that it goes inside the < DefaultContext> tag inside the < Host> tag. eg. < Host> … < DefaultContext> …
Alternatively there is a conf/Catalina/hostname/context.xml file. For example
http://localhost:8080/servlet-example
has a directory $CATALINA_HOME/conf/Catalina/localhost/servlet-example.xml
file.
Inside this file place the above xml inside the < Context> tag
Then you can use the following code to access the connection.
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;
}
}