前面一节我们介绍了如何利用 jdbc 连接数据库,已经实现了数据库的连接,但是在实际的项目开发中,可以发现基本上都使用了数据库连接池技术,为什么要使用数据库连接池呢?根源在于对数据库连接的低效管理 答: 普通的 JDBC 数据库连接,用户请求一次查询的时候就会向数据库发起一次连接,执行完后就断开连接,这样的方式会消耗大量的资源和时间,数据库的连接资源并没有得到很好的重复利用。若是同时有几百人甚至几千人在线,频繁地进行数据库连接操作,这将会占用很多的系统资源,严重的甚至会造成服务器的奔溃。这样频繁的创建销毁数据库连接十分耗费时间和资源,而且开发者也不能很好的控制数据库的连接数,有可能因为分配的连接过多而导致内存耗尽。
数据库连接池的实现原理解析,以及设计连接池时需要考虑的因素
- try{connect.setAutoCommit(false);
- ........ 进行的数据库操作语句connect.commit();
- }catch{connect.rollback();
- }finally{connect.close();
- }
但是当2个线程共用一个连接 Connection 对象,而且各自都有自己的事务要处理时候,对于连接池是一个很头疼的问题,因为即使 Connection 类提供了相应的事务支持,可是我们仍然不能确定那个数据库操作是对应那个事务的,这是由于我们有2个线程都在进行事务操作而引起的。但要高效的进行 Connection 复用,就必须提供相应的事务支持机制。可采用每一个事务独占一个连接来实现,虽然这种方法有点浪费连接池资源,但是可以大大降低事务管理的复杂性。 4. 连接池的分配与释放 合理的分配与释放,可以提高连接的复用度,从而降低建立新连接的开销,同时还可以加快用户的访问速度。 对于连接的管理可使用空闲池。即把已经创建但尚未分配出去的连接按创建时间存放到一个空闲池中。每当用户请求一个连接时,系统首先检查空闲池内有没有空闲连接。如果有就把建立时间最长(通过容器的顺序存放实现)的那个连接分配给他(实际是先做连接是否有效的判断,如果可用就分配给用户,如不可用就把这个连接从空闲池删掉,重新检测空闲池是否还有连接);如果没有则检查当前所开连接池是否达到连接池所允许的最大连接数(maxconn)如果没有达到,就新建一个连接,如果已经达到,就等待一定的时间(timeout)。如果在等待的时间内有连接被释放出来就可以把这个连接分配给等待的用户,如果等待时间超过预定时间 timeout 则返回空值(null)。系统对已经分配出去正在使用的连接只做计数,当使用完后再返还给空闲池,但是会发生无法对正在使用的连接进行管理的状况,所以建议使用一个链表存储。对于空闲连接的状态,可开辟专门的线程定时检测,这样会花费一定的系统开销,但可以保证较快的响应速度。也可采取不开辟专门线程,只是在分配前检测的方法
再分配、释放策略对于有效复用连接非常重要,引用记数模式在复用资源方面用的非常广泛,每一个数据库连接,保留一个引用记数,用来记录该连接的使用者的个数,我们对 Connection 类进行进一步包装来实现引用记数,确定当前被引用多少,具体是哪个用户引用了该连接将在连接池中登记,一旦一个连接被分配出去,那么就会对该连接的申请者进行登记,并且增加引用记数,当被释放回来时候就删除他已经登记的信息,同时减少一次引用记数
5、连接池的配置与维护 连接池中到底应该放置多少连接,才能使系统的性能最佳?系统可采取设置最小连接数(minconn)和最大连接数(maxconn)来控制连接池中的连接。最小连接数是系统启动时连接池所创建的连接数。如果创建过多,则系统启动就慢,但创建后系统的响应速度会很快;如果创建过少,则系统启动的很快,响应起来却慢。这样,可以在开发时,设置较小的最小连接数,开发起来会快,而在系统实际使用时设置较大的,因为这样对访问客户来说速度会快些。最大连接数是连接池中允许连接的最大数目,具体设置多少,要看系统的访问量,可通过反复测试,找到最佳点。 如何确保连接池中的最小连接数呢?有动态和静态两种策略。动态即每隔一定时间就对连接池进行检测,如果发现连接数量小于最小连接数,则补充相应数量的新连接以保证连接池的正常运转。静态是发现空闲连接不够时再去检查。
自己实现一个简易的数据库连接池,只考虑了连接池的一些方面
// 连接池类 我在设计的时候需要考虑的几点 1. 首先是数据库连接的存储,要能很容易的管理和获取数据库连接,将数据库连接分为两部分,一部分是空闲池,一部分是正在使用的数据库连接, 使用 LinkedList 实现栈来存储空闲的数据库连接,(优势在于每一次获取到的连接都是新的连接,这样的连接基本上都是可用的,基本上不会发生连接不可用导致重新再去获取连接的操作), 使用 LinkedList 实现队列来存储正在使用中数据库连接(优势在于,队列的头部就是目前使用时间最长的连接,方便进行检查,回收这个使用时间超过限制的数据库连接) 2. 如何回收分配出去的连接,即当外部的连接调用了 close 方法之后,如何让它返回到数据库连接池中,而不是销毁? 方法: 使用动态代理, 当请求一个 Connection 时,返回用户一个代理的 Connection 对象,这样就可以对 close 方法进行拦截,调用了 close 方法,会自动执行代理类中的 invoke 方法, 在 invoke 方法里面就可以实现对实际连接的一些操作了, 具体实现请查看 getConnection() 方法。 3. 其实获取连接的时候应当首先检查空闲池是否有空闲连接,再检查空闲连接是否可用,当数据库连接池没有连接的时候,要进行一次性创建新的连接,同时要进行检查看是否能进行连接的创建,是否达到了最大值等, 所以数据库的一些配置属性需要在静态代码块中通过 Properties 类读取出来。
- public class MyDatabasePool{
- privateLinkedList idlelist; // 使用LinkedList实现栈存储数据库连接,存放的空闲连接
- privateLinkedList usinglist; // 使用LinkedList实现队列存储数据库连接,存放的正在使用的连接
- private staticProperties props;// 读取配置文件信息
- private static intinitialPoolSize;// 初始连接池大小
- private static intmaxPoolSize;// 连接池最大连接数
- private static intacquireIncrement;// 无连接时,一次性创建连接数
- static{
- props =newProperties();try{
- props.load(newFileInputStream("myPool.properties"));
- initialPoolSize = Integer.parseInt(props
- .getProperty("initialPoolSize"));
- maxPoolSize = Integer.parseInt(props.getProperty("maxPoolSize"));
- acquireIncrement = Integer.parseInt(props
- .getProperty("acquireIncrement"));
- }catch(IOException e) {// TODO Auto-generated catch blocke.printStackTrace();
- }
- }// 构造函数,在数据库连接池里先创建几个连接
- // 我看了一下c3p0的源码,里面是用的代理连接 new ,而不是真实的物理连接
- public MyDatabasePool()throwsClassNotFoundException, SQLException {
- idlelist =newLinkedList();
- usinglist = newLinkedList();
- Class.forName(props.getProperty("MySQLdriverClass"));for(inti =0; i < initialPoolSize; i++) {
- Connection conn = DriverManager.getConnection(
- props.getProperty("MySQLurl"),
- props.getProperty("MySQLusername"),
- props.getProperty("MySQLpassword"));
- idlelist.addLast(conn);
- }
- }// 获取数据库连接
- publicConnectiongetConnection()throwsSQLException {if(idlelist.size() >0) {
- usinglist.addFirst(idlelist.getLast());// 只是获取第一个连接并没有删除Connection conn = idlelist.removeLast();// 获取第一个连接并删除
- // return conn; //返回真实的物理连接
- // 返回一个真实物理连接的动态代理连接对象
- return(Connection) Proxy.newProxyInstance(ProxyConnection.class
- .getClassLoader(),newClass[] { Connection.class },newProxyConnection(conn));
- }else{// 创建新的数据库连接
- booleanflag = dynamicIncrement();if(flag) {
- usinglist.add(idlelist.getLast());// 只是获取第一个连接并没有删除Connection conn = idlelist.removeLast();// 获取第一个连接并删除
- // return conn; //返回真实的物理连接
- // 返回一个真实物理连接的动态代理连接对象
- return(Connection) Proxy.newProxyInstance(
- ProxyConnection.class.getClassLoader(),newClass[] { Connection.class },newProxyConnection(
- conn));
- }else{throw newSQLException("没连接了");
- }
- }
- }// 连接池里无连接,动态增长
- private boolean dynamicIncrement()throwsSQLException {intnum = idlelist.size() + usinglist.size();intnum2 = maxPoolSize - num;// 如果可以创建连接,而且创建的连接数就是acquireIncrement
- if(num2 >= acquireIncrement) {for(inti =0; i < acquireIncrement; i++) {
- Connection conn = DriverManager.getConnection(
- props.getProperty("MySQLurl"),
- props.getProperty("MySQLusername"),
- props.getProperty("MySQLpassword"));
- idlelist.addLast(conn);
- }return true;
- }// 如果可以创建连接,但是创建的连接数只能是num2个
- if(num2 >0) {for(inti =0; i < num2; i++) {
- Connection conn = DriverManager.getConnection(
- props.getProperty("MySQLurl"),
- props.getProperty("MySQLusername"),
- props.getProperty("MySQLpassword"));
- idlelist.addLast(conn);
- }return true;
- }return false;
- }// Connection的动态代理类class ProxyConnection implements InvocationHandler {privateConnection conn;public ProxyConnection(Connection conn) {this.conn = conn;
- }// 关闭数据库连接,放回到空闲池中
- @Override
- publicObjectinvoke(Object proxy, Method method, Object[] args)throwsThrowable {// TODO Auto-generated method stub
- // 分配出去的代理连接调用了close方法,进行拦截,实现我们自己想要的操作
- if(method.getName().equals("close")) {// conn.close(); // 这一句的话就直接关闭连接了,所以不写
- // 应该事先的操作是将 conn 放到空闲池中去,从使用池中移除System.out.println(idlelist.size());
- System.out.println(usinglist.size());
- idlelist.addLast(conn);
- usinglist.remove(conn);
- System.out.println(idlelist.size());
- System.out.println(usinglist.size());return null;
- }// 其他方法仍然调用真实对象的方法
- returnmethod.invoke(conn, args);
- }
- }
- }
配置文件 myPool.properties
- # mysql database driver
- MySQLdriverClass=com.mysql.jdbc.DriverMySQLurl=jdbc:mysql://127.0.0.1/test?useSSL=falseMySQLusername=rootMySQLpassword=rootinitialPoolSize=3minPoolSize=2maxPoolSize=50acquireIncrement=3
JDBC 的 API 中没有提供连接池的方法,所以可以使用如下常见的几种数据库连接池: C3P0: C3P0 是一个开源的 JDBC 连接池,支持 JDBC3 规范和 JDBC2 的标准扩展。c3p0 是异步操作的,缓慢的 JDBC 操作通过帮助进程完成。扩展这些操作可以有效的提升性能。目前使用它的开源项目有 Hibernate,Spring 等。c3p0 有自动回收空闲连接功能,稳定性好,大并发量的压力下稳定性也有一定的保证 无连接池监控 c3p0 所需 jar: c3p0-0.9.2.1.jar mchange-commons-java-0.2.3.4.jar
DBCP 是 apache 上的一个 java 连接池项目,也是 tomcat 使用的连接池组件,连接池的基本功能都有,一般不建议使用,无连接池监控 使用 dbcp 需要 2 个包: commons-dbcp.jar commons-pool.jar
Proxool Proxool 是一种 Java 数据库连接池技术。Sourceforge 下的一个开源项目, 这个项目提供一个健壮、易用的连接池,最为关键的是这个连接池提供监控数据库连接的功能,方便易用,便于发现连接泄漏的情况。
Druid Druid 是阿里巴巴开源平台上的一个项目,整个项目由数据库连接池、插件框架和 SQL 解析器组成。该项目主要是为了扩展 JDBC 的一些限制,可以让程序员实现一些特殊的需求,比如向密钥服务请求凭证、统计 SQL 信息、SQL 性能收集、SQL 注入检查、SQL 翻译等,程序员可以通过定制来实现自己需要的功能。
利用 C3PO 进行配置实现数据库连接池的使用: 第一步: 导入 jar 包 c3p0-0.9.2.1.jar 和 mchange-commons-java-0.2.3.4.jar 第二步: 书写配置文件 c3p0-config.xml; 里面有一个参数是需要注意的,可以详细看看下面的配置文件 注意的是: 1. 文件名必须为 c3p0-config.xml, 这是因为 C3P0 会默认读取文件名为 c3p0-config.xml 的配置文件进而对数据库连接池进行配置。 2. c3p0-config.xml 必须和你写的 java 代码在同一个目录下,一般就是放在项目的 src 目录下
- <?xml version="1.0" encoding="utf-8" ?>
- <c3p0-config>
- <!-- c3p0也可以指定配置文件,而且配置文件可以是properties,也可骒xml的。 当然xml的高级一些了。但是c3p0的配置文件名必须为c3p0-config.xml,
- 并且必须放在类路径下 -->
- <!-- 默认的配置这里我们默认使用mysql数据库 -->
- <default-config>
- <!-- 设置数据库的驱动,url, 用户名, 密码 -->
- <property name="driverClass">
- com.mysql.jdbc.Driver
- </property>
- <property name="jdbcUrl">
- jdbc:mysql://127.0.0.1/test?useSSL=false
- </property>
- <property name="user">
- root
- </property>
- <property name="password">
- root
- </property>
- <!-- 建立连接池时初始分配的连接池数=3 -->
- <property name="initialPoolSize">
- 3
- </property>
- <!-- 连接池中的最少连接数=2 -->
- <property name="minPoolSize">
- 2
- </property>
- <!-- 连接池中的最大连接数=5 0-->
- <property name="maxPoolSize">
- 50
- </property>
- <!-- 当连接池中连接耗尽时再一次新生成多少个连接 Default: 3 -->
- <property name="acquireIncrement">
- 3
- </property>
- <!-- 最大空闲时间,超过多长时间连接自动销毁,秒为单位,默认为0,即永远不会自动销毁 -->
- <property name="maxIdleTime">
- 1800
- </property>
- <!--每60秒检查所有连接池中的空闲连接。Default: 0 -->
- <property name="idleConnectionTestPeriod">
- 60
- </property>
- <!-- c3p0还可以为某个用户设置单独的连接数-->
- <user-overrides user="test-user">
- <property name="maxPoolSize">
- 10
- </property>
- <property name="minPoolSize">
- 1
- </property>
- <property name="maxStatements">
- 0
- </property>
- </user-overrides>
- </default-config>
- <!-- c3p0的配置文件中可以配置多个数据库连接信息,可以给每个配置起个名字,这样可以方便的通过配置名称来切换配置信息 -->
- <!-- 名字为Oracle-config的配置 -->
- <named-config name="Oracle-config">
- <property name="driverClass">
- oracle.jdbc.driver.OracleDriver
- </property>
- <property name="jdbcUrl">
- jdbc:oracle:thin:@localhost:1521:test
- </property>
- <property name="user">
- scott
- </property>
- <property name="password">
- tiger
- </property>
- </named-config>
- </c3p0-config>
第三步: 可以自己创建一个工具类,从数据库的连接池中获取连接,我这里只是写了一个建议的连接池工具类,当然你可以根据自己的需要进行扩展
- importjava.sql.Connection;importjava.sql.SQLException;importcom.mchange.v2.c3p0.ComboPooledDataSource;// 数据库连接池的工具类
- public final class PoolUtil{
- private staticComboPooledDataSource ds =null;static{// 两种创建数据库连接池的办法
- // 第一种: 使用配置文件中的默认配置<default-config>ds =newComboPooledDataSource();// 第二种: 使用配置文件中设置的其他配置名字的配置 name-config
- // ds = new ComboPooledDataSource("Oracle-config");
- // 第三种: 我们可以显式的在程序中进行设置数据库连接池的信息
- // ds.setDriverClass("com.mysql.jdbc.Driver");
- // ds.setJdbcUrl("jdbc:mysql://127.0.0.1/test?useSSL=false");
- // ds.setUser("root");
- // ds.setPassword("root");}// 获取数据库的连接
- public staticConnectiongetConnection()throwsSQLException {returnds.getConnection();
- }
- }
第四步 :进行数据库连接池的测试,观测是否成功使用了连接池 在这里需要注意的一点,当你在程序里使用完数据库连接之后,必须显示的调用 close() 方法, 此时的 close 语句并不会关闭与数据库的 TCP 连接,而是将连接归还回到连接池中去,变为空闲状态, 如果不 close 掉的话,这个连接将会一直被占用。
- public classPoolTest {public static void main(String[] args) throws SQLException {
- Connection conn =null;try{
- conn = PoolUtil.getConnection();
- }catch(SQLException e) {
- System.out.println("未获取数据库连接");
- e.printStackTrace();
- }
- String sql ="select * from user where id = ?";
- PreparedStatement prep =null;
- prep = (PreparedStatement) conn.prepareStatement(sql);
- prep.setInt(1,1);// 查询sql 语句, 返回一个结果集ResultSet result = prep.executeQuery();// 处理结果集, 释放资源
- while(result.next()) {
- System.out.println(result.getInt("id"));
- System.out.println(result.getString("name"));
- System.out.println(result.getInt("age"));
- System.out.println(result.getString("salary"));
- }// 注意的是,即使使用了数据库连接池之后,这里也必须显式的调用close语句,
- // 此时的close语句并不会关闭与数据库的TCP连接,而是将连接归还回到池中去,变为空闲状态
- // 如果不close掉的话,这个连接将会一直被占用result.close();
- prep.close();
- conn.close();
- }
- }
C3P0 的源代码的一些关键解析: 几个关键的类: C3P0PooledConnectionPoolManager 是连接池的管理类, C3P0PooledConnectionPool 是连接池类, BasicResourcePool 是真正管理数据库连接池的类
获取一个连接的代码:
- publicConnectiongetConnection()throwsSQLException
- {
- PooledConnection pc = getPoolManager().getPool().checkoutPooledConnection();returnpc.getConnection();
- }
public Object checkoutResource(long timeout) 1) 关键步骤代码:Object resc = prelimCheckoutResource(timeout); 查看池中是否有未使用的 connection, 有就返回(还要判断是否空闲、是否过期);没有,如果没有达到最大数,就生成一个,或者就等待。 2) 关键步骤代码: boolean refurb = attemptRefurbishResourceOnCheckout (resc); 得到连接后,检测连接的可用性。 3) 连接可用,接着判断连接是否处于管理中,不在就再调用本方法获取一个,在就返回本连接。
C3P0** 从连接池拿到的连接都是代理的连接,一个对 PooledConnection 类型对象的代理对象,所以可以放心调用 close 方法,只是连接进行了归还,不会关闭物理连接而且 C3p0 中实际创建的对象是实现了 PooledConnection(接口,位于 javax.sql),它本身包含 Connection, 和这个 connection 相关的所有 Statement,Result,可以实现对连接的一些管理,添加监听器等, 所做的所有数据库操作,都被 PooledConnection 所管理。**c3p0 默认的实现是 NewPooledConnection
C3P0 使用了 LinkedList 来存放空闲池中的连接,每次取连接的时候是 get(0), 然后 remove(0) 应该是使用的队列来实现的空闲池 C3P0 使用的 HashMap 来存放的正在使用的连接,这样方便进行查找,连接返回的时候,根据连接可以快速的定位到要 remove 的那个连接 C3P0 使用 HashSet 来存储一些失效的,但是仍旧被使用或者检查的资源。
这些数据结构可以在 BasicResourcePool 中查看到 C3P0 返回的 Connection 对象是 本博客参考了 http://blog.csdn.net/shuaihj/article/details/14223015
来源: http://blog.csdn.net/woshiluoye9/article/details/70038266