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")); } }
以上就是本文的全部内容,希望对大家的学习有所帮助。