一、连接池的基本工作原理
1、基本概念及原理
数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接。更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量和使用情况,为系统开发、测试及性能调整提供依据。
2、服务器自带的连接池
JDBC的API中没有提供连接池的方法。一些大型的WEB应用服务器如BEA的WebLogic和IBM的WebSphere等提供了连接池的机制,但是必须有其第三方的专用类方法支持连接池的用法。
二、连接池关键问题分析
1、并发问题
为了使连接管理服务具有最大的通用性,必须考虑多线程环境,即并发问题。这个问题相对比较好解决,因为Java语言自身提供了对并发管理的支持,使用synchronized关键字即可确保线程是同步的。使用方法为直接在类方法前面加上synchronized关键字,如:
public synchronized Connection getConnection ()
2、多数据库服务器和多用户
对于大型的企业级应用,常常需要同时连接不同的数据库(如连接Oracle和Sybase)。如何连接不同的数据库呢?我们采用的策略是:设计一个符合单例模式的连接池管理类,在连接池管理类的唯一实例被创建时读取一个资源文件,其中资源文件中存放着多个数据库的地址、用户名、密码等信息。根据资源文件提供的信息,创建多个连接池类的实例,每一个实例都是一个特定数据库的连接池。连接池管理类实例为每个连接池实例取一个名字,通过不同的名字来管理不同的连接池。
对于同一个数据库有多个用户使用不同的名称和密码访问的情况,也可以通过资源文件处理,即在资源文件中设置多个具有相同url地址,但具有不同用户名和密码的数据库连接信息。
3、事务处理
我们知道,事务具有原子性,此时要求对数据库的操作符合“ALL-ALL-NOTHING”原则,即对于一组SQL语句要么全做,要么全不做。
在Java语言中,Connection类本身提供了对事务的支持,可以通过设置Connection的AutoCommit属性为 false,然后显式的调用commit或rollback方法来实现。但要高效的进行Connection复用,就必须提供相应的事务支持机制。可采用每一个事务独占一个连接来实现,这种方法可以大大降低事务管理的复杂性。
4、连接池的分配与释放
连接池的分配与释放,对系统的性能有很大的影响。合理的分配与释放,可以提高连接的复用度,从而降低建立新连接的开销,同时还可以加快用户的访问速度。
对于连接的管理可使用空闲池。即把已经创建但尚未分配出去的连接按创建时间存放到一个空闲池中。每当用户请求一个连接时,系统首先检查空闲池内有没有空闲连接。如果有就把建立时间最长(通过容器的顺序存放实现)的那个连接分配给它(实际是先做连接是否有效的判断,如果可用就分配给用户,如果不可用就把这个连接从空闲池删掉,重新检测空闲池是否还有连接),如果没有则检查当前所开连接池是否达到连接池所允许的最大连接数(maxConn),如果没有达到,就新建一个连接,如果已经达到,就等待一定的时间(timeout)。如果在等待的时间内有连接被释放出来就可以把这个连接分配给等待的用户,如果等待时间超过预定时间timeout,则返回空值(null)。系统对已经分配出去正在使用的连接只做计数,当使用完后再返还给空闲池。对于空闲连接的状态,可开辟专门的线程定时检测,这样会花费一定的系统开销,但可以保证较快的响应速度。也可采取不开辟专门线程,只是在分配前检测的方法。
5、连接池的配置与维护
连接池中到底应该放置多少连接,才能使系统的性能最佳?系统可采取设置最小连接数(minConn)和最大连接数(maxConn)来控制连接池中的连接。最小连接数是系统启动时连接池所创建的连接数。如果创建过多,则系统启动就慢,但创建后系统的响应速度会很快;如果创建过少,则系统启动的很快,响应起来却慢。这样,可以在开发时,设置较小的最小连接数,开发起来会快,而在系统实际使用时设置较大的,因为这样对访问客户来说速度会快些。最大连接数是连接池中允许连接的最大数目,具体设置多少,要看系统的访问量,可通过反复测试,找到最佳点。
如何确保连接池中的最小连接数呢?有动态和静态两种策略。动态即每隔一定时间就对连接池进行检测,如果发现连接数量小于最小连接数,则补充相应数量的新连接,以保证连接池的正常运转。静态是发现空闲连接不够时再去检查。
三、连接池的实现
1、连接池模型
本文讨论的连接池包括一个连接池类(DBConnectionPool)和一个连接池管理类(DBConnetionPoolManager)。连接池类是对某一数据库所有连接的“缓冲池”,主要实现以下功能:①从连接池获取或创建可用连接;②使用完毕之后,把连接返还给连接池;③在系统关闭前,断开所有连接并释放连接占用的系统资源;④还能够处理无效连接(原来登记为可用的连接,由于某种原因不再可用,如超时,通讯问题),并能够限制连接池中的连接总数不低于某个预定值和不超过某个预定值。
连接池管理类是连接池类的外覆类(wrapper),符合单例模式,即系统中只能有一个连接池管理类的实例。其主要用于对多个连接池对象的管理,具有以下功能:①装载并注册特定数据库的JDBC驱动程序;②根据属性文件给定的信息,创建连接池对象;③为方便管理多个连接池对象,为每一个连接池对象取一个名字,实现连接池名字与其实例之间的映射;④跟踪客户使用连接情况,以便需要是关闭连接释放资源。连接池管理类的引入主要是为了方便对多个连接池的使用和管理,如系统需要连接不同的数据库,或连接相同的数据库但由于安全性问题,需要不同的用户使用不同的名称和密码。
2、连接池实现
DBConnectionPool:连接池类。
package com.svse.pool;
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; import java.util.Iterator; import java.util.List;
/** * * 此内部类定义了一个连接池.它能够根据要求创建新连接,直到预定的最大连接数为止. * * 在返回连接给客户程序之前,它能够验证连接的有效性. * * @author svse * */ public class DBConnectionPool { /** * 传说中的连接池 */ private List<Connection> freeConnections = new ArrayList<Connection>(); private Connection con = null; private int connect = 0; // 使用的连接数
private int maxConn; // 最大连接 @SuppressWarnings("unused") private String name; // 连接池名字 private String driver; // 驱动 private String url; // 数据库连接地址 private String user; // 用户名 private String password;// 密码
/** * 有参构造创建连接池 * * @param driver * @param name * @param URL * @param user * @param password * @param maxConn */ public DBConnectionPool(String name, String driver, String URL, String user, String password, int maxConn) { this.name = name; this.driver = driver; this.url = URL; this.user = user; this.password = password; this.maxConn = maxConn; poolInfo(); }
/** * 显示准备创建连接池的信息 */ private void poolInfo() { Connection conn = this.newConnection(); freeConnections.add(conn); for (int i = 0; i < this.maxConn - 1; i++) { Connection freeConn = conn; freeConnections.add(freeConn); } }
/** * 用完,释放连接 * * @param con * 释放一个连接 */ public synchronized void freeConnection(Connection con) { this.freeConnections.add(con); this.connect--; }
/** * 从连接池中获取一个可用连接 * * 当无法从池中获取可用连接时,新创建一个连接 * * @return 返回连接对象 */ public synchronized Connection getConnection() { if (this.freeConnections.size() > 0) { con = this.freeConnections.get(0); /** * 当在池中取出一个连接后,删除此连接 */ this.freeConnections.remove(0); /** * 当取出的连接为null时,递归调用自己,直到获得一个可用连接为止 */ if (con == null) con = getConnection(); } else { con = newConnection(); } if (this.maxConn == 0 || this.maxConn < this.connect) { /** * 等待 超过最大连接时 */ con = null; } if (con != null) { this.connect++; } return con; }
/** * 释放全部连接 * */ public synchronized void release() { Iterator<Connection> allConns = this.freeConnections.iterator(); while (allConns.hasNext()) { Connection conn = (Connection) allConns.next(); try { if (null != conn) { conn.close(); } conn = null; } catch (SQLException e) { e.printStackTrace(); }
} this.freeConnections.clear();
}
/** * 创建一个数据库连接对象 * * @return 返回数据库连接 */ private Connection newConnection() { try { Class.forName(driver); } catch (ClassNotFoundException e2) {
e2.printStackTrace(); } try { con = DriverManager.getConnection(url, user, password); } catch (SQLException e1) { e1.printStackTrace(); System.exit(0); } return con; } }
|
DBConnectionManager:连接池管理类。
package com.svse.pool;
import java.sql.Connection; import java.util.Enumeration; import java.util.Hashtable;
/** * 连接池的管理类,负责读取配置连接池的文件,并创建连接池 * * 从池中获取,释放连接 * * @author svse * */ public class DBConnectionManager { /** * 唯一数据库连接池管理实例类 * * 使用单例模式创建 */ private static DBConnectionManager instance; /** * 连接池的集合, */ private Hashtable<String, DBConnectionPool> pools = new Hashtable<String, DBConnectionPool>();
/** * 得到唯一实例管理类 * * @return 一个连接池的管理类 */ public static synchronized DBConnectionManager getInstance(String name) { if (instance == null) { instance = new DBConnectionManager(name); } return instance;
}
/** * 只允许内部实例化管理类 */ private DBConnectionManager(String name) { this.init(name); }
/** * 加载驱动程序 */ private void init(String name) { PoolParse poolParse = new PoolParse(name); DBConnectionPool pool = new DBConnectionPool(poolParse.getName(), poolParse.getDriver(), poolParse.getUrl(), poolParse.getUser(), poolParse.getPassword(), poolParse.getMaxconn()); pools.put(poolParse.getName(), pool); }
/** * 根据连接池的名字得到一个连接 * * @param name * 连接池的名称 * @return 池中的一个可用连接 */ public Connection getConnection(String name) { DBConnectionPool pool = null; Connection con = null; pool = pools.get(name); try { con = pool.getConnection(); } catch (Exception e) { e.printStackTrace(); } return con; }
/** * 释放一个连接 * * @param name * 连接池的名称 * @param con * 将要是释放的连接对象 */ public synchronized void freeConnection(String name, Connection con) { DBConnectionPool pool = pools.get(name);// 根据连接池名称得到连接池 if (pool != null) pool.freeConnection(con);// 释放连接 }
/** * 释放所有连接 */ public synchronized void release() { Enumeration<DBConnectionPool> allpools = pools.elements(); while (allpools.hasMoreElements()) { DBConnectionPool pool = allpools.nextElement(); if (pool != null) pool.release(); } pools.clear(); } }
|
PoolParse:读取数据库连接。
package com.svse.pool;
import java.io.UnsupportedEncodingException; import java.net.URLDecoder;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList;
public class PoolParse { private static String path;
static{ try { path = URLDecoder.decode(PoolParse.class.getResource("/com/svse/pool/pool.xml").getPath(),"UTF-8"); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block throw new RuntimeException(e); } }
public PoolParse(String name){ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); Document doc = null; try { DocumentBuilder db = dbf.newDocumentBuilder(); doc = db.parse(path); } catch (Exception e) { // TODO Auto-generated catch block throw new RuntimeException(e); } NodeList nodeList = doc.getElementsByTagName("config"); for(int i=0;i<nodeList.getLength();i++){ Element e = (Element) nodeList.item(i); if(e.getElementsByTagName("name").item(0).getTextContent().equals(name)){ this.setName(e.getElementsByTagName("name").item(0).getTextContent()); this.setDriver(e.getElementsByTagName("driver").item(0).getTextContent()); this.setUrl(e.getElementsByTagName("url").item(0).getTextContent()); this.setUser(e.getElementsByTagName("user").item(0).getTextContent()); this.setPassword(e.getElementsByTagName("password").item(0).getTextContent()); this.setMaxconn(Integer.parseInt(e.getElementsByTagName("maxconn").item(0).getTextContent())); break; } } }
private String name; private String driver; private String url; private String user; private String password; private Integer maxconn; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDriver() { return driver; } public void setDriver(String driver) { this.driver = driver; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getUser() { return user; } public void setUser(String user) { this.user = user; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Integer getMaxconn() { return maxconn; } public void setMaxconn(Integer maxconn) { this.maxconn = maxconn; } }
|
pool.xml:记录数据库连接信息。
<?xml version="1.0" encoding="UTF-8"?> <configs> <config> <name>oracle</name> <driver>oracle.jdbc.driver.OracleDriver</driver> <url>jdbc:oracle:thin:@PC-200911080919:1521:orcl</url> <user>scott</user> <password>tiger</password> <maxconn>4</maxconn> </config> <config> <name>sql</name> <driver>xxx</driver> <url>xxx</url> <user>sa</user> <password>123456</password> <maxconn>4</maxconn> </config> <config> <name>mysql</name> <driver>xxx</driver> <url>xxx</url> <user>root</user> <password>123456</password> <maxconn>4</maxconn> </config> </configs> |
Test:用于测试。
package com.svse.pool;
import java.sql.Connection;
public class Test { public static void main(String[] args) { //获取一个实例 DBConnectionManager manager = DBConnectionManager.getInstance("oracle"); //获取一个连接 Connection conn1 = manager.getConnection("oracle"); Connection conn2 = manager.getConnection("oracle"); System.out.println(conn1.toString()); System.out.println(conn2.toString()); //释放连接 manager.freeConnection("oracle", conn1); manager.freeConnection("oracle", conn2); } } |
四、开源数据库连接池的常见配置
自己要实现一个连接池太复杂了!要考虑的东西太多,比如:连接对象的 close 方法处理、物理连接中断时处理、 数据库连接池耗尽了之后如何处理等等诸如此类的问题。在实际项目中,我们会直接使用一些成熟的开源数据库连接池,常见的连接池有c3p0、dbcp等开源数据库连接池。下面的代码总结了spring下如何配置dbcp,c3p0,proxool等数据源连接池。
Spring配置dbcp数据源:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName"> <value>${jdbc.driverClassName}</value> </property> <property name="url"> <value>${jdbc.url}</value> </property> <property name="username"> <value>${jdbc.username}</value> </property> <property name="password"> <value>${jdbc.password}</value> </property> <property name="maxActive"> <value>80</value> </property> <property name="maxIdle"> <value>20</value> </property> <property name="maxWait"> <value>3000</value> </property> </bean> |
Spring中配置c3p0数据源:
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass"> <value>${jdbc.driverClassName}</value> </property> <property name="jdbcUrl"> <value>${jdbc.url}</value> </property> <property name="user"> <value>${jdbc.username}</value> </property> <property name="password"> <value>${jdbc.password}</value> </property> <property name="acquireIncrement"> <value>5</value> </property> <property name="idleConnectionTestPeriod"> <value>3000</value> </property> <property name="checkoutTimeout"> <value>3000</value> </property> <property name="maxPoolSize"> <value>80</value> </property> <property name="minPoolSize"> <value>1</value> </property> <property name="maxStatements"> <value>6000</value> </property> <property name="initialPoolSize"> <value>5</value> </property> </bean> |
Spring中配置proxool数据源:
<bean id="dataSource" class="org.logicalcobwebs.proxool.ProxoolDataSource" destroy-method="close"> <property name="driver"> <value>${jdbc.driverClassName}</value> </property> <property name="driverUrl"> <value>${jdbc.url}</value> </property> <property name="user"> <value>${jdbc.username}</value> </property> <property name="password"> <value>${jdbc.password}</value> </property> <property name="alias"> <value>test</value> </property> <property name="houseKeepingSleepTime"> <value>90000</value> </property> <property name="prototypeCount"> <value>10</value> </property> <property name="maximumConnectionCount"> <value>100</value> </property> <property name="minimumConnectionCount"> <value>10</value> </property> <property name="trace"> <value>true</value> </property> <property name="verbose"> <value>true</value> </property> </bean> |
五、总结:
在使用JDBC进行与数据库有关的应用开发中,特别是大量的JSP访问数据库的应用开发中,由于应用数据库连接池技术能极大的提高减少连接和关闭数据库的操作而显著的提高了系统的性能。本文探讨了数据库连接池的组成、调度策略,并自定义了数据库连接池以及开源数据库连接池的使用。通过笔者以前的开发经验,数据库连接池的有效引入,明显提高了系统的性能。