建立数据库连接相对其他代码的执行是一个需要消耗大量时间的工作,当我们的JDBC程序向数据库请求连接需要同数据库系统建立通讯、分配资源和权限认证等操作。这些操作对系统的开销很高。
为了提高整个JAVA数据库应用的速度,我们必须解决数据库连接的瓶颈问题。于是就出现了数据库连接池(connection pool)的概念。核心理念就是在内存中建立一个虚拟的容器,在这个容器中保存预先建立好的数据库连接对象,把这些连接对象分配给需要的程序使用,当连接对象使用完成之后,不直接关闭连接,而是重新保存到容器中,供其他程序重复使用。这样就减少了建立数据库连接所需要的时间。
通过使用连接池技术大大缓解了数据连接的瓶颈问题,让数据库应用获得更快的运行速度。并且通过使用连接池技术还可以获得更好的安全性,并且可以根据应用系统的需求,实时的修改连接池中保存的数据库连接对象的数量。更有效的使用数据库连接池中的数据库连接对象。主流的JAVA应用服务器都提供了内置的数据库连接池,供我们部署的应用所使用;还可以使用一些第三方的开源连接池组件。
下面简析一下简单连接池的工作机制,主要的目的是理解连接连接池内部运行机制。
首先,需要需要实现一个对象容器来保存建立好的数据库连接对象。通过它对所有的数据库连接进行统一管理,获取连接池中连接的方法应该使用设计模式中的单子模式,让所有想获得连接池中连接的外表程序只能通过一个连接池管理对象来获得。最好还有相应的处理机制,能够把超过一定时限没有使用的数据库连接对象强行回收。同时能够调整连接池中的连接数,也就是可以设置连接池的初始大小。
新建一个dbpool.properties 文件,这个文件是一个文本文件,记录了建立数据库连接池所需要的数据库参数,所以称这个文件为连接池的配置文件。如果需要改变数据库的类型,只需要修改本文件中的内容,不需要修改连接池程序代码。
dbpool.properties内容如下:
driverClassName = oracle.jdbc.driver.OracleDriver
username = platemm
password = platemm
url=jdbc:oracle:thin:@localhost:1521:orcl
poolsize=10
ConnectionPool.java 实现连接池的核心类,代码如下:
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Properties;
public class ConnectionPool {
private ArrayList<Connection> pool;
private String url;
private String username;
private String password;
private String driverClassName;
private int poolsize = 1;
private static ConnectionPool instance =null;
private ConnectionPool(){
init();
}
private void init() {
pool = new ArrayList<Connection>(poolsize);
readConfig();
addConnection();
}
public synchronized void release(Connection conn){
pool.add(conn);
}
public synchronized void closePool(){
for(int i=0;i<pool.size();i++){
try{
((Connection) pool.get(i)).close();
}catch (SQLException e){
e.printStackTrace();
}
pool.remove(i);
}
}
public static ConnectionPool getInstance(){
if(instance ==null){
instance = new ConnectionPool();
}
return instance;
}
public synchronized Connection getConnnetion(){
if(pool.size()>0){
Connection conn = pool.get(0);
pool.remove(conn);
return conn;
}else{
return null;
}
}
private void addConnection(){
Connection conn =null;
for(int i =0;i<poolsize;i++){
try{
Class.forName(driverClassName);
conn = java.sql.DriverManager.getConnection(url,username,password);
pool.add(conn);
}catch (ClassNotFoundException e){
e.printStackTrace();
}catch (SQLException e){
e.printStackTrace();
}
}
}
private void readConfig(){
try{
String path=System.getProperty("userid")+"\\dbpool.properties";
FileInputStream is = new FileInputStream(path);
Properties props = new Properties();
this.driverClassName=props.getProperty("driverClassName");
this.username =props.getProperty("username");
this.password=props.getProperty("password");
this.url = props.getProperty("url");
this.poolsize = Integer.parseInt(props.getProperty("poolsieze"));
}catch (Exception e){
e.printStackTrace();
System.err.println("读取属性文件出错。");
}
}
}
ConnectionPool.java 说明:ConnectionPool.java文件中的Vector类型的pool对象保存所有的数据库连接,其中的readConfig()方法用来读取dbpool.properties配置文件。addConnection()方法建立数据库连接并且加入到pool对象中。release()方法用来让使用连接池的程序释放所使用的数据库连接,并把所释放的连接重新添加到连接池中,供重复使用。closePool()方法用来关闭连接池中的所有连接,当结束使用连接池的时候,可以调用这个方法。ConnectionPool类中的构造方法为私有的,不能让其他程序通过调用其构造方法生成当前类的对象,只能通过调用其静态的getInstance()方法来获取当前的Connection类型的对象,使用了设计模式中的单例模式,保证只会有一个ConnectionPool对象供使用。
ConnectionPoolTest.java 内容如下:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class ConnectionPoolTest {
public static void main(String[] args) throws Exception{
String sql ="select id,name,phone from guestbook";
long start = System.currentTimeMillis();
ConnectionPool pool = null;
for(int i=0;i<100;i++){
pool =ConnectionPool.getInstance();
Connection conn=pool.getConnnetion();
Statement stmt = conn.createStatement();
ResultSet rs=stmt.executeQuery(sql);
while(rs.next()){
}
rs.close();
stmt.close();
pool.release(conn);
}
pool.closePool();
System.out.println("经过100次得循环调用,使用连接池花费的时间:"+(System.currentTimeMillis()-start)+"ms\n");
String hostName ="localhost";
String driverClass = "oracle.jdbc.driver.OracleDriver";
String url ="jdbc:oracle:thin:@"+hostName+":1521:orcl";
String user="platemm";
String password ="platemm";
start = System.currentTimeMillis();
Class.forName(driverClass);
for(int i=0;i<100;i++){
Connection conn=DriverManager.getConnection(url,user,password);
Statement stmt = conn.createStatement();
ResultSet rs=stmt.executeQuery(sql);
while(rs.next()){
}
rs.close();
stmt.close();
conn.close();
}
System.out.println("经过100次得循环调用,不使用连接花费的时间:"+(System.currentTimeMillis()-start)+"ms/n");
}
}
ConnectionPoolTest.java 说明:ConnectionPoolTest.java中的main()方法中分别计算使用连接池获得连接和传统方式获得连接所花费的时间。
总结:此处只是完成了一个功能简单的连接池程序,在实际中使用数据库连接池,不仅需要缓冲连接对象,通常还有缓冲Statement对象。因为申请一个连接(connection)会在物理网络上建立一个用于通讯的连接,在此连接上可以申请一定数量的Statement。一般的应用中,有多少个程序调用,则会产生多少次物理连接,每个Statement单独占用一个物理连接,这是极大的资源浪费。我们可以通过资源池实现对Statement对象的缓冲,让Statement对象也资源化。这样就可以让几十、几百个Statement只占用同一个物理连接,发挥数据库的最大性能。