Java实现数据库连接池简易教程
一、引言
池化技术在Java中应用的很广泛,简而论之,使用对象池存储某个实例数受限制的实例,开发者从对象池中获取实例,使用完之后再换回对象池,从而在一定程度上减少了系统频繁创建对象销毁对象的开销。Java线程池和数据库连接池就是典型的应用,但并非所有的对象都适合拿来池化,对于创建开销比较小的对象拿来池化反而会影响性能,因为维护对象池也需要一定的资源开销,对于创建开销较大,又频繁创建使用的对象,采用池化技术会极大提高性能。
业界有很多成熟的数据库连接池,比如C3P0,DBCP,Proxool以及阿里的Druid。很多以及开源,在GitHub可以找到源码,开发者可以根据自己的需求结合各种连接池的特点和性能进行选择。本文仅是为了了解学习池化技术,实现的一个简单的数据库连接池,如有错误,还望批评指正。
二、设计
主要类和接口
.ConnectionParam-数据库连接池参数类,负责配置数据库连接以及连接池相关参数。使用Builder实现。
driverurluserpassword-连接数据库所需
minConnection-最小连接数
maxConnection-最大连接数
minIdle-最小空闲连接数
maxWait-最长等待时间
privatefinalStringdriver; privatefinalStringurl; privatefinalStringuser; privatefinalStringpassword; privatefinalintminConnection; privatefinalintmaxConnection; privatefinalintminIdle; privatefinallongmaxWait;
.ConnectionPool-数据库连接池
ConnectionPool构造方法声明为保护,禁止外部创建,交由ConnectionPoolFactory统一管理。
ConnectionPool实现DataSource接口,重新getConnection()方法。
ConnectionPool持有两个容器-一个Queue存储空闲的Connection,另一个Vector(考虑到同步)存储正在使用的Connection。
当开发者使用数据库连接时,从Queue中获取,没有则返回空;使用完成close连接时,则放回Vector。
ConnectionPool提供了一个简单的基于minIdle和maxConnection的动态扩容机制。
privatestaticfinalintINITIAL_SIZE=5; privatestaticfinalStringCLOSE_METHOD="close"; privatestaticLoggerlogger; privateintsize; privateConnectionParamconnectionParam; privateArrayBlockingQueue<Connection>idleConnectionQueue; privateVector<Connection>busyConnectionVector;
.ConnectionPoolFactory-连接池管理类
ConnectionPoolFactory持有一个静态ConcurrentHashMap用来存储连接池对象。
ConnectionPoolFactory允许创建多个不同配置不同数据库的连接池。
开发者首次需要使用特定的名称注册(绑定)连接池,以后每次从指定的连接池获取Connection。
如果连接池不再使用,开发者可以注销(解绑)连接池。
privatestaticMap<String,ConnectionPool>poolMap=newConcurrentHashMap<>();
publicstaticConnectiongetConnection(StringpoolName)throwsSQLException{
nameCheck(poolName);
ConnectionPoolconnectionPool=poolMap.get(poolName);
returnconnectionPool.getConnection();
}
publicstaticvoidregisterConnectionPool(Stringname,ConnectionParamconnectionParam){
registerCheck(name);
poolMap.put(name,newConnectionPool(connectionParam));
}
//LetGC
publicstaticvoidunRegisterConnectionPool(Stringname){
nameCheck(name);
finalConnectionPoolconnectionPool=poolMap.get(name);
poolMap.remove(name);
newThread(newRunnable(){
@Override
publicvoidrun(){
connectionPool.clear();
}
}).start();
}
核心代码
数据库连接池核心代码在于getConnection()方法,通常,开发者处理完数据库操作后,都会调用close()方法,Connection此时应该被关闭并释放资源。而在数据库连接池中,用户调用close()方法,不应直接关闭Connection,而是要放回池中,重复使用,这里就用到Java动态代理机制,getConnection返回的并不是“真正”的Connection,而是自定义的代理类(此处使用匿名类),当用户调用close()方法时,进行拦截,放回池中。有关动态代理,可以参看另一篇博客《Java动态代理简单应用》
@Override
publicConnectiongetConnection()throwsSQLException{
try{
finalConnectionconnection=idleConnectionQueue.poll(connectionParam.getMaxWait(),TimeUnit.MILLISECONDS);
if(connection==null){
logger.info(emptyMsg());
ensureCapacity();
returnnull;
}
busyConnectionVector.add(connection);
return(Connection)Proxy.newProxyInstance(this.getClass().getClassLoader(),
newClass[]{Connection.class},newInvocationHandler(){
@Override
publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{
if(!method.getName().equals(CLOSE_METHOD)){
returnmethod.invoke(connection,args);
}else{
idleConnectionQueue.offer(connection);
busyConnectionVector.remove(connection);
returnnull;
}
}
});
}catch(InterruptedExceptione){
e.printStackTrace();
}
returnnull;
}
二、使用
首先用户构建数据库连接池参数(ConnectionParam),包括driver、url、user、password必须项,可以自定义minConnection、maxConnection等可选项,如果不设置,则使用系统默认值,这是使用Builder构建含有大量属性的好处,其中包括必须属性和可选属性。然后向ConnectionPoolFactory使用特定的名称注册连接池,最后通过调用ConnectionPoolFactory静态工厂方法获取Connection。
Stringdriver="com.mysql.jdbc.Driver";
Stringurl="jdbc:mysql://localhost:3306/test";
Stringuser="root";
Stringpassword="root";
ConnectionParamconnectionParam=newConnectionParam.ConnectionParamBuilder(driver,url,user,password).build();
ConnectionPoolFactory.registerConnectionPool("test",connectionParam);
Connectionconnection=ConnectionPoolFactory.getConnection("test");
三、代码
.ParamConfiguration
packagedatabase.config;
importjava.io.Serializable;
/**
*DataBaseConnectionParameters
*CreatedbyMichaelWongon2016/1/18.
*/
publicclassParamConfigurationimplementsSerializable{
publicstaticfinalintMIN_CONNECTION=5;
publicstaticfinalintMAX_CONNECTION=50;
publicstaticfinalintMIN_IDLE=5;
publicstaticfinallongMAX_WAIT=30000;
privateParamConfiguration(){}
}
.Builder
packagedatabase;
/**
*Builder
*CreatedbyMichaelWongon2016/1/18.
*/
publicinterfaceBuilder<T>{
Tbuild();
}
.ConnectionParam
packagedatabase;
importdatabase.config.ParamConfiguration;
/**
*DataBaseConnectionParameters
*CreatedbyMichaelWongon2016/1/18.
*/
publicclassConnectionParam{
privatefinalStringdriver;
privatefinalStringurl;
privatefinalStringuser;
privatefinalStringpassword;
privatefinalintminConnection;
privatefinalintmaxConnection;
privatefinalintminIdle;
privatefinallongmaxWait;
privateConnectionParam(ConnectionParamBuilderbuilder){
this.driver=builder.driver;
this.url=builder.url;
this.user=builder.user;
this.password=builder.password;
this.minConnection=builder.minConnection;
this.maxConnection=builder.maxConnection;
this.minIdle=builder.minIdle;
this.maxWait=builder.maxWait;
}
publicStringgetDriver(){
returnthis.driver;
}
publicStringgetUrl(){
returnthis.url;
}
publicStringgetUser(){
returnthis.user;
}
publicStringgetPassword(){
returnthis.password;
}
publicintgetMinConnection(){
returnthis.minConnection;
}
publicintgetMaxConnection(){
returnthis.maxConnection;
}
publicintgetMinIdle(){
returnthis.minIdle;
}
publiclonggetMaxWait(){
returnthis.maxWait;
}
publicstaticclassConnectionParamBuilderimplementsBuilder<ConnectionParam>{
//Requiredparameters
privatefinalStringdriver;
privatefinalStringurl;
privatefinalStringuser;
privatefinalStringpassword;
//Optionalparameters-initializedtodefaultvalue
privateintminConnection=ParamConfiguration.MIN_CONNECTION;
privateintmaxConnection=ParamConfiguration.MAX_CONNECTION;
privateintminIdle=ParamConfiguration.MIN_IDLE;
//GettingConnectionwaittime
privatelongmaxWait=ParamConfiguration.MAX_WAIT;
publicConnectionParamBuilder(Stringdriver,Stringurl,Stringuser,Stringpassword){
this.driver=driver;
this.url=url;
this.user=user;
this.password=password;
}
publicConnectionParamBuilderminConnection(intminConnection){
this.minConnection=minConnection;
returnthis;
}
publicConnectionParamBuildermaxConnection(intmaxConnection){
this.maxConnection=maxConnection;
returnthis;
}
publicConnectionParamBuilderminIdle(intminIdle){
this.minIdle=minIdle;
returnthis;
}
publicConnectionParamBuildermaxWait(intmaxWait){
this.maxWait=maxWait;
returnthis;
}
@Override
publicConnectionParambuild(){
returnnewConnectionParam(this);
}
}
}
.ConnectionPool
packagedatabase.factory;
importdatabase.ConnectionParam;
importjavax.sql.DataSource;
importjava.io.PrintWriter;
importjava.lang.reflect.InvocationHandler;
importjava.lang.reflect.Method;
importjava.lang.reflect.Proxy;
importjava.sql.Connection;
importjava.sql.DriverManager;
importjava.sql.SQLException;
importjava.sql.SQLFeatureNotSupportedException;
importjava.util.Vector;
importjava.util.concurrent.ArrayBlockingQueue;
importjava.util.concurrent.TimeUnit;
importjava.util.logging.Logger;
/**
*ConnectionPool
*CreatedbyMichaelWongon2016/1/18.
*/
publicclassConnectionPoolimplementsDataSource{
privatestaticfinalintINITIAL_SIZE=5;
privatestaticfinalStringCLOSE_METHOD="close";
privatestaticLoggerlogger;
privateintsize;
privateConnectionParamconnectionParam;
privateArrayBlockingQueue<Connection>idleConnectionQueue;
privateVector<Connection>busyConnectionVector;
protectedConnectionPool(ConnectionParamconnectionParam){
this.connectionParam=connectionParam;
intmaxConnection=connectionParam.getMaxConnection();
idleConnectionQueue=newArrayBlockingQueue<>(maxConnection);
busyConnectionVector=newVector<>();
logger=Logger.getLogger(this.getClass().getName());
initConnection();
}
privatevoidinitConnection(){
intminConnection=connectionParam.getMinConnection();
intinitialSize=INITIAL_SIZE<minConnection?minConnection:INITIAL_SIZE;
try{
Class.forName(connectionParam.getDriver());
for(inti=0;i<initialSize+connectionParam.getMinConnection();i++){
idleConnectionQueue.put(newConnection());
size++;
}
}catch(Exceptione){
thrownewExceptionInInitializerError(e);
}
}
@Override
publicConnectiongetConnection()throwsSQLException{
try{
finalConnectionconnection=idleConnectionQueue.poll(connectionParam.getMaxWait(),TimeUnit.MILLISECONDS);
if(connection==null){
logger.info(emptyMsg());
ensureCapacity();
returnnull;
}
busyConnectionVector.add(connection);
return(Connection)Proxy.newProxyInstance(this.getClass().getClassLoader(),
newClass[]{Connection.class},newInvocationHandler(){
@Override
publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{
if(!method.getName().equals(CLOSE_METHOD)){
returnmethod.invoke(connection,args);
}else{
idleConnectionQueue.offer(connection);
busyConnectionVector.remove(connection);
returnnull;
}
}
});
}catch(InterruptedExceptione){
e.printStackTrace();
}
returnnull;
}
privateConnectionnewConnection()throwsSQLException{
Stringurl=connectionParam.getUrl();
Stringuser=connectionParam.getUser();
Stringpassword=connectionParam.getPassword();
returnDriverManager.getConnection(url,user,password);
}
protectedintsize(){
returnsize;
}
protectedintidleConnectionQuantity(){
returnidleConnectionQueue.size();
}
protectedintbusyConnectionQuantity(){
returnbusyConnectionVector.size();
}
privatevoidensureCapacity()throwsSQLException{
intminIdle=connectionParam.getMinIdle();
intmaxConnection=connectionParam.getMaxConnection();
intnewCapacity=size+minIdle;
newCapacity=newCapacity>maxConnection?maxConnection:newCapacity;
intgrowCount=0;
if(size<newCapacity){
try{
for(inti=0;i<newCapacity-size;i++){
idleConnectionQueue.put(newConnection());
growCount++;
}
}catch(InterruptedExceptione){
e.printStackTrace();
}
}
size=size+growCount;
}
protectedvoidclear(){
try{
while(size-->0){
Connectionconnection=idleConnectionQueue.take();
connection.close();
}
}catch(InterruptedException|SQLExceptione){
e.printStackTrace();
}
}
privateStringemptyMsg(){
return"Databaseisbusy,pleasewait...";
}
@Override
publicConnectiongetConnection(Stringusername,Stringpassword)throwsSQLException{
returnnull;
}
@Override
publicPrintWritergetLogWriter()throwsSQLException{
returnnull;
}
@Override
publicvoidsetLogWriter(PrintWriterout)throwsSQLException{
}
@Override
publicvoidsetLoginTimeout(intseconds)throwsSQLException{
}
@Override
publicintgetLoginTimeout()throwsSQLException{
return0;
}
@Override
publicLoggergetParentLogger()throwsSQLFeatureNotSupportedException{
returnnull;
}
@Override
public<T>Tunwrap(Class<T>iface)throwsSQLException{
returnnull;
}
@Override
publicbooleanisWrapperFor(Class<?>iface)throwsSQLException{
returnfalse;
}
}
.ConnectionPoolFactory
packagedatabase.factory;
importdatabase.ConnectionParam;
importjava.sql.Connection;
importjava.sql.SQLException;
importjava.util.Map;
importjava.util.concurrent.ConcurrentHashMap;
/**
*ConnectionPoolFactory
*CreatedbyMichaelWongon2016/1/18.
*/
publicclassConnectionPoolFactory{
privateConnectionPoolFactory(){}
privatestaticMap<String,ConnectionPool>poolMap=newConcurrentHashMap<>();
publicstaticConnectiongetConnection(StringpoolName)throwsSQLException{
nameCheck(poolName);
ConnectionPoolconnectionPool=poolMap.get(poolName);
returnconnectionPool.getConnection();
}
publicstaticvoidregisterConnectionPool(Stringname,ConnectionParamconnectionParam){
registerCheck(name);
poolMap.put(name,newConnectionPool(connectionParam));
}
//LetGC
publicstaticvoidunRegisterConnectionPool(Stringname){
nameCheck(name);
finalConnectionPoolconnectionPool=poolMap.get(name);
poolMap.remove(name);
newThread(newRunnable(){
@Override
publicvoidrun(){
connectionPool.clear();
}
}).start();
}
publicstaticintsize(StringpoolName){
nameCheck(poolName);
returnpoolMap.get(poolName).size();
}
publicstaticintgetIdleConnectionQuantity(StringpoolName){
nameCheck(poolName);
returnpoolMap.get(poolName).idleConnectionQuantity();
}
publicstaticintgetBusyConnectionQuantity(StringpoolName){
nameCheck(poolName);
returnpoolMap.get(poolName).busyConnectionQuantity();
}
privatestaticvoidregisterCheck(Stringname){
if(name==null){
thrownewIllegalArgumentException(nullName());
}
}
privatestaticvoidnameCheck(Stringname){
if(name==null){
thrownewIllegalArgumentException(nullName());
}
if(!poolMap.containsKey(name)){
thrownewIllegalArgumentException(notExists(name));
}
}
privatestaticStringnullName(){
return"Poolnamemustnotbenull";
}
privatestaticStringnotExists(Stringname){
return"Connectionpoolnamed"+name+"doesnotexists";
}
}
四、测试
JUnit单元测试
packagedatabase.factory;
importdatabase.ConnectionParam;
importorg.junit.Test;
importjava.sql.Connection;
importjava.sql.SQLException;
importjava.util.ArrayList;
importjava.util.List;
importstaticorg.junit.Assert.*;
/**
*ConnectionPoolFactoryTest
*CreatedbyMichaelWongon2016/1/20.
*/
publicclassConnectionPoolFactoryTest{
@Test
publicvoidtestGetConnection()throwsSQLException{
Stringdriver="com.mysql.jdbc.Driver";
Stringurl="jdbc:mysql://localhost:3306/test";
Stringuser="root";
Stringpassword="root";
ConnectionParamconnectionParam=newConnectionParam.ConnectionParamBuilder(driver,url,user,password).build();
ConnectionPoolFactory.registerConnectionPool("test",connectionParam);
List<Connection>connectionList=newArrayList<>();
for(inti=0;i<12;i++){
connectionList.add(ConnectionPoolFactory.getConnection("test"));
}
print();
close(connectionList);
print();
connectionList.clear();
for(inti=0;i<12;i++){
connectionList.add(ConnectionPoolFactory.getConnection("test"));
}
print();
close(connectionList);
ConnectionPoolFactory.unRegisterConnectionPool("test");
}
@Test(expected=IllegalArgumentException.class)
publicvoidtestException(){
try{
ConnectionPoolFactory.getConnection("test");
}catch(SQLExceptione){
e.printStackTrace();
}
}
privatevoidclose(List<Connection>connectionList)throwsSQLException{
for(Connectionconn:connectionList){
if(conn!=null){
conn.close();
}
}
}
privatevoidprint(){
System.out.println("idle:"+ConnectionPoolFactory.getIdleConnectionQuantity("test"));
System.out.println("busy:"+ConnectionPoolFactory.getBusyConnectionQuantity("test"));
System.out.println("size:"+ConnectionPoolFactory.size("test"));
}
}
以上就是本文的全部内容,希望对大家的学习有所帮助。