Connection Pool
数据库连接池的基本原理是在内部对象池中维护一定数量的数据库连接,并对外暴露数据库连接获取和返回方法。如:
外部使用者可通过getConnection 方法获取连接,使用完毕后再通过releaseConnection 方法将连接返回,注意此时连接并没有关闭,而是由连接池管理器回收,并为下一次使用做好准备。
数据库连接池技术带来的优势:
1. 资源重用
由于数据库连接得到重用,避免了频繁创建、释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增进了系统运行环境的平稳性(减少内存碎片以及数据库临时进程/线程的数量)。
2. 更快的系统响应速度
数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而缩减了系统整体响应时间。
3. 新的资源分配手段
对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接的配置,实现数据库连接池技术,几年钱也许还是个新鲜话题,对于目前的业务系统而言,如果设计中还没有考虑到连接池的应用,那么…….快在设计文档中加上这部分的内容吧。某一应用最大可用数据库连接数的限制,避免某一应用独占所有数据库资源。
4. 统一的连接管理,避免数据库连接泄漏
在较为完备的数据库连接池实现中,可根据预先的连接占用超时设定,强制收回被占用连接。从而避免了常规数据库连接操作中可能出现的资源泄漏。一个最小化的数据库连接池实现:
public class DBConnectionPool implements ConnectionPool
{
private static Vector pool;
private final int POOL_MAX_SIZE = 20;
/*
* 获取数据库连接
* 如果当前池中有可用连接,则将池中最后一个返回,如果没有,则新建一个返回
*/
public synchronized Connection getConnection() throws DBException
{
if (pool == null)
{
pool = new Vector();
}
Connection conn;
if (pool.isEmpty())
{
conn = createConnection();
}
else
{
int last_idx = pool.size() - 1;
conn = (Connection) pool.get(last_idx);
pool.remove(pool.get(last_idx));
}
return conn;
}
/*
* 将使用完毕的数据库连接放回备用池。
*
* 判断当前池中连接数是否已经超过阀值(POOL_MAX_SIZE),
* 如果超过,则关闭该连接。
* 否则放回池中以备下次重用。
*/
public synchronized void releaseConnection(Connection conn)
{
if (pool.size() > POOL_MAX_SIZE)
{
try
{
conn.close();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
else
{
pool.add(conn);
}
}
/**
* 读取数据库配置信息,并从数据库连接池中获得数据库连接
*
* @return
* @throws DBException
*/
private static Connection createConnection() throws DBException
{
Connection conn;
try
{
Class.forName("oracle.jdbc.driver.OracleDriver");
conn = DriverManager.getConnection(
"jdbc:oracle:thin:@localhost:1521:oracle",
"personal", "personal");
return conn;
}
catch (ClassNotFoundException e)
{
throw new DBException("ClassNotFoundException when loading JDBC Driver");
}
catch (SQLException e)
{
throw new DBException("SQLException when loading JDBC Driver");
}
}
}
上面的代码实现了一个最简单的连接池,因为简单,所以明了,目的只是展示数据库连接池实现的一般原理。完备的连接池实现固然错综复杂,但就其根本而言,还是源自同样的思想。
先脱离连接池本身的具体实现,我们看看这段代码在实际应用中可能产生的问题,注意到,由于getConnection 方法返回的是一个标准的JDBC Connection,程序员由于编程习惯,可能会习惯性的调用其close 方法关闭连接。如此一来,连接无法得到重用,数据库连接池机制形同虚设。为了解决这个问题,比较好的途径有:
1. Decorator 模式
2. Dynamic Proxy 模式
下面我们就这两种模式进行一些探讨
Decorator模式
“Decorator 模式的主要目的是利用一个对象,透明的为另一个对象添加新的功能”。这句话是从GOF 关于设计模式的经典著作《设计模式-可复用面向对象软件的基础》一书中关于Decorator 模式的描述直译而来,可能比较难以理解。简单来讲,就是通过一个Decorator 对原有对象进行封装,同时实现与原有对象相同的接口,从而得到一个基于原有对象的,对既有接口的增强性实现。其UML 描述如下:对于前面所讨论的Connection 释放的问题,理所当然,我们首先想到的是,如果能让JDBC Connection 在执行close 操作时自动将自己返回到数据库连接池中,那么所有问题都将迎刃而解,但是,JDBC Connection 自己显然无法根据实际情况判断何去何从。此时,引入Decorator 模式来解决我们所面对的问题就非常合适。
首先,我们引入一个ConnectionDecorator 类:
public class ConnectionDecorator implements Connection
{
Connection dbconn;
public ConnectionDecorator(Connection conn)
{
this.dbconn = conn; //实际从数据库获得的Connection引用
}
/* 此方法将被子类覆盖,以实现数据库连接池的连接返回操作
* @see java.sql.Connection#close()
*/
public void close() throws SQLException
{
this.dbconn.close();
}
/* (non-Javadoc)
* @see java.sql.Connection#commit()
*/
public void commit() throws SQLException
{
this.dbconn.commit();//调用实际连接的commit方法
}
……
//以下各个方法类似,均调用dbconn.xxx方法作为Connection接口定义的功能。
……
}
可以看到,ConnectionDecorator 类实际上是对传入的数据库连接加上了一个外壳,它实现了java.sql.Connection接口,不过本身并没有实现任何实际内容,只是简单的把方法的实现委托给运行期实际获得的Connection 实例,而从外部来看,ConnectionDecorator与普通的Connection 实例并没有什么区别,因为它们实现了同样的接口,对外提供了同样的功能调用。目标很清楚,通过这样的封装,我们的ConnectionDecorator 对于外部的程序员而言,调用方法与普通的JDBC Connection 完全相同,而在内部通过对ConnectionDecorator 的修改,我们就可以透明的改变现有实现,为之增加新的特性:
public class PooledConnection
extends ConnectionDecorator
implements Connection /????还需要实现这个接口吗?
{
private ConnectionPool connPool;
public PooledConnection(ConnectionPool pool, Connection conn)
{
super(conn);
connPool = pool;
}
/**
* 覆盖close方法,将数据库连接返回连接池,而不是直接关闭连接
*/
public void close() throws SQLException
{
connPool.releaseConnection(this.dbconn);
}
……
}
为了应用新的PooledConnection,我们需要对原本的DBConnectionPool.getConnection 和releaseConnection 方法稍做改造:
public synchronized Connection getConnection() throws DBException
{
if (pool == null)
{
pool = new Vector();
}
Connection conn;
if (pool.isEmpty())
{
conn = createConnection();
}
else
{
int last_idx = pool.size() - 1;
conn = (Connection) pool.get(last_idx);
pool.remove(pool.get(last_idx));
}
return new PooledConnection(this,conn);
}
public synchronized void releaseConnection(Connection conn)
{
if (conn instanceof PooledConnection || pool.size() > POOL_MAX_SIZE)
{
try {
conn.close();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
else{
pool.add(conn);
}
}
此时,获取数据库连接后,调用者只需要按照JDBC Connection 的标准用法进行调用即可,从而实现了数据库连接池的透明化。同样的道理,我们甚至可以利用Decorator模式对DriverManager 类进行同样的改造,从而最小化数据库连接池实现对传统JDBC编码方式的影响。有兴趣的可以基于同样的原理尝试自行实现。