一、直接获取数据库连接和通过池获取示意图:
二、编写数据库连接池
1、实现DataSource接口,并实现连接池功能的步骤:
•在DataSource构造函数中批量创建与数据库的连接,并把创建的连接加入LinkedList对象中.
•实现getConnection方法,让getConnection方法每次调用时,从LinkedList中取一个Connection返回给用户.
•当用户使用完Connection,调用Connection.close()方法时,Collection对象应保证将自己返回到LinkedList中,而不要把conn还给数据库.
•Collection保证将自己返回到LinkedList中是此处编程的难点.
2、通过DataSource来取代 DriverManager来获取Connection,像这样内存级别的操作相对于创建连接走公网来说速度快的多,相当于将时间转移到了服务器启动的时候,可以大幅度提高数据库的访问速度(一个请求最大的时间消耗在连接的建立上),通过DataSource获得的Connection都是已经被包裹过的(不是驱动原来的连接),他的close方法已经被修改.我们的程序只和DataSource打交道,不会直接访问连接池.
3、装饰模式实现连接池代码如下:
//自己实现连接池 public class JdbcPool implements DataSource { private static LinkedList<Connection> list = new LinkedList<Connection>(); private static Properties config = new Properties(); static{ try { config.load(JdbcUtils_DBCP.class.getClassLoader().getResourceAsStream("db.properties")); Class.forName(config.getProperty("driver")); for(int i=0;i<10;i++){ Connection conn = DriverManager.getConnection(config.getProperty("url"), config.getProperty("username"), config.getProperty("password")); list.add(conn); } } catch (Exception e) { throw new ExceptionInInitializerError(e); } } //conn.close(),不满足需求,为了保持用户关闭连接的习惯 /* 在实际开发,发现对象的方法满足不了开发需求时,有三种方式对其进行增强 * 1.写一个connecton子类,覆盖close方法,增强close方法 * 2.用包装设计模式,MyConnection也可以叫代理 * 3.用动态代理 aop 面向切面编程 */ //要加锁,否则多线程情况下不同请求会拿到相同连接 public synchronized Connection getConnection() throws SQLException { if(list.size()<=0){ throw new RuntimeException("数据库忙,请稍会再来!!"); } Connection conn = list.removeFirst(); //list.get()不行 MyConnection my = new MyConnection(conn); return my; } //1.定义一个类,实现与被增强相同的接口 //2.在类中定义一个变量,记住被增强对象 //3.定义一个构造函数,接收被增强对象 //4.覆盖想增强的方法 //5.对于不想增强的方法,直接调用目标对象(被增强对象)的方法 class MyConnection implements Connection{ private Connection conn; public MyConnection(Connection conn){ this.conn = conn; } public void close(){ list.add(this.conn); } .... } public static void release(Connection conn,Statement st,ResultSet rs){ if(rs!=null){ try{ rs.close(); }catch (Exception e) { e.printStackTrace(); } rs = null; } if(st!=null){ try{ st.close(); }catch (Exception e) { e.printStackTrace(); } st = null; } if(conn!=null){ try{ conn.close(); }catch (Exception e) { e.printStackTrace(); } } } }
4、使用动态代理模式实现连接池代码,将getConnection()改为如下:
public synchronized Connection getConnection() throws SQLException { if(list.size()<=0){ throw new RuntimeException("数据库忙,请稍会再来!!"); } Connection conn = list.removeFirst(); //list.get()不行 return new MyConnectionHandler(conn,this).getWarpConn(); }
class MyConnectionHandler implements InvocationHandler { private Connection realConnection; private Connection warpedConnection; private MyDataSource dataSource; private int maxUseCount = 5; private int currentUserCount = 0; MyConnectionHandler(Connection conn,MyDataSource dataSource) { this.realConnection=conn; this.dataSource = dataSource; } Connection getWarpConn() { warpedConnection = (Connection) Proxy.newProxyInstance(this .getClass().getClassLoader(), new Class[] { Connection.class },this); return warpedConnection; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("close".equals(method.getName())) { currentUserCount++; if (currentUserCount < maxUseCount) dataSource.connectionsPool.addLast(warpedConnection); else { realConnection.close(); dataSource.currentCount--; } } return method.invoke(realConnection, args); } }
三、开源数据库连接池
1、现在很多WEB服务器(Weblogic, WebSphere, Tomcat)都提供了DataSoruce的实现,即连接池的实现.通常我们把DataSource的实现,按其英文含义称之为数据源,数据源中都包含了数据库连接池的实现.
2、也有一些开源组织提供了数据源的独立实现:
•DBCP 数据库连接池
•C3P0 数据库连接池
3、实际应用时不需要编写连接数据库代码,直接从数据源获得数据库的连接.程序员编程时也应尽量使用这些数据源的实现,以提升程序的数据库访问性能.
DBCP数据源&C3P0数据源
1、DBCP 是 Apache 软件基金组织下的开源连接池实现,使用DBCP数据源,应用程序应在系统中增加如下两个 jar 文件:
•Commons-dbcp.jar:连接池的实现
•Commons-pool.jar:连接池实现的依赖库
Tomcat 的连接池正是采用该连接池来实现的.该数据库连接池既可以与应用服务器整合使用,也可由应用程序独立使用.
2、使用DBCP数据源后,Jdbcutils改为如下:
public class JdbcUtils_DBCP { private static DataSource ds = null; static{ try{ InputStream in = JdbcUtils_DBCP.class.getClassLoader().
getResourceAsStream("dbcpconfig.properties"); Properties prop = new Properties(); prop.load(in); BasicDataSourceFactory factory = new BasicDataSourceFactory(); ds = factory.createDataSource(prop); }catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException{ return ds.getConnection(); } public static void release(Connection conn,Statement st,ResultSet rs){ if(rs!=null){ try{ rs.close(); }catch (Exception e) { e.printStackTrace(); } rs = null; } if(st!=null){ try{ st.close(); }catch (Exception e) { e.printStackTrace(); } st = null; } if(conn!=null){ try{ conn.close(); }catch (Exception e) { e.printStackTrace(); } } } }
#连接设置 driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/jdbc username=root password= #<!-- 初始化连接 --> initialSize=10 #最大连接数量 maxActive=50 #<!-- 最大空闲连接 --> maxIdle=20 #<!-- 最小空闲连接 --> minIdle=5 #<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 --> maxWait=60000 #JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;] #注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们. connectionProperties=useUnicode=true;characterEncoding=gbk #指定由连接池所创建的连接的自动提交(auto-commit)状态. defaultAutoCommit=true #driver default 指定由连接池所创建的连接的只读(read-only)状态. #如果没有设置该值,则"setReadOnly"方法将不被调用.(某些驱动并不支持只读模式,如:Informix) defaultReadOnly= #driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation). #可用值为下列之一:(详情可见javadoc.)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE defaultTransactionIsolation=READ_UNCOMMITTED
3、使用c3p0数据源后,Jdbcutils改为如下:
public class JdbcUtils_C3P0 { private static ComboPooledDataSource ds = null; static{ try{ ds = new ComboPooledDataSource("mysql");可通过名称获取那个配置 }catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException{ return ds.getConnection(); } public static void release(Connection conn,Statement st,ResultSet rs){ if(rs!=null){ try{ rs.close(); }catch (Exception e) { e.printStackTrace(); } rs = null; } if(st!=null){ try{ st.close(); }catch (Exception e) { e.printStackTrace(); } st = null; } if(conn!=null){ try{ conn.close(); }catch (Exception e) { e.printStackTrace(); } } } }
c3p0-config.xml配置如下,必须是这个名称,服务器会自动搜索.
<c3p0-config> <default-config> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/day16</property> <property name="user">root</property> <property name="password">root</property> <property name="initialPoolSize">10</property> <property name="maxIdleTime">30</property> <property name="maxPoolSize">20</property> <property name="minPoolSize">5</property> <property name="maxStatements">200</property> </default-config> <named-config name="mysql"> <property name="acquireIncrement">50</property> <property name="initialPoolSize">100</property> <property name="minPoolSize">50</property> <property name="maxPoolSize">1000</property> <property name="maxStatements">0</property> <property name="maxStatementsPerConnection">5</property> </named-config> <named-config name="oracle"> <property name="acquireIncrement">50</property> <property name="initialPoolSize">100</property> <property name="minPoolSize">50</property> <property name="maxPoolSize">1000</property> <property name="maxStatements">0</property> <property name="maxStatementsPerConnection">5</property> </named-config> </c3p0-config>
4、配置Tomcat数据源(tomcat自带有连接池),查看Tomcat文档,示例代码:
<Context> <Resource name="jdbc/EmployeeDB" auth="Container" type="javax.sql.DataSource" username="root" password="root" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/day16" initialSize="10" maxActive="30" maxIdle="4"/> </Context>
public class JdbcUtils_Tomcat { private static DataSource ds; static { try { Context initCtx = new InitialContext(); Context envCtx = (Context) initCtx.lookup("java:comp/env"); ds = (DataSource) envCtx.lookup("jdbc/EmployeeDB"); } catch (Exception e) { throw new RuntimeException(e); } } public static Connection getConnection() throws SQLException{ return ds.getConnection(); } }
ps:此种配置可以在tomcat server.xml文件中或者是MATA-INF下新建context.xml文件都可以(可以参考apache提供的文档),其实这种用法是将datasource配置(实际就是一个对象)放在了JNDI容器中,这种情况下驱动的jar文件需放置在tomcat的lib下.
四、JNDI技术简介
1.JNDI(Java Naming and Directory Interface),Java命名和目录接口,它对应于J2SE中的javax.naming包,这套API的主要作用在于:它可以把Java对象放在一个容器中(JNDI容器),并为容器中的java对象取一个名称,以后程序想获得Java对象,只需通过名称检索即可.
2、其核心API为Context,它代表JNDI容器,其lookup方法为检索容器中对应名称的对象.
ps:举个例子,Servlet中要用到request对象,可以将它作为方法参数传递进去,也可以将它放在JNDI容器中.3、JNDI容器图示:
五、各种连接池性能比较
测试执行申请归还连接1,000,000次总耗时性能对比.
Java6 Environment
OS | OS X 10.8.2 |
CPU | intel i7 2GHz 4 core |
JVM | java version "1.6.0_37" |
Java6 Benchmark Result
Jdbc Connection Pool | 1 thread | 2 threads | 5 threads | 10 threads | 20 threads | 50 threads |
Druid | 1,102 | 1,509 | 1,889 | 1,904 | 2,027 | 1,977 |
tomcat-jdbc | 1,399 | 1,378 | 2,257 | 2,289 | 2,305 | 2,503 |
DBCP | 3,144 | 3,834 | 6,276 | 6,408 | 6,563 | 6,783 |
BoneCP | 4,327 | 3,598 | 3,800 | 5,242 | 9,402 | 19,066 |
jboss-datasource | 4,912 | 3,049 | 6,868 | 6,512 | 40,146 | 43,748 |
C3P0 | 18,570 | 19,467 | 15,270 | 19,294 | 28,195 | 66,677 |
Proxool | 16,221 | 14,455 | 24,688 | 38,905 | 48,087(Exception) | 58,238(Exception) |
Java7 Environment
OS | OS X 10.8.2 |
CPU | intel i7 2GHz 4 core |
JVM | java version "1.7.0_05" |
Java7 Benchmark Result
Jdbc Connection Pool | 1 thread | 2 threads | 5 threads | 10 threads | 20 threads | 50 threads |
Druid | 898 | 1,191 | 1,324 | 1,362 | 1,325 | 1,459 |
tomcat-jdbc | 1,269 | 1,378 | 2,029 | 2,103 | 1,879 | 2,025 |
DBCP | 2,324 | 5,055 | 5,446 | 5,471 | 5,524 | 5,415 |
BoneCP | 3,738 | 3,150 | 3,194 | 5,681 | 11,018 | 23,125 |
jboss-datasource | 4,377 | 2,988 | 3,680 | 3,980 | 32,708 | 37,742 |
C3P0 | 10,841 | 13,637 | 10,682 | 11,055 | 14,497 | 20,351 |
Proxool | 16,337 | 16,187 | 18,310(Exception) | 25,945 | 33,706(Exception) | 39,501 (Exception) |
结论
- Druid是性能最好的数据库连接池,tomcat-jdbc和druid性能接近.
- proxool在激烈并发时会抛异常,完全不靠谱.
- c3p0和proxool都相当慢,慢到影响sql执行效率的地步.
- bonecp性能并不优越,采用LinkedTransferQueue并没有能够获得性能提升.
- 除了bonecp,其他的在JDK 7上跑得比JDK 6上快
- jboss-datasource虽然稳定,但是性能很糟糕