使用数据库连接池的原因及优势
JDBC做为一种数据库访问技术,具有简单易用的优点。但在程序开发时,不使用数据库连接池的情况下,系统会存在诸多问题如:每一次Web请求都要建立依次数据库连接,改过程相当耗时,在开发模式下使用MyEclipse开发工具2G内存的情况下建立一次数据库连接的时间为1.5s左右。高并发访问时,如此频繁耗时的数据库连接操作势必占用很多的系统资源,造成网站响应速度的下降,甚至造成服务器的崩溃。
数据库连接池的原理及具体实现
对于共享资源如何高效分配,资源池模式可解决该问题。为数据库连接建立一个缓冲池,预先在“缓冲池”中放入一定数量的连接,有对象需要数据库连接时,从缓冲池中取出一个,使用完毕后在放回去。基于此原理解决了资源频繁分配和创建的冗余和浪费问题。
在系统初始化时,连接池管理对象根据配置文件中的minConn的值,创建与该值对应的数据库连接。当系统中有对象需要数据库连接时,连接池管理对象便从池中为该对象获取一个连接,该对象用完后放回连接池。当连接数达到最大连接数(配置文件中的maxConn)时,连接池不再创建新的连接。需要数据库连接的对象等待一定的时间(配置文件中的waitTime)。如果在等待时间内,有连接放回连接池,则被该对象获取,如果没有再次等待相同时间(递归实现)知道获取连接为止。数据库连接池相关参数的配置文件如下图:
DBConnectionManager类采用单例模式避免了资源的冗余创建(对象反复创建销毁)造成的不必要开销。
package pool.db;
import java.sql.Connection;
publicclass DBConnectionManager {
privatestatic DBConnectionManager instance;
private DBConnectionPool dbPool;
private DBConnectionManager() {
init();
}
publicstaticsynchronized DBConnectionManager getInstance() {
if (instance == null) {
instance = new DBConnectionManager();
}
returninstance;
}
public Connection getConnection() {
returndbPool.getConnection();
}
publicvoid close(Connection conn) {
if (dbPool != null) {
dbPool.freeConnection(conn);
}
}
public synchronizedvoid release() {
if (dbPool != null) {
dbPool.release();
}
}
privatevoid init() {
dbPool = new DBConnectionPool();
}
}
构成数据库连接池各类之间的关系如下:
在该模块中通过.properties类型的配置文件,接口、实现类、管理者三者之间完全分离,极大提高了项目的扩展性。实例代码如下:
......
privatevoid init() {
ConfigUtil.setFileName("util/db_xk.properties");
String driver = ConfigUtil.getValue("driver");
String url = ConfigUtil.getValue("url");
String user = ConfigUtil.getValue("user");
String password = ConfigUtil.getValue("password");
String minConn = ConfigUtil.getValue("minConn");
String maxConn = ConfigUtil.getValue("maxConn");
String waitTime = ConfigUtil.getValue("waitTime");
long start = System.currentTimeMillis();
for (int i = 0; i < Long.valueOf(minConn); i++) {
System.out.print("DBConnectionPool init :");
System.out.println(i);
Connection conn = null;
try {
Class.forName(driver);
conn = DriverManager.getConnection(url, user, password);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
System.out
.println("sorry can't create Connection, please check the SQL service...");
}
this.freeConnections.add(conn);
this.maxConn = Integer.valueOf(maxConn);
this.minConn = Integer.valueOf(minConn);
this.waitTime = Integer.valueOf(waitTime);
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
......
数据库核心算法的实现方法中递归避免栈溢出的办法
核心算法实现方法如下:
……
public synchronized Connection getConnection() {
Connection conn = null;
if (this.maxConn == 0 || this.maxConn < this.inUsed) {
try {
Thread.sleep(waitTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
conn = getConnection();
} else {
if (this.freeConnections.size() > 0) {
conn = this.freeConnections.get(0);
this.freeConnections.remove(0);
if (conn == null) {
conn = getConnection();
}
} else {
conn = newConnection();
}
}
if (conn != null) {
this.inUsed ++;
}
return conn;
}
......
方法中避免递归造成栈溢出的代码段为:
......
if (this.maxConn == 0 || this.maxConn < this.inUsed) {
try {
Thread.sleep(waitTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
conn = getConnection();
}
......
配置文件db_xk.properties内容为:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/db_xk?useUnicode=true&characterEncoding=utf8
user=root
password=mysql
minConn=3
maxConn=20
waitTime=300
从上述论述可知,在系统中的对象需要连接但连接池中又没有空闲连接可用时,系统将会等待300ms,300ms过后仍无空闲连接递归调用。在实际应用中,系统中的对象从连接池中取出连接到释放的过程所用的时间最多不会超过100ms(证明在下文),这就意味着一般情况下当连接池中没有空闲时,对象等待300ms后有连接放回连接池,对象可以得到。
假设在高并发时,请求连接的对象需要多次等待即getConnection方法进行多次递归时。该情况下能否造成栈溢出致使系统崩溃。测试结果如下