连接池相关概念:
对于共享资源,有一个很著名的设计模式:资源池(Resource Pool)。该模式正是为了解决资源的频繁分配﹑释放所造成的问题。为解决我们的问题,可以采用数据库连接池技术。数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接。更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量﹑使用情况,为系统开发﹑测试及性能调整提供依据。
为什么使用连接池
连接,是我们的编程语言与数据库交互的一种方式。我们经常会听到这么一句话“数据库连接很昂贵“。
有人接受这种说法,却不知道它的真正含义。因此,下面通过实例解释它究竟是什么。
下面是MySQL数据库创建连接的的一段代码:
String connUrl ="jdbc:mysql://your.database.domain/yourDBname";
Class.forName("com.mysql.jdbc.Driver");
Connection con =DriverManager.getConnection (connUrl);
当我们创建了一个Connection对象,它在内部都执行了什么:
1.“DriverManager”检查并注册驱动程序;
2.“com.mysql.jdbc.Driver”就是我们注册了的驱动程序,它会在驱动程序类中调用“connect(url…)”方法。
3.com.mysql.jdbc.Driver的connect方法根据我们请求的“connUrl”,创建一个“Socket连接”,连接到IP为“your.database.domain”,默认端口3306的数据库。
4.创建的Socket连接将被用来查询我们指定的数据库,并最终让程序返回得到一个结果。
简单的获取一个连接,系统却要在背后做很多消耗资源的事情,大多时候,创建连接的时间比执行sql语句的时间还要长。
传统的获取连接方式如下图所示:
用户每次请求都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、拓机。
采用连接池技术后的过程如下:
数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现的尤为突出。对数据库连接的管理能显著影响到整个应用程序的伸缩性和健壮性,影响到程序的性能指标。数据库连接池负责分配,管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
需要注意的问题
1、并发问题
为了使连接管理服务具有最大的通用性,必须考虑多线程环境,即并发问题。这个问题相对比较好解决,因为各个语言自身提供了对并发管理的支持像java,c#等等,使用synchronized(java)、lock(C#)关键字即可确保线程是同步的。
2、事务处理
我们知道,事务具有原子性,此时要求对数据库的操作符合“ALL-OR-NOTHING”原则,即对于一组SQL语句要么全做,要么全不做。
我们知道当2个线程公用一个连接Connection对象,而且各自都有自己的事务要处理时候,对于连接池是一个很头疼的问题,因为即使Connection类提供了相应的事务支持,可是我们仍然不能确定那个数据库操作是对应那个事务的,这是由于我们有2个线程都在进行事务操作而引起的。为此我们可以使用每一个事务独占一个连接来实现,虽然这种方法有点浪费连接池资源但是可以大大降低事务管理的复杂性。
3、连接池的分配与释放
连接池的分配与释放,对系统的性能有很大的影响。合理的分配与释放,可以提高连接的复用度,从而降低建立新连接的开销,同时还可以加快用户的访问速度。
对于连接的管理可使用一个List。即把已经创建的连接都放入List中去统一管理。每当用户请求一个连接时,系统检查这个List中有没有可以分配的连接。如果有就把那个最合适的连接分配给他(如何能找到最合适的连接文章将在关键议题中指出);如果没有就抛出一个异常给用户,List中连接是否可以被分配由一个线程来专门管理。
4、连接池的配置与维护
连接池中到底应该放置多少连接,才能使系统的性能最佳?系统可采取设置最小连接数(minConnection)和最大连接数(maxConnection)等参数来控制连接池中的连接。比方说,最小连接数是系统启动时连接池所创建的连接数。如果创建过多,则系统启动就慢,但创建后系统的响应速度会很快;如果创建过少,则系统启动的很快,响应起来却慢。这样,可以在开发时,设置较小的最小连接数,开发起来会快,而在系统实际使用时设置较大的,因为这样对访问客户来说速度会快些。最大连接数是连接池中允许连接的最大数目,具体设置多少,要看系统的访问量,可通过软件需求上得到。
如何确保连接池中的最小连接数呢?有动态和静态两种策略。动态即每隔一定时间就对连接池进行检测,如果发现连接数量小于最小连接数,则补充相应数量的新连接,以保证连接池的正常运转。静态是发现空闲连接不够时再去检查。
我的理解是相当于 一种 依缓存的方式,减少 直接与资源库的交互,节约时间与资源的创建从而提高效率;
下面为实现 连接池的具体代码(mysql为例):
package com.java.auto.pool;
import java.sql.Connection;
/**
* 定义 池pool具有的规则
* @author zhangqingzhou
*
*/
public interface Pool {
/**
* 获取 最初初始化的 连接数
* @return
*/
public int getInitialConnections() ;
/**
* 设置 初始化连接数
* @param initialConnections
*/
public void setInitialConnections(int initialConnections);
/**
* 每次增长值
* @return
*/
public int getIncrementalConnections() ;
/**
* 设置 每次增长值
* @param incrementalConnections
*/
public void setIncrementalConnections(int incrementalConnections);
/**
* 获取最大连接数
* @return
*/
public int getMaxConnections();
/**
* 设置最大连接数
*
*/
public void setMaxConnections(int maxConnections);
/**
* 初始化池
*/
public void initPool();
/**
* 获取连接
* @return
*/
public Connection getConnection();
/**
* 释放(返还)连接到 池子
* @param conn
*/
public void returnConnection(Connection conn);
/**
* 刷新 池子
* 相当于重启
*/
public void refreshConnections();
/**
* 关闭连接池
*/
public void closeConnectionPool() ;
}
package com.java.auto.pool;
import java.sql.Connection;
/**
* 记录每个已经创建连接的状态 是否忙碌
* 便于 获取非忙碌的 connect
* @author zhangqingzhou
*
*/
public class PooledConnection {
Connection connection = null; // 数据库连接
// 此连接是否正在使用的标志,默认没有正在使用
boolean busy = false;
// 构造函数,根据一个 Connection 构告一个 PooledConnection 对象
public PooledConnection(Connection connection) {
this.connection = connection;
}
// 返回此对象中的连接
public Connection getConnection() {
return connection;
}
// 设置此对象的,连接
public void setConnection(Connection connection) {
this.connection = connection;
}
// 获得对象连接是否忙
public boolean isBusy() {
return busy;
}
// 设置对象的连接正在忙
public void setBusy(boolean busy) {
this.busy = busy;
}
}
package com.java.auto.pool;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Enumeration;
import java.util.Vector;
// 具体实现类
public class ConnectionPool {
private String jdbcDriver = ""; // 数据库驱动
private String dbUrl = ""; // 数据 URL
private String dbUsername = ""; // 数据库用户名
private String dbPassword = ""; // 数据库用户密码
private String testTable = ""; // 测试连接是否可用的测试表名,默认没有测试表
private int initialConnections = 10; // 连接池的初始大小
private int incrementalConnections = 5; // 连接池自动增加的大小
private int maxConnections = 50; // 连接池最大的大小
private Vector connections = null; // 存放连接池中数据库连接的向量 , 初始时为 null
// 它中存放的对象为 PooledConnection 型
public ConnectionPool(String jdbcDriver, String dbUrl, String dbUsername,
String dbPassword) {
this.jdbcDriver = jdbcDriver;
this.dbUrl = dbUrl;
this.dbUsername = dbUsername;
this.dbPassword = dbPassword;
}
public int getInitialConnections() {
return this.initialConnections;
}
public void setInitialConnections(int initialConnections) {
this.initialConnections = initialConnections;
}
public int getIncrementalConnections() {
return this.incrementalConnections;
}
public void setIncrementalConnections(int incrementalConnections) {
this.incrementalConnections = incrementalConnections;
}
public int getMaxConnections() {
return this.maxConnections;
}
public void setMaxConnections(int maxConnections) {
this.maxConnections = maxConnections;
}
public String getTestTable() {
return this.testTable;
}
public void setTestTable(String testTable) {
this.testTable = testTable;
}
public synchronized void createPool() throws Exception {
// 确保连接池没有创建
// 假如连接池己经创建了,保存连接的向量 connections 不会为空
if (connections != null) {
return; // 假如己经创建,则返回
}
// 实例化 JDBC Driver 中指定的驱动类实例
Driver driver = (Driver) (Class.forName(this.jdbcDriver).newInstance());
DriverManager.registerDriver(driver); // 注册 JDBC 驱动程序
// 创建保存连接的向量 , 初始时有 0 个元素
connections = new Vector();
// 根据 initialConnections 中设置的值,创建连接。
createConnections(this.initialConnections);
System.out.println(" 数据库连接池创建成功! ");
}
private void createConnections(int numConnections) throws SQLException {
// 循环创建指定数目的数据库连接
for (int x = 0; x < numConnections; x++) {
// 是否连接池中的数据库连接的数量己经达到最大?最大值由类成员 maxConnections
// 指出,假如 maxConnections 为 0 或负数,表示连接数量没有限制。
// 假如连接数己经达到最大,即退出。
if (this.maxConnections > 0 &&
this.connections.size() >= this.maxConnections) {
break;
}
//add a new PooledConnection object to connections vector
// 增加一个连接到连接池中(向量 connections 中)
try {
connections.addElement(new PooledConnection(newConnection()));
} catch (SQLException e) {
System.out.println(" 创建数据库连接失败! " + e.getMessage());
throw new SQLException();
}
System.out.println(" 数据库连接己创建 ......");
}
}
private Connection newConnection() throws SQLException {
// 创建一个数据库连接
Connection conn = DriverManager.getConnection(dbUrl, dbUsername,
dbPassword);
// 假如这是第一次创建数据库连接,即检查数据库,获得此数据库答应支持的
// 最大客户连接数目
//connections.size()==0 表示目前没有连接己被创建
if (connections.size() == 0) {
DatabaseMetaData metaData = conn.getMetaData();
int driverMaxConnections = metaData.getMaxConnections();
// 数据库返回的 driverMaxConnections 若为 0 ,表示此数据库没有最大
// 连接限制,或数据库的最大连接限制不知道
//driverMaxConnections 为返回的一个整数,表示此数据库答应客户连接的数目
// 假如连接池中设置的最大连接数量大于数据库答应的连接数目 , 则置连接池的最大
// 连接数目为数据库答应的最大数目
if (driverMaxConnections > 0 &&
this.maxConnections > driverMaxConnections) {
this.maxConnections = driverMaxConnections;
}
}
return conn; // 返回创建的新的数据库连接
}
public synchronized Connection getConnection() throws SQLException {
// 确保连接池己被创建
if (connections == null) {
return null; // 连接池还没创建,则返回 null
}
Connection conn = getFreeConnection(); // 获得一个可用的数据库连接
// 假如目前没有可以使用的连接,即所有的连接都在使用中
while (conn == null) {
// 等一会再试
wait(250);
conn = getFreeConnection(); // 重新再试,直到获得可用的连接,假如
//getFreeConnection() 返回的为 null
// 则表明创建一批连接后也不可获得可用连接
}
return conn; // 返回获得的可用的连接
}
private Connection getFreeConnection() throws SQLException {
// 从连接池中获得一个可用的数据库连接
Connection conn = findFreeConnection();
if (conn == null) {
// 假如目前连接池中没有可用的连接
// 创建一些连接
createConnections(incrementalConnections);
// 重新从池中查找是否有可用连接
conn = findFreeConnection();
if (conn == null) {
// 假如创建连接后仍获得不到可用的连接,则返回 null
return null;
}
}
return conn;
}
private Connection findFreeConnection() throws SQLException {
Connection conn = null;
PooledConnection pConn = null;
// 获得连接池向量中所有的对象
Enumeration enumerate = connections.elements();
// 遍历所有的对象,看是否有可用的连接
while (enumerate.hasMoreElements()) {
pConn = (PooledConnection) enumerate.nextElement();
if (!pConn.isBusy()) {
// 假如此对象不忙,则获得它的数据库连接并把它设为忙
conn = pConn.getConnection();
pConn.setBusy(true);
// 测试此连接是否可用
if (!testConnection(conn)) {
// 假如此连接不可再用了,则创建一个新的连接,
// 并替换此不可用的连接对象,假如创建失败,返回 null
try {
conn = newConnection();
} catch (SQLException e) {
System.out.println(" 创建数据库连接失败! " + e.getMessage());
return null;
}
pConn.setConnection(conn);
}
break; // 己经找到一个可用的连接,退出
}
}
return conn; // 返回找到到的可用连接
}
private boolean testConnection(Connection conn) {
try {
// 判定测试表是否存在
if (testTable.equals("")) {
// 假如测试表为空,试着使用此连接的 setAutoCommit() 方法
// 来判定连接否可用(此方法只在部分数据库可用,假如不可用 ,
// 抛出异常)。注重:使用测试表的方法更可靠
conn.setAutoCommit(true);
} else { // 有测试表的时候使用测试表测试
//check if this connection is valid
Statement stmt = conn.createStatement();
stmt.execute("select count(*) from " + testTable);
}
} catch (SQLException e) {
// 上面抛出异常,此连接己不可用,关闭它,并返回 false;
closeConnection(conn);
return false;
}
// 连接可用,返回 true
return true;
}
public void returnConnection(Connection conn) {
// 确保连接池存在,假如连接没有创建(不存在),直接返回
if (connections == null) {
System.out.println(" 连接池不存在,无法返回此连接到连接池中 !");
return;
}
PooledConnection pConn = null;
Enumeration enumerate = connections.elements();
// 遍历连接池中的所有连接,找到这个要返回的连接对象
while (enumerate.hasMoreElements()) {
pConn = (PooledConnection) enumerate.nextElement();
// 先找到连接池中的要返回的连接对象
if (conn == pConn.getConnection()) {
// 找到了 , 设置此连接为空闲状态
pConn.setBusy(false);
break;
}
}
}
public synchronized void refreshConnections() throws SQLException {
// 确保连接池己创新存在
if (connections == null) {
System.out.println(" 连接池不存在,无法刷新 !");
return;
}
PooledConnection pConn = null;
Enumeration enumerate = connections.elements();
while (enumerate.hasMoreElements()) {
// 获得一个连接对象
pConn = (PooledConnection) enumerate.nextElement();
// 假如对象忙则等 5 秒 ,5 秒后直接刷新
if (pConn.isBusy()) {
wait(5000); // 等 5 秒
}
// 关闭此连接,用一个新的连接代替它。
closeConnection(pConn.getConnection());
pConn.setConnection(newConnection());
pConn.setBusy(false);
}
}
public synchronized void closeConnectionPool() throws SQLException {
// 确保连接池存在,假如不存在,返回
if (connections == null) {
System.out.println(" 连接池不存在,无法关闭 !");
return;
}
PooledConnection pConn = null;
Enumeration enumerate = connections.elements();
while (enumerate.hasMoreElements()) {
pConn = (PooledConnection) enumerate.nextElement();
// 假如忙,等 5 秒
if (pConn.isBusy()) {
wait(5000); // 等 5 秒
}
//5 秒后直接关闭它
closeConnection(pConn.getConnection());
// 从连接池向量中删除它
connections.removeElement(pConn);
}
// 置连接池为空
connections = null;
}
private void closeConnection(Connection conn) {
try {
conn.close();
} catch (SQLException e) {
System.out.println(" 关闭数据库连接出错: " + e.getMessage());
}
}
private void wait(int mSeconds) {
try {
Thread.sleep(mSeconds);
} catch (InterruptedException e) {
}
}
}
package com.java.auto.pool;
import java.sql.Connection;
import java.sql.SQLException;
/**
* 连接池 工具类
* @author zhangqingzhou
*
*/
public class PoolUtil implements Pool{
/**
* 此处可以设置成 读取 配置文件的方式
*/
private static String jdbcDriver = "com.mysql.jdbc.Driver"; // 数据库驱动
private static String dbUrl = "jdbc:mysql://localhost:3308/test"; // 数据 URL
private static String dbUsername = " "; // 数据库用户名
private static String dbPassword = "66666"; // 数据库用户密码
private static ConnectionPool connPool = null;
// 设置成单例模式 防止 多次实例化连接池
private static PoolUtil instance = new PoolUtil();
private PoolUtil(){};
public static PoolUtil getInstance(){
return instance;
}
static{
connPool = new ConnectionPool(jdbcDriver, dbUrl , dbUsername, dbPassword);
}
@Override
public int getInitialConnections() {
return connPool.getInitialConnections();
}
@Override
public void setInitialConnections(int initialConnections) {
connPool.setInitialConnections(initialConnections);
}
@Override
public int getIncrementalConnections() {
return connPool.getIncrementalConnections();
}
@Override
public void setIncrementalConnections(int incrementalConnections) {
connPool.setIncrementalConnections(incrementalConnections);
}
@Override
public int getMaxConnections() {
return connPool.getMaxConnections();
}
@Override
public void setMaxConnections(int maxConnections) {
connPool.setMaxConnections(maxConnections);
}
@Override
public void initPool() {
try {
connPool.createPool();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public Connection getConnection() {
Connection conn = null;
try {
conn = connPool.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
@Override
public void returnConnection(Connection conn) {
connPool.returnConnection(conn);
}
@Override
public void refreshConnections() {
try {
connPool.refreshConnections();
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void closeConnectionPool() {
try {
connPool.closeConnectionPool();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
package com.java.auto.pool;
import java.sql.Connection;
/**
* 测试类
* @author zhangqingzhou
*
*/
public class Test {
public static void main(String[] args) {
PoolUtil pool = PoolUtil.getInstance();
pool.initPool();
Connection connection = pool.getConnection();
System.out.println("connection:"+connection);
//pool.returnConnection(connection);
}
}
logs:
数据库连接己创建 ......
数据库连接己创建 ......
数据库连接己创建 ......
数据库连接己创建 ......
数据库连接己创建 ......
数据库连接己创建 ......
数据库连接己创建 ......
数据库连接己创建 ......
数据库连接己创建 ......
数据库连接己创建 ......
数据库连接池创建成功!
connection:com.mysql.jdbc.JDBC4Connection@736e9adb
表明 创建成功
不足之处,请指正!
相关资料:
http://blog.youkuaiyun.com/fangquan1980/article/details/52429903
http://www.open-open.com/lib/view/open1410875608164.html