介绍
本文档提供的信息旨在帮助开发人员为必须处理连接池的应用程序提供连接池策略。
首先, 本文档提供 jdbc 3.0 规范指定的 jdbc 连接池概述。
接下来, 它提供了一些示例, 说明如何使用 DataDirect 连接池管理器 (它随 DataDirect Connect®用于jdbc 和 DataDirect SequeLink® for jdbc) 一起运送到应用程序中。
最后, 本文档提供了一个示例, 显示性能基准, 以演示通过使用连接池可以实现的性能优势。
连接池(Connection Pooling)
建立 jdbc 连接资源成本高, 尤其是在中间层服务器环境中使用 jdbc API 时,
例如,当DataDirect Connect for JDBC或DataDirect SequeLink for JDBC在支持Java的Web服务器上运行时。
在这种类型的环境中, 使用连接池时,可以显著提高性能。连接池意味着在每次请求连接时都重用连接, 而不是每次都需要创建。
为了便于连接重用,称为连接池的数据库连接的内存缓存(memory cache)由连接池模块(connection pooling module) 维护,作为任何标准JDBC驱动程序产品的顶层。
在后台执行连接池, 不影响应用程序的编码方式。但是, 应用程序必须使用 DataSource 对象 (一个实现(implement)DataSource接口的对象) 来获取连接, 而不是使用 DriverManager 类。
实现DataSource接口的类可能会提供连接池,也可能不提供。
一个DataSource对象注册一个JNDI命名服务。(Java Naming and Directory Interface)
一旦注册了DataSource对象,应用程序就会以标准方式从JNDI命名服务中检索它。
例如:
Context ctx = new InitialContext(); //在Context.xml中设置数据源
DataSource ds = (DataSource) ctx.lookup("jdbc/SequeLink");
如果DataSource对象提供连接池,则查找将返回池中的连接(如果有)。
如果DataSource对象不提供连接池或池中没有可用连接,则查找会创建一个新连接。
应用程序受益于连接重用,无需任何代码更改。 来自池的重用连接的行为与新创建的物理连接相同。
应用程序以通常的方式连接到数据库和数据访问。 当应用程序完成对连接的工作后,应用程序将明确关闭连接。
例如:
Connection con = ds.getConnection("scott", "tiger");
// Do some database activities using the connection...
con.close();
共享连接上的关闭事件 通知 连接池模块将连接放回连接池以供将来重新使用。
连接池技术访问数据库的处理步骤:
0)配置数据源参数:
在配置数据源时,可以将其配置到Tomcat安装目录中的conf文件夹下,也可以将其配置到Web工程目录下的META-INF\context.xml(没有的话可以创建)
书上建议使用后者,说是这样更具有针对性。
代码:
<Context>
<Resource name = "jdbc/mysql"
type = "javax.sql.DataSource"
auth = "Container"
driverClassName = "com.mysql.jdbc.Driver
url = "jdbc:mysql://localhost:3306/数据库名字"
username = "root"
password = "yourPassword"
maxActive = "16"
maxIdle = "8"
maxWait = "6000" />
</Context>
<Resource>元素的属性及其说明
Name | 设置数据源的JNDI名 |
type | 设置数据源的类型 |
auth | 数据源的管理者,有Container和Application两个值可选 Container表示用容器来创建和管理数据源, Application表示用web应用来创建和管理数据源 |
driverClassName | 连接数据库的驱动程序 |
url | 数据库路径 |
username | 用户名 |
password | 密码 |
maxActive | 连接数据库处于活动状态数据库链接的最大数目,0表示不受限制 |
maxIdle | 。。。。。。。空闲状态。。。。。。。。。。。。。。。。。。 |
maxWait | 当连接池中没有处于空闲状态的连接时,请求数据库的最长等待时间(单位毫秒) 如果超出该时间则抛出异常,-1表示无限等待 |
1)获得对数据源的引用:
Context ctx = new InitalContext();
DataSource ds = (DataSource)ctx.lookup("java:comp/env/jdbc/mysql");
2)获取数据库连接对象:
Connection con = ds.getConnection();
3)返回数据库连接到连接池:
con.close();
JDBC 3.0 API框架
JDBC 3.0 API提供了一个带有“钩子”的通用框架来支持连接池,而不是指定特定的连接池实现。
通过这种方式,第三方供应商或用户可以实现最适合他们需求的特定缓存或池化算法。
JDBC 3.0 API将ConnectionEvent类和以下接口,指定为任何连接池实现(implement)的挂钩:
ConnectionPoolDataSource
PooledConnection
ConnectionEventListener
JDBC 3.0 连接池架构
JDBCDriverVendorDataSouce
JDBC驱动程序供应商必须提供一个实现标准ConnectionPoolDataSource接口的类。
此接口提供了第三方供应商可以使用的钩子,以将池作为其JDBC驱动程序的一层。
ConnectionPoolDataSource接口充当创建PooledConnection对象的“工厂”。
JDBCDriverVendorPooledConnection
JDBC驱动程序供应商必须提供一个实现标准PooledConnection接口的类。
这个接口允许第三方供应商在他们的JDBC驱动程序之上实现共享。
PooledConnection对象充当创建连接对象的“工厂”。PooledConnection对象保持与数据库的物理连接;
PooledConnection对象创建的连接对象只是物理连接的句柄。
PoolingVendorDataSource
第三方供应商必须提供一个实现数据源接口的类,该接口是允许与其池模块交互的入口点。
池供应商的类使用JDBC驱动程序的PooledConnectionDataSource对象来创建池管理的PooledConnections。
PoolingVendorConnectionCache
JDBC 3.0 API没有指定数据源对象和连接缓存之间使用的接口。
池供应商决定这些组件如何进行交互。
通常,连接缓存模块包含一个或多个类。
在图1中(上面那个图),用PoolingVendorConnectionCache类作为传递这个概念的方法。
连接缓存模块应该有一个实现标准ConnectionEventListener接口的类。
ConnectionEventListener接口在连接关闭或发生连接错误时从PooledConnection对象接收ConnectionEvent对象。
当由PooledConnection创建的连接关闭时,连接缓存模块将PooledConnection对象返回到缓存。
当应用程序通过调用PoolingVendorDataSource对象上的DataSource.getConnection()进行连接时,
PoolingVendorDataSource对象会在连接缓存中执行查找以确定PooledConnection对象是否可用。如果有一个可用,则使用它。
如果PooledConnection对象不可用,那么JDBC驱动程序供应商的ConnectionPoolDataSource会创建一个新的PooledConnection对象。
无论哪种情况,PooledConnection对象都可用。
然后,PoolingVendorDataSource对象会调用PooledConnection.getConnection()方法来获取一个连接对象,它返回到应用程序中作为一个正常连接使用。
由于JDBC驱动程序供应商实现了PooledConnection接口,JDBC驱动程序将创建连接对象;
但是,此连接对象与非池情况下的物理连接不一样。连接对象是由PooledConnection对象维护的物理连接的句柄。
当应用程序通过调用Connection.close()方法关闭连接时,会生成一个ConnectionEvent并传递给cache模块。
缓存模块将PooledConnection对象返回到要重用的缓存。
应用程序无法访问PooledConnection.close()方法。只有连接池模块作为其清理活动的一部分,发出PooledConnection.close()方法来实际关闭物理连接。
创建一个数据源
本节提供了一些示例,介绍如何为JDBC的DataDirect连接创建池化和非池化数据源对象,并将它们注册到JNDI(Java Naming and Directory Interface)命名服务中。
创建一个DataDirect数据源对象(DataDirect Connect for JDBC)
这个示例展示了如何创建JDBC DataSource对象的DataDirect连接,并将其注册到JNDI命名服务。
由DataDirect连接的JDBC驱动程序提供的DataSource类是数据库依赖的。在下面的例子中,我们使用Oracle,所以DataSource类是com.ddtek. jdbcx.oracledatasource。
如果你希望客户端程序使用:
1)一个非共享数据源,应用程序可以指定这个数据源对象的JNDI名称,如以下代码示例所示(“jdbc/ConnectSparkyOracle”)。
2)应用程序必须指定JNDI名称(“jdbc/SparkyOracle”),并在 “使用DataDirect连接池管理器创建数据源” 一节(下面那个小节就是了)中注册。
//********************************************************************
//
//此代码创建一个DataDirect Connect for JDBC数据源和
//将其注册到JNDI命名服务。 此DataDirect Connect for
// JDBC数据源使用由提供的DataSource实现
// DataDirect Connect for JDBC驱动程序。
//
//
//这个数据源将其JNDI名称注册为<jdbc/ConnectSparkyOracle>。
//使用非池连接的客户端应用程序必须执行查找。
/ /这个名字。
//
//
//********************************************************************
// From DataDirect Connect for JDBC:
import com.ddtek.jdbcx.oracle.OracleDataSource;
import javax.sql.*;
import java.sql.*;
import javax.naming.*;
import javax.naming.directory.*;
import java.util.Hashtable;
public class OracleDataSourceRegisterJNDI
{
public static void main(String argv[])
{
try {
//为命名上下文设置数据源参考数据:
// -----------------------------------------------------
//创建一个实现接口的类实例
// ConnectionPoolDataSource
OracleDataSource ds = new OracleDataSource();
ds.setDescription(
"Oracle on Sparky - Oracle Data Source");
ds.setServerName("sparky");
ds.setPortNumber(1521);
ds.setUser("scott");
ds.setPassword("test");
// Set up environment for creating initial context
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");
env.put(Context.PROVIDER_URL, "file:c:\\JDBCDataSource");
Context ctx = new InitialContext(env);
// Register the data source to JNDI naming service
ctx.bind("jdbc/ConnectSparkyOracle", ds);
} catch (Exception e) {
System.out.println(e);
return;
}
} // Main
} // class OracleDataSourceRegisterJNDI
使用DataDirect连接池管理器创建数据源(DataDirect connect for JDBC)
下面的Java代码示例用于创建JDBC的DataDirect连接的数据源,并将其注册到JNDI命名服务。
PooledConnectionDataSource类由DataDirect com.ddtek.pool提供。
在下面的代码示例中,PooledConnectionDataSource对象引用了一个共用的DataDirect Connect for JDBC数据源对象
因此,该示例通过将DataSourceName属性设置为已注册的池数据源的JNDI名称来执行查找
(在本例中是jdbc/ConnectSparkyOracle,它是在“创建DataDirect数据源对象”小节(就是上面那一个小节)中创建的jdbc DataSource对象的DataDirect连接。)
使用此数据源的客户端应用程序必须使用注册的JNDI名称(本例中为jdbc / SparkyOracle)执行查找。
//********************************************************************
//
//此代码创建一个数据源并将其注册为一个JNDI命名
//服务。 该数据源使用PooledConnectionDataSource
//由DataDirect com.ddtek.pool包提供的实现。
//
//此数据源是指先前注册的池中的数据源。
//
//此数据源将其名称注册为<jdbc / SparkyOracle>
//使用池的客户端应用程序必须执行查找此名称。
//
//********************************************************************
// From the DataDirect connection pooling package:
import com.ddtek.pool.PooledConnectionDataSource;
import javax.sql.*;
import java.sql.*;
import javax.naming.*;
import javax.naming.directory.*;
import java.util.Hashtable;
public class PoolMgrDataSourceRegisterJNDI
{
public static void main(String argv[])
{
try {
//为命名上下文设置数据源参考数据:
// ----------------------------------------------------
//创建一个实现的池管理器的类实例
//接口DataSource
PooledConnectionDataSource ds = new PooledConnectionDataSource();
ds.setDescription("Sparky Oracle - Oracle Data Source");
//请参阅之前注册的池中的数据源进行访问
//一个ConnectionPoolDataSource对象
ds.setDataSourceName("jdbc/ConnectSparkyOracle");
//池管理器将以5个物理连接启动
ds.setInitialPoolSize(5);
//池维护线程将确保有5个
//可用物理连接
ds.setMinPoolSize(5);
//池维护线程将检查没有更多
//比可用的10个物理连接
ds.setMaxPoolSize(10);
//池维护线程将唤醒并检查池
//每20秒钟
ds.setPropertyCycle(20);
//池维护线程将删除物理连接
//超过300秒无效
ds.setMaxIdleTime(300);
//因为我们选择不看输出列表,所以设置跟踪
//连接上的活动
ds.setTracing(false);
//设置创建初始上下文的环境
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");
env.put(Context.PROVIDER_URL, "file:c:\\JDBCDataSource");
Context ctx = new InitialContext(env);
//将数据源注册到JNDI命名服务
//供应用程序使用
ctx.bind("jdbc/SparkyOracle", ds);
} catch (Exception e) {
System.out.println(e);
return;
}
} // Main
} // class PoolMgrDataSourceRegisterJNDI
DataDirect SequeLink
以下Java代码示例为JDBC创建数据源并将其注册到JNDI命名服务。
PooledConnectionDataSource类由DataDirect com.ddtek.pool包提供。
在下面的代码示例中,PooledConnectionDataSource对象引用JDBC数据源对象。
该示例通过将DataSourceName属性设置为已注册池中数据源的JNDI名称
(在本例中为jdbc / SequeLinkSparkyOracle,即在“创建DataDirect数据源对象”部分中创建的JDBC DataSource对象)来执行查找。
使用此数据源的客户端应用程序必须使用注册的JNDI名称(本例中为jdbc / SparkyOracle)执行查找。
//********************************************************************
//
//此代码创建一个数据源并将其注册为一个JNDI命名
//服务。 该数据源使用PooledConnectionDataSource
//由DataDirect com.ddtek.pool包提供的实现。
//
//此数据源是指先前注册的池中的数据源。
//
//此数据源将其名称注册为<jdbc / SparkyOracle>
//使用池的客户端应用程序必须执行查找此名称。
//
//********************************************************************
// From the DataDirect connection pooling package:
import com.ddtek.pool.PooledConnectionDataSource;
import javax.sql.*;
import java.sql.*;
import javax.naming.*;
import javax.naming.directory.*;
import java.util.Hashtable;
public class PoolMgrDataSourceRegisterJNDI
{
public static void main(String argv[])
{
try {
// Set up data source reference data for naming context:
// ----------------------------------------------------
// Create a pooling manager's class instance that implements
// the interface DataSource
PooledConnectionDataSource ds = new PooledConnectionDataSource();
ds.setDescription("Sparky Oracle - Oracle Data Source");
// Refer to a previously registered pooled data source to access
// a ConnectionPoolDataSource object
ds.setDataSourceName("jdbc/SequeLinkSparkyOracle");
// The pool manager will be initiated with 5 physical connections
ds.setInitialPoolSize(5);
// The pool maintenance thread will make sure that there are
// at least 5 physical connections available
ds.setMinPoolSize(5);
// The pool maintenance thread will check that there are no more
// than 10 physical connections available
ds.setMaxPoolSize(10);
// The pool maintenance thread will wake up and check the pool
// every 20 seconds
ds.setPropertyCycle(20);
// The pool maintenance thread will remove physical connections
// that are inactive for more than 300 seconds
ds.setMaxIdleTime(300);
// Set tracing off since we choose not to see output listing
// of activities on a connection
ds.setTracing(false);
//设置创建初始上下文的环境
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");
env.put(Context.PROVIDER_URL, "file:c:\\JDBCDataSource");
Context ctx = new InitialContext(env);
//将数据源注册到JNDI命名服务
//供应用程序使用
ctx.bind("jdbc/SparkyOracle", ds);
} catch (Exception e) {
System.out.println(e);
return;
}
} // Main
} // class PoolMgrDataSourceRegisterJNDI
连接到数据源
连接池是否使用不会影响应用程序代码。
它不需要对应用程序进行任何代码更改,因为应用程序会执行对以前注册的数据源的JNDI名称的查找。
如果数据源在JNDI注册期间指定了连接池实现(如“使用DataDirect连接池管理器创建数据源”部分所述),则客户端应用程序将通过连接池实现更快速的连接。
DataDirect Connect for JDBC
以下示例显示了可用于查找和使用JNDI注册的数据源进行连接的代码。 您为您创建的数据源指定JNDI查找名称(如“使用DataDirect连接池管理器创建数据源”中所述。)
//********************************************************************
//
//测试程序查找并使用JNDI注册的数据源。
//
//要运行该程序,请为该程序指定JNDI查找名称
//命令行参数,例如:
// java TestDataSourceApp JNDI_lookup_name
//
//********************************************************************
import javax.sql.*;
import java.sql.*;
import javax.naming.*;
import java.util.Hashtable;
public class TestDataSourceApp
{
public static void main(String argv[])
{
String strJNDILookupName = """;
//获取数据源的JNDI查找名称
int nArgv = argv.length;
if (nArgv != 1) {
//用户没有为数据源指定JNDI查找名称,
System.out.println(
"Please specify a JNDI name for your data source");
System.exit(0);
} else {
strJNDILookupName = argv[0];
}
DataSource ds = null;
Connection con = null;
Context ctx = null;
Hashtable env = null;
long nStartTime, nStopTime, nElapsedTime;
//设置创建InitialContext对象的环境
env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");
env.put(Context.PROVIDER_URL, "file:c:\\JDBCDataSource");
try {
//检索绑定到的DataSource对象
//逻辑查找JNDI名称
ctx = new InitialContext(env);
ds = (DataSource) ctx.lookup(strJNDILookupName);
} catch (NamingException eName) {
System.out.println("Error looking up " +
strJNDILookupName + ": " +eName);
System.exit(0);
}
int numOfTest = 4;
int [] nCount = {100, 100, 1000, 3000};
for (int i = 0; i < numOfTest; i ++) {
//记录开始时间
nStartTime = System.currentTimeMillis();
for (int j = 1; j <= nCount[i]; j++) {
// Get Database Connection
try {
con = ds.getConnection("scott", "tiger");
//对连接做些什么
// ...
//关闭数据库连接
if (con != null) con.close();
} catch (SQLException eCon) {
System.out.println("Error getting a connection: " + eCon);
System.exit(0);
} //尝试getConnection
} //用于j循环
//记录结束时间
nStopTime = System.currentTimeMillis();
//计算经过的时间
nElapsedTime = nStopTime - nStartTime;
System.out.println("Test number " + i + ": looping " +
nCount[i] + " times");
System.out.println("Elapsed Time: " + nElapsedTime + "\n");
} // for i loop
// All done
System.exit(0);
} // Main
} // TestDataSourceApp
注意:除了用于池化的ConnectionPoolDataSource之外,DataDirect Connect for JDBC DataSource对象类还实现用于非池化的DataSource接口。
要使用非池化数据源,请使用在“创建DataDirect数据源对象”一节中的示例代码中注册的JNDI名称,然后运行TestDataSourceApp。
例如:java TestDataSourceApp jdbc/ConnectSparkyOracle
DataDirect SequeLink for JDBC
以下示例显示了可用于查找和使用JNDI注册的数据源进行连接的代码。 您可以为您创建的数据源指定JNDI查找名称(如“使用DataDirect连接池管理器创建数据源”中所述)。
//********************************************************************
//
// Test program to look up and use a JNDI-registered data source.
//
// To run the program, specify the JNDI lookup name for the
// command-line argument, for example:
//
// java TestDataSourceApp JNDI_lookup_name
//
//********************************************************************
import javax.sql.*;
import java.sql.*;
import javax.naming.*;
import java.util.Hashtable;
public class TestDataSourceApp
{
public static void main(String argv[])
{
String str JNDILookupName = "jdbc/SparkyOracle";
// Hard-code the JNDI entry, the application does not need to change
DataSource ds = null;
Connection con = null;
Context ctx = null;
Hashtable env = null;
long nStartTime, nStopTime, nElapsedTime;
// Set up environment for creating InitialContext object
env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");
env.put(Context.PROVIDER_URL, "file:c:\\JDBCDataSource");
try {
// Retrieve the DataSource object that bound to the
// logical lookup JNDI name
ctx = new InitialContext(env);
ds = (DataSource) ctx.lookup(strJNDILookupName);
} catch (NamingException eName) {
System.out.println("Error looking up " +
strJNDILookupName + ": " +eName);
System.exit(0);
}
int numOfTest = 4;
int [] nCount = {100, 100, 1000, 3000};
for (int i = 0; i < numOfTest; i ++) {
// Log the start time
nStartTime = System.currentTimeMillis();
for (int j = 1; j <= nCount[i]; j++) {
// Get Database Connection
try {
con = ds.getConnection("scott", "tiger");
// Do something with the connection
// ...
// Close Database Connection
if (con != null) con.close();
} catch (SQLException eCon) {
System.out.println("Error getting a connection: " + eCon);
System.exit(0);
} // try getConnection
} // for j loop
// Log the end time
nStopTime = System.currentTimeMillis();
// Compute elapsed time
nElapsedTime = nStopTime - nStartTime;
System.out.println("Test number " + i + ": looping " +
nCount[i] + " times");
System.out.println("Elapsed Time: " + nElapsedTime + "\n");
} // for i loop
// All done
System.exit(0);
} // Main
} // TestDataSourceApp
注意:除了用于池的ConnectionPoolDataSource之外,DataDirect SequeLink for JDBC DataSource对象类还实现用于非池的DataSource接口。
要使用非共用连接,请修改“创建DataDirect数据源对象”中的示例,以便使用JNDI条目注册SequeLink数据源jdbc/SparkyOracle.
然后您可以运行TestDataSourceApp而不做任何修改:
java TestDataSourceApp
Closing the Connection Pool
为确保在应用程序停止运行时连接池被正确关闭,应用程序必须在其停止时通知DataDirect连接池管理器。
如果应用程序在JRE 1.3或更高版本上运行,则应用程序停止运行时会自动发出通知。
如果应用程序在JRE 1.2上运行,则应用程序必须在其停止使用PooledConnectionDataSource.close方法时显式通知池管理器,如下面的代码所示:
if (ds instanceof com.ddtek.pool.PooledConnectionDataSource){
com.ddtek.pool.PooledConnectionDataSource pcds = (com.ddtek.pool.PooledConnectionDataSource) ds;
pcds.close();
}