当前位置:文档之家› 04. 数据库连接池(DataSource)

04. 数据库连接池(DataSource)

数据库连接池(DataSource)1、概念在三层架构中,DAO层直接与数据库交互,首先要建立与数据库的连接,如果采用下图(a)所示,则用户每次的请求都要创建连接,用完又关闭,而数据库连接的创建和关闭需要消耗较大的资源,因此实际开发中常采用图(b)所示,在应用程序启动时创建一个包含多个Connection对象的连接池,DAO层使用时直接从池子里取一个Connection对象,用完后放回池子,避免了重复创建关闭数据库连接造成的开销。

2、数据库连接池原理下面的代码模拟了数据库连接池的原理(代码中的JDBCUtil工具类见《MySQL(JDBC)》),池子里保持了10个Connection对象,并提供了getConnection和release方法:public class ConnectionPoolDemo {//连接池实际上就是一个Listprivate static List<Connection> pool = new LinkedList<Connection>();static{//加载连接池类时在池子中放入10个连接for(int i = 0;i < 10;i ++){Connection conn;try {conn = JDBCUtil.getConnection();pool.add(conn);} catch (Exception e) {e.printStackTrace();}}}//从池子中取出一个连接public synchronized Connection getConnection(){return pool.remove(0);}//把连接还回池子中public static void release(Connection conn){pool.add(conn);}}3、编写一个符合规范的连接池上节模拟数据库连接池原理的代码也实现了一个简单连接池,但是不符合规范(Sun公司制定)。

编写一个符合规范的连接池需要实现javax.sql.DataSource接口。

(DataSource接口中定义了两个重载的getConnection方法)编程难点☆:当用户使用完Connection,执行conn.close()时,Connection对象应保证将自己还给连接池,而不要把conn关闭。

之所由Connection对象保证将自己返回到LinkedList 中,是因为DataSource接口中并未定义上节例子中类似release的方法。

所以必须改写Connection中的close方法,使得用户执行conn.close()时,将Connection对象还给连接池。

解决方案☆:改写驱动程序中Connection类的close方法。

对已知类的某些方法进行功能上的改变,有以下几种编码方案(☆):1)编写子类,覆写需要改变的方法。

此处行不通,原因有:①程序中不知道继承哪个驱动的Connection实现类②数据库驱动对Connection接口的实现类是final的,不允许被继承。

2)装饰(包装)设计模式(静态代理)①定义包装类:MyConnection,该类完成了对com.mysql.jdbc.Connection类的包装。

关键词:保持被包装对象的原有信息、对某个/某些方法进行改写。

包装类的编写过程如下:/*** 目前要包装的类是:com.mysql.jdbc.Connection* @author flyne*///1、编写一个类,实现与被包装类相同的接口。

public class MyConnection implements Connection {//2、定义一个变量,引用被包装类的实例(保持被包装对象的原有信息)private Connection conn;private List<Connection> pool;//close方法中需要用//3、在构造方法中传入被包装类的实例public MyConnection(Connection conn,List<Connection> pool){ this.conn = conn;this.pool = pool;}//4、对于需要改写的方法,编写自己的代码即可public void close() throws SQLException {pool.add(conn);}//5、对于不需要改写的方法,调用被包装对象的对应方法public <T> T unwrap(Class<T> iface) throws SQLException {return conn.unwrap(iface);}……//其他代码从略}②实现DataSource接口。

public class MyDataSource implements DataSource {private static List<Connection> pool = new LinkedList<Connection>();static{try {for(int i=0;i<10;i++){Connection conn = JDBCUtil.getConnection();//创建的新连接pool.add(conn);}} catch (Exception e) {e.printStackTrace();}}public Connection getConnection() throws SQLException {if(pool.size()>0){Connection conn = pool.remove(0);MyConnection mconn = new MyConnection(conn,pool);return mconn;}else{throw new RuntimeException("服务器忙");}}…… //其他代码从略}3)默认适配器Connection接口中有多个方法,如果直接实现此接口,那么需要在实现类中实现所有的方法,但往往可能只用到接口中一个或者几个方法(如上面的MyConnection类),显然用这样的实现类会造成资源的浪费,系统开销的加大。

此时可以在中间引入一个默认适配器。

①默认适配器默认适配器本身也是一个包装类,但并没有对任何的方法进行改写:public class ConnectionAdapter implements Connection {private Connection conn;public ConnectionAdapter(Connection conn){this.conn = conn;}public <T> T unwrap(Class<T> iface) throws SQLException {return this.unwrap(iface);}…… //其他代码从略}②MyConnection类的改写,此时直接继承ConnectionAdapter,并改写close()方法即可。

public class MyConnection extends ConnectionAdapter {private Connection conn;private List<Connection> pool;public MyConnection(Connection conn,List<Connection> pool) { super(conn);this.conn = conn;this.pool = pool;}public void close() throws SQLException {pool.add(conn);}}③MyDataSource类的实现同上。

4)动态代理(Proxy)利用动态代理改写上面的MyDataSource类,代码如下://在getConnection方法中返回Connection对象的代理对象,其他部分同MyDataSource 类public Connection getConnection() throws SQLException {if (pool.size() > 0) {final Connection conn = pool.remove(0);Connection connProxy = (Connection)Proxy.newProxyInstance(conn.getClass().getClassLoader(),conn.getClass().getInterfaces(),new InvocationHandler() {//此处为内部类,当close方法被调用时将conn还回池中,其它方法直接执行public Object invoke(Object proxy, Method method,Object[] args) throws Throwable {if("close".equals(method.getName())){return pool.add(conn);}else{return method.invoke(conn, args);}}});return connProxy;} else {throw new RuntimeException("服务器忙");}}注:InvocationHandler:调用处理程序。

它是一个接口,说明如何代理,是典型的策略设计模式。

有关动态代理的知识可参考动态代理。

4、开源的数据库连接池大多数Web服务器都实现了DataSource接口,如WebLogic、Tomcat,另外,一些开源组织也提供了DataSource的实现,如DBCP、C3P0和Druid(阿里巴巴)。

1)DBCP数据源(在Java Project中使用)DBCP:DataBase Connection Pool,由Apache提供实现。

本文采用配置文件的方式读取数据库连接的参数,使用步骤如下:①拷贝jar包:commons-dbcp.jar、commons-pool.jar②配置数据库连接池(src/dbcpconfig.properties)driverClassName=com.mysql.jdbc.Driverurl=jdbc:mysql://localhost:3306/mydb2username=rootpassword=initialSize=10maxActive=50minIdle=5③将使用DBCP时的共同操作封装到DBCPUtil中,便于下次调用:public class DBCPUtil {private static DataSource dataSource;static{try {InputStream in = DBCPUtil.class.getClassLoader().getResourceAsStream("dbcpconfig.prop erties");Properties props = new Properties();props.load(in);dataSource= BasicDataSourceFactory.createDataSource(props);} catch (Exception e) {throw new ExceptionInInitializerError(e);}}public static DataSource getDataSource(){return dataSource;}public static Connection getConnection() throws SQLException{ return dataSource.getConnection();}}完成上述步骤后,就可以在程序中直接使用DBCP了,下面用JUnit简单的模拟了一下:@Testpublic void test1() throws Exception{Connection conn = DBCPUtil.getConnection();System.out.println(conn.getClass().getName());conn.close();}2)C3P0数据源①导入jar包:c3p0-0.9.x.x.jar②配置数据库连接池(src/c3p0-config.xml)<?xml version="1.0"encoding="UTF-8"?><c3p0-config><default-config><property name="driverClass">com.mysql.jdbc.Driver</property><propertyname="jdbcUrl">jdbc:mysql://localhost:3306/mydb2</property><property name="user">root</property><property name="password"></property><property name="initialPoolSize">10</property><property name="acquireIncrement">5</property><property name="maxIdleTime">30</property><property name="maxPoolSize">100</property><property name="minPoolSize">10</property></default-config><named-config name="mysql"><!-- 除了设置默认配置外还可以自定义多套配置,方便切换 --><property name="xxx">……</property></named-config><named-config name="oracle"><property name="xxx">……</property></named-config></c3p0-config>③同使用DBCP一样,封装一个C3P0Util类:public class C3P0Util {private static ComboPooledDataSource dataSource= new ComboPooledDataSource();public static DataSource getDataSource(){return dataSource;}public static Connection getConnection(){try {return dataSource.getConnection();} catch(SQLException e) {throw new RuntimeException(e);}}}完成上述步骤后,就可以在程序中直接使用C3P0了,下面用JUnit简单的模拟了一下:@Testpublic void test1() throws Exception{Connection conn = C3P0Util.getConnection();System.out.println(conn.getClass().getName());conn.close();}3)使用JNDI管理数据源上面介绍的两个开源数据源可以在Java工程中使用,如果是一个Web工程,就可以直接使用服务器实现的数据源。

相关主题