中科院云平台架构师,专注于数字化、智能化,技术方向:云、Linux内核、AI、MES/ERP/CRM/OA、物联网、传感器、大数据、ML、微服务。
分类: Java
2013-04-22 19:03:54
数据库连接池的基本原理
数据库连接池的基本原理
传统的数据库连接方式(指通过DriverManager和基本实现DataSource进行连接)中,一个数据库连接对象均对应一个物理数据库连接,数据库连接的建立以及关闭对系统而言是耗费系统资源的操作,在多层结构的应用程序环境中这种耗费资源的动作对系统的性能影响尤为明显。
在多层结构的应用程序中通过连接池(connection pooling)技术可以使系统的性能明显得到提到,连接池意味着当应用程序需要调用一个数据库连接的时,数据库相关的接口通过返回一个通过重用数据库连接来代替重新创建一个数据库连接。通过这种方式,应用程序可以减少对数据库连接操作,尤其在多层环境中多个客户端可以通过共享少量的物理数据库连接来满足系统需求。通过连接池技术Java应用程序不仅可以提高系统性能同时也为系统提高了可测量性。
数据库连接池是运行在后台的而且应用程序的编码没有任何的影响。此中状况存在的前提是应用程序必须通过DataSource对象(一个实现javax.sql.DataSource接口的实例)的方式代替原有通过DriverManager类来获得数据库连接的方式。一个实现javax.sql.DataSource接口的类可以支持也可以不支持数据库连接池,但是两者获得数据库连接的代码基本是相同的。
代码如下:
一个DataSource对象通常注册在JNDI命名服务上,应用程序可以通过标准的方式获得到注册在JNDI服务上的DataSource对象。
Context ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup("jdbc/openbase");
如果当前DataSource不支持数据库连接池,应用程序将获得一个和物理数据库连接对应的Connection对象。而如果当前的DataSource对象支持数据库连接池,应用程序自动获得重用的数据库连接而不用创建新的数据库连接。重用的数据库连接和新建立连接的数据库连接使用上没有任何不同。应用程序可以通过重用的连接正常的访问数据库,进行访问数据的操作,完成操作后应显式的调用close()关闭数据库连接。
Connection con = ds.getConnection("User", "Pwd");
相关数据库的操作;
con.close();
当关闭数据连接后,当前使用的数据库连接将不会被物理关闭,而是放回到数据库连接池中进行重用。
JDBC3.0规范中数据库连接池框架
JDBC3.0规范中通过提供了一个支持数据库连接池的框架,这个框架仅仅规定了如何支持连接池的实现,而连接池的具体实现JDBC 3.0规范并没有做相关的规定。通过这个框架可以让不同角色的开发人员共同实现数据库连接池。
通过JDBC3.0规范可以知道具体数据库连接池的实现可以分为JDBC Driver级和Application Server级。在JDBC Driver级的实现中任何相关的工作均由特定数据库厂商的JDBC Drvier的开发人员来具体实现,即JDBC Driver既需要提供对数据库连接池的支持同时也必须对数据库连接池进行具体实现。而在Application Server级中数据库连接池的实现中特定数据库厂商的JDBC Driver开发人员和Application Server开发人员来共同实现数据库连接池的实现(但是现在大多数Application Server厂商实现的连接池的机制和规范中提到有差异),其中特定数据库厂商的JDBC Driver提供数据库连接池的支持而特定的Application Server厂商提供数据库连接池的具体实现。
JDBC3.0规范规定了如下的类和接口来支持数据库连接池的实现。
javax.sql.ConnectionEvent
javax.sql.ConnectionPoolDataSource
javax.sql.PooledConnection
javax.sql.ConnectionEventListener
其中除javax.sql.ConnectionEvent是类,其它的均为接口。
通过此图可以大概的了解相关接口在一个典型的三层环境中应用程序的位置。
数据库连接池实现层次中,由特定数据库厂商的JDBC Driver开发人员提供连接池支持,而特定Application Server提供连接池实现的情况比较复杂,其它的实现层次均可视为其简化情况的一种。下面将针对这种情况进行说明。
在这个框架主要有两个用户角色存在,它们分别是:
特定数据库厂商的JDBC Driver开发人员,之后将简称为Driver Vendor
特定Application Server中连接池开发人员,之后将简称为Pooling Vendor
下面对几个关键模块进行详细的说明:
Driver Vendor DataSource:
Driver Vendor必须提供一个ConnectionPoolDataSource 接口的具体实现,通过这个接口Pooling Vendor可以得到一个PooledConnection对象,从而使第三方实现的连接池可以使用特定数据库厂商得到JDBC Driver产生的数据库连接。在这里ConnectionPoolDataSource接口扮演的角色可以视为产生PooledConnection 对象的工厂。
Driver Vendor PooledConnection:
Driver Vendor必须提供标准PooledConnection 接口实现的类,这个接口允许Pooling Vendor在JDBC Driver提供连接池支持的基础上实现连接池。一个具体PooledConnection对象代表了一个物理的数据库连接;由PooledConnection对象创建Connection对象仅仅只是一个指向PooledConnetion对象的句柄。在JDBC 3.0连接池实现框架中PooledConnection对象扮演的角色可以视为产生Connection对象的工厂。
Pooling Vendor DataSource:
Pooling Vendor必须实现DataSource接口,这个接口是和连接池实现模块进行交互的入口点。ConnectionPoolDataSource根据需要创建PooledConnection对象。
Pooling Vendor Connection Cache:
此模块是Pooling Vendor对连接池的具体实现。JDBC 3.0 规范没有规定在DataSource对象和数据库连接池实现之间的需要实现的接口,所以它们之间的交互由Pooling Vendor自己定义。一般而言,一个数据库连接池的具体实现包含了一个或若干个具体的类,但是在连接池实现模块中必须包含一个类实现标准ConnectionEventListener接口。当一个PooledConnectiond对象被关闭或者出现异常的时候,PooledConnection对象将会向ConnectionEventListener接口发送ConnectionEvent对象,连接池实现模块将会根据返回的ConnectionEvent对象对PooledConnection进行关闭或者重用操作。
ConnectionEvent:
实现连接池时,当应用程序调用Connection.close()试图去关闭数据库连接时,这时需要有一个通告给连接池实现模块,通告对当前的数据库物理连接(PooledConnection 对象)进行重用。为了使连接池实现模块能得到这种"通告",连接池实现模块必须实现ConnectionEventListener接口,而且同时需要注册成为PooledConnection对象的监听者。连接池实现模块通过PooledConnection.addConnectionEventListener()方法注册自己成为一个监听者。
在典型三层环境中具体调用流程:
当应用程序通过调用DataSource.getConnection()得到一个数据库连接。
Pooling Vendor实现的DataSource对象在连接池中进行查找看当前是否有有效的PooledConnection对象,如果连接池中有可用的PooledConnection,则进行检查,如果当前的PooledConnection可用则使用。
如果如果连接池中没有可用的PooledConnection对象,或者当前的PooledConnection对象不正确,那么Pooling Vendor调用ConnectionPoolDataSource.getPooledConnection类创建一个新的PooledConnection对象,这时由Driver Vendor实现的ConnectionPoolDataSource将会创建一个满足要求新的PooledConnection对象,并将其返回给连接池实现模块进行管理。
然后,Pooling Vendor会调用PooledConnection.getConnection()获得一个逻辑的Connection对象,这个逻辑的Connection对象将会象正常的Connection对象返回给应用程序。这个逻辑Connection对象实际上是连接池中PooledConnection对象的一个句柄,当连接池有效时,应用程序调用DataSource.getConnection()就会得到这个句柄。简而言之,应用程序此时使用的Connection对象仅仅是其创建者PooledConnection对象的句柄而已。
连接池实现模块调用PooledConnection.addConnectionEventListener()将自己注册成为一个PooledConnection对象的监听者,当数据库连接需要重用或者关闭的时候连接池实现模块可以得到通告。
当应用程序通过调用Connection.close()来关闭数据库连接,这时一个ConnectionEvent对象被创建并被返回到连接池实现模块,连接池实现模块接受到此通告后,将PooledConnection对象返回到池中进行重用。这些过程中其它角色都不能访问PooledConnection.close()方法,能访问这个方法的只有Pooling Vendor,它们使用这个方法对连接池中的对象进行操作,通过PooledConnection.close()方法可以关闭物理数据库连接。
数据库连接池-连接的关闭内幕(1)
我们经常会遇到这样那样的连接未关闭的问题,连接没有及时关闭导致的直接后果就是内存泄漏直至down机。我们也都知道解决的方式,但是在解决了问题之后经常会思考为什么会这样呢?连接close()掉,然后在创建不是很浪费cpu等系统资源嘛?有没有更好的方法解决呢?大家也经常听到连接池、线程池之类的线程、池的概念,那么究竟这些概念与我们的连接有什么关系呢?
下面我就想就上面的问题谈谈我的一点浅见,请大家批评指正。
大家都知道java语言是一种语言级的多线程机制的面向对象语言。比如说,java的基类object,它就有一些诸如wati(),notify(),notifyall()等线程控制的方法。如果大家学习过操作系统的化,我想应该知道线程存在同步和异步的问题,解决的方法很多,其中就有“信号量机制”实现线程的同步,java的这种同步机制与操作系统大同小异。同步机制是一个比较复杂的问题,如果感兴趣可以找一本操作系统的书看看。
下面简单介绍一些进程和线程的概念:
1. 进程:
进程是资源分配和独立运行的基本单位。进程的定义很多,下面列举一些
进程是程序的一次执行;
进程是可以和别的计算机并发执行的计算;
进程可定义为一个数据结构及能在其上进行操作的一个程序
进程是一个程序及其数据在处理机上顺序执行时发生的活动;
进程时程序在一个数据集合上的运行过程,时系统进行资源分配和调度的一个独立单位。
2. 线程
由于进程是一个资源拥有者,因而在进程的创建、撤消和切换过程中,系统必须为之付出较大的时空开销。也正因为如此,在系统中所设置的进程数目不宜过多,进程切换的频率也不宜过高,但这也就限制了并发程度的进一步提高。因此便引出了线程的概念
把线程作为调度和分派的基本单位,而把进程作为资源拥有的基本单位,使传统进程的两个属性分开,线程便能轻装运行,从而显著提高系统的并发程度。
在同一个进程内可以有多个线程;
同一个进程内的线程切换不会引起进程切换;
一个进程的线程切换到另一个进程的线程时会引起进程切换
3. JSP/SERVLET
而我们常用的jsp/ervlet这种j2ee的体系结构正是建立在java的多线程机制之上的。JSP/SERVLET容器会自动使用线程池等技术来支持系统的运行。因此,JSP/SERVLET的实质是一种线程技术,JSP会在运行时被编译成servlet来运行,
当客户端向服务器发出一个请求时,servlet容器会分配一个线程专门处理这个请求,线程内容就是JSP/servlet应用程序。
这部分内容与本主体无关,只是顺便说说。
常用的数据库连接池代码,转载的
总结了一下数据库连接池的用法。
数据库连接是最常用的提高性能的方法,总结了一下:
1 定义数据库访问接口
public interface IDatabaseConnection
{
public abstract Connection createConnection();
public abstract void closeConnection(Connection conn);
}
2 实现该接口
2.1 直接连接,连接速度较慢,用户量较大时资源占用很大
public Connection createConnection()
{
try
{
SystemParameter SP = new SystemParameter();
Class.forName(DBDRIVER).newInstance();
Connection con =
DriverManager.getConnection(
DBDRIVER_URL,
DBUSER,
DBPASSWORD);
return con;
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
return null;
}
catch (SQLException e)
{
e.printStackTrace();
return null;
}
catch (InstantiationException e)
{
e.printStackTrace();
return null;
}
catch (IllegalAccessException e)
{
e.printStackTrace();
return null;
}
}
public void closeConnection(Connection conn)
{
if(conn!=null) try
{
conn.close();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
2.2 DBConnectionManager、DBConnectionPool方式
这个连接池是网上比较多的连接池代码
public class DBConnectionManager
{
static private DBConnectionManager instance; /*唯一实例*/
static private int clients;
/*该计数代表引用DBConnectionManager唯一实例的客户程序总数,
它将被用于控制连接池的关闭操作 */
//Added by leo on 2001-5-23
static private String DEFAULTCONNECTIONPOOL = "homepage";
private Vector drivers = new Vector();
private PrintWriter logPrint;
private Hashtable pools = new Hashtable();
private SystemParameter sp = new SystemParameter();
/**
返回唯一实例。如果是第一次调用此方法,则创建实例
*/
static synchronized public DBConnectionManager getInstance()
{
if (instance == null)
{
instance = new DBConnectionManager();
}
clients++;
return instance;
}
/**
建构函数私有以防止其它对象创建本类实例
*/
public DBConnectionManager()
{
init();
}
/** Added by leo on 2001-5-23
使用默认的连接池名,创建并返回新连接
*
* @return Connection 可用连接或null
*/
public Connection getConnection()
{
return getConnection(DEFAULTCONNECTIONPOOL);
}
/**
获得一个可用的(空闲的)连接.如果没有可用连接,且已有连接数小于最大连接数
限制,则创建并返回新连接
*
* @param name 在属性文件中定义的连接池名字
* @return Connection 可用连接或null
*/
public Connection getConnection(String name)
{
DBConnectionPool pool = (DBConnectionPool) pools.get(name);
if (pool != null)
{
return pool.getConnection();
}
return null;
}
/**
获得一个可用连接.若没有可用连接,且已有连接数小于最大连接数限制,
则创建并返回新连接.否则,在指定的时间内等待其它线程释放连接.
*
* @param name 连接池名字
* @param time 以毫秒计的等待时间
* @return Connection 可用连接或null
*/
public Connection getConnection(String name, long time)
{
DBConnectionPool pool = (DBConnectionPool) pools.get(name);
if (pool != null)
{
return pool.getConnection(time);
}
return null;
}
/** Added by leo on 2001-5-23
将连接对象返回给默认的连接池
*
* @param con 连接对象
*/
public void freeConnection(Connection con)
{
String name = DEFAULTCONNECTIONPOOL;
freeConnection(name, con);
}
/**
将连接对象返回给由名字指定的连接池
*
* @param name 在属性文件中定义的连接池名字
* @param con 连接对象
*/
public void freeConnection(String name, Connection con)
{
DBConnectionPool pool = (DBConnectionPool) pools.get(name);
if (pool != null)
{
pool.freeConnection(con);
}
}
/**
关闭所有连接,撤销驱动程序的注册
*/
public synchronized void release()
{ // 等待直到最后一个客户程序调用
if (--clients != 0)
{
return;
}
Enumeration allPools = pools.elements();
while (allPools.hasMoreElements())
{
DBConnectionPool pool = (DBConnectionPool) allPools.nextElement();
pool.release();
}
Enumeration allDrivers = drivers.elements();
while (allDrivers.hasMoreElements())
{
Driver driver = (Driver) allDrivers.nextElement();
try
{
DriverManager.deregisterDriver(driver);
log("撤消JDBC驱动程序" + driver.getClass().getName() + "的注册");
}
catch (SQLException e)
{
log(e, "无法撤消下列JDBC驱动程序的注册" + driver.getClass().getName());
}
}
}
/**
根据指定属性创建连接池实例.
*
* @param props 连接池属性
*/
private void createPools()
{
String poolName = sp.DBPOOLNAME;
String url = sp.DBDRIVER_URL;
String user = sp.DBUSER;
String password = sp.DBPASSWORD;
int max = sp.DBMAXCONN;
DBConnectionPool pool =
new DBConnectionPool(poolName, url, user, password, max);
pools.put(poolName, pool);
log("成功创建连接池" + poolName);
}
/**
读取属性完成初始化
*/
private void init()
{
logPrint=new PrintWriter(System.out);
loadDrivers();
createPools();
}
/**
装载和注册所有JDBC驱动程序
*
* @param props 属性
*/
private void loadDrivers()
{
String driverClassName = sp.DBDRIVER;
try
{
Driver driver =
(Driver) Class.forName(driverClassName).newInstance();
DriverManager.registerDriver(driver);
drivers.addElement(driver);
log("成功JDBC注册驱动程序" + driverClassName);
}
catch (Exception e)
{
log("无法注册JDBC驱动程序;" + driverClassName + ",错误:" + e);
}
}
/**
将文本信息写入日志文件
缺省为packaged
*/
void log(String msg)
{
logPrint.println(new java.util.Date() + ": " + msg);
}
/**
将文本信息与异常写入日志文件
*/
void log(Throwable e, String msg)
{
logPrint.println(new java.util.Date() + ": " + msg);
e.printStackTrace(logPrint);
}
}
------------------------------------------------------
class DBConnectionPool
{
private int checkedOut;
private PrintWriter logPrint;
private Vector freeConnections = new Vector();
private int maxConn;
private String name;
private String password;
private String URL;
private String user;
/**
创建新的连接池
name 连接池名字
URL 数据库的JDBC URL
user 数据库帐号,或null
password 密码,或null
maxConn 此连接池允许建立的最大连接数
*/
public DBConnectionPool(String name, String URL, String user, String password, int maxConn)
{
this.name = name;
this.URL = URL;
this.user = user;
this.password = password;
this.maxConn = maxConn;
logPrint = new PrintWriter(System.out);
}
/**
将不再使用的连接返回给连接池
con 客户程序释放的连接
*/
public synchronized void freeConnection(Connection con)
{
freeConnections.addElement(con);
checkedOut--;
notifyAll();
}
/**
从连接池获得一个可用连接,如没有空闲的连接且当前连接数小于最大连接数,则创建新连接,
如原来登记为可用的连接不再有效,则从向量删除之,然后递归调用自己以尝试新的可用连接
*/
public synchronized Connection getConnection()
{
Connection con = null;
if(freeConnections.size() > 0)
{// 获取向量中第一个可用连接
con = (Connection)freeConnections.firstElement();
freeConnections.removeElementAt(0);
try
{
if(con.isClosed())
{
log("从连接池" + name + "删除一个无效连接");
con = getConnection();
}
}
catch(SQLException e)
{
log("从连接池" + name + "删除一个无效连接");
con = getConnection();
}
}
else if(maxConn == 0 || checkedOut < maxConn)
{
con = newConnection();
}
if(con != null)
{
checkedOut++;
}
return con;
}
/**
从连接池获取可用连接,可以指定客户程序能够等待的最长时间
timeout 以毫秒计的等待时间限制
*/
public synchronized Connection getConnection(long timeout)
{
long startTime = new Date().getTime();
Connection con;
while((con = getConnection()) == null)
{
try
{
wait(timeout);
}
catch(InterruptedException e){}
if((new Date().getTime() - startTime) >= timeout)
{
return null;
}
}
return con;
}
/**
关闭所有连接
*/
public synchronized void release()
{
Enumeration allConnections = freeConnections.elements();
while(allConnections.hasMoreElements())
{
Connection con = (Connection)allConnections.nextElement();
try
{
con.close();
log("关闭连接池" + name + "中的一个连接");
}
catch(SQLException e)
{
log(e, "无法关闭连接池" + name + "中的连接");
}
}
freeConnections.removeAllElements();
}
/**
创建新的连接
*/
private Connection newConnection()
{
Connection con = null;
try
{
if(user == null)
{
con = DriverManager.getConnection(URL);
}
else
{
con = DriverManager.getConnection(URL, user, password);
}
log("连接池" + name + "创建一个新的连接");
}
catch(SQLException e)
{
log(e, "无法创建下列URL的连接" + URL);
return null;
}
return con;
}
/**
将文本信息写入日志文件
缺省为packaged
*/
void log(String msg)
{
logPrint.println(new Date() + ": " + msg);
}
/**
将文本信息与异常写入日志文件
*/
void log(Throwable e, String msg)
{
logPrint.println(new Date() + ": " + msg);
e.printStackTrace(logPrint);
}
}
---------------------------------
DBConnectionManager connMgr = DBConnectionManager.getInstance();
public Connection createConnection()
{
Connection dBConn = connMgr.getConnection(new SystemParameter().DBPOOLNAME);
return dBConn;
}
public void closeConnection(Connection conn)
{
connMgr.freeConnection(new SystemParameter().DBPOOLNAME, conn);
connMgr.release();
}
经过测试,使用这种方法,会造成数据库大量死锁,证明该连接池实现方法存在隐患
也可能是因为它不属于webserver容器自身的,不具有可管理性。
2.3 tomcat连接池
public Connection createConnection()
{
try
{
Context initCtx = new InitialContext();
DataSource ds;
ds = (DataSource) initCtx.lookup("java:comp/env/"+new SystemParameter().JDBC_SOURCE);
return ds.getConnection();
}
catch (NamingException e)
{
e.printStackTrace();
return null;
}
catch (SQLException e)
{
e.printStackTrace();
return null;
}
}
public void closeConnection(Connection conn)
{
if(conn!=null)try
{
conn.close();
conn=null;
}
catch (SQLException e)
{
e.printStackTrace();
}
}
2.4 weblogic连接池
public Connection createConnection()
{
Context ctx = null;
Hashtable ht = new Hashtable();
ht.put(Context.INITIAL_CONTEXT_FACTORY, CONTEXT_FACTORY);
ht.put(Context.PROVIDER_URL, URL);
// ht.put(Context.SECURITY_PRINCIPAL, "admin");
// ht.put(Context.SECURITY_CREDENTIALS, "11111111");
Connection conn = null;
try
{
ctx = new InitialContext(ht);
DataSource ds = (DataSource) ctx.lookup(POOL_NAME);
return ds.getConnection();
}
catch (NamingException e)
{
e.printStackTrace();
}
catch (SQLException e)
{
e.printStackTrace();
}
return null;
}
public void closeConnection(Connection conn)
{
if (conn != null)
try
{
conn.close();
conn = null;
}
catch (SQLException e)
{
e.printStackTrace();
}
}
3,4的方法比较好