数据库连接池深度剖析

原文出处:http://blog.csdn.net/shuaihj/article/details/14223015

一、技术演化

(一)前期我们怎么进行数据库操作

    1.原理:java应用程序访问数据库的过程

        (1)加载数据库驱动

        (2)获取数据库连接对象

        (3)根据SQL获取sql会话对象

        (4)执行sql结果处理集

        (5)关闭结果集,关闭会话、关闭连接

    2.代码
          public class JDBCTest {
//定义变量
static Connection conn;
static PreparedStatement ps;
static ResultSet rs;
private static final String URL="jdbc:oracle:thin:@localhost:1521:orcl";

public void query() {

try {
//1.加载数据库驱动
Class.forName("oracle.jdbc.driver.OracleDriver");

//2.获取数据库连接对象
conn = DriverManager.getConnection(URL, "scott", "123456");

//3.根据SQL获取sql会话对象
String SQL = "";
ps = conn.prepareStatement(SQL);

//4.执行sql处理结果集
rs = ps.executeQuery();
while(rs.next()){
System.out.println("");
}

//5.关闭结果集、关闭会话、关闭连接
rs.close();
ps.close();
conn.close();

} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
3.分析

 程序开发过程中,存在很多问题:首先,每一次web请求都要建立一次数据库连接。建立连接是一个费时的活动,每次都得花费0.05s~1s的时间,而且系统还要分配内存资源。这个时间对于一次或几次数据库操作,或许感觉不出系统有多大的开销。可是对于现在的web应用,尤其是大型电子商务网站,同时有几百人甚至几千人在线是很正常的事。在这种情况下,频繁的进行数据库连接操作势必占用很多的系统资源,网站的响应速度必定下降,严重的甚至会造成服务器的崩溃。不是危言耸听,这就是制约某些电子商务网站发展的技术瓶颈问题。其次,对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将不得不重启数据库。还有,这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。

上述的用户查询案例,如果同时有1000人访问,就会不断的有数据库连接、断开操作:


通过上面的分析,我们可以看出来,“数据库连接”是一种稀缺的资源,为了保障网站的正常使用,应该对其进行妥善管理。其实我们查询完数据库后,如果不关闭连接,而是暂时存放起来,当别人使用时,把这个连接给他们使用。就避免了一次建立数据库连接和断开的操作时间消耗。原理如下:


(二)技术演进出来的数据库连接池

       由上面的分析可以看出,问题的根源就在于对数据库连接资源的低效管理。我们知道,对于共享资源,有一个很著名的设计模式:资源池(resource pool)。该模式正是为了解决资源的频繁分配﹑释放所造成的问题。为解决上述问题,可以采用数据库连接池技术。数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接。更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量﹑使用情况,为系统开发﹑测试及性能调整提供依据。

       我们自己尝试开发一个连接池,来为上面的查询业务提供数据库连接服务:

       ①   编写class 实现DataSource 接口

       ②   class构造器一次性创建10个连接,将连接保存LinkedList

       ③   实现getConnection   LinkedList返回一个连接

       ④   提供将连接放回连接池中方法

 

       1、连接池代码       

  1. public class MyDataSource implements DataSource {
  2. //链表 --- 实现栈结构
  3. privateLinkedList<Connection> dataSources = new LinkedList<Connection>();
  4. //初始化连接数量
  5. publicMyDataSource() {
  6. //一次性创建10个连接
  7. for( int i = 0; i < 10; i++) {
  8. try {
  9. //1、装载sqlserver驱动对象
  10. DriverManager.registerDriver( new SQLServerDriver());
  11. //2、通过JDBC建立数据库连接
  12. Connection con =DriverManager.getConnection(
  13. "jdbc:sqlserver://192.168.2.6:1433;DatabaseName=customer", "sa", "123");
  14. //3、将连接加入连接池中
  15. dataSources.add(con);
  16. } catch (Exception e) {
  17. e.printStackTrace();
  18. }
  19. }
  20. }
  21. @Override
  22. publicConnection getConnection() throws SQLException {
  23. //取出连接池中一个连接
  24. finalConnection conn = dataSources.removeFirst(); // 删除第一个连接返回
  25. returnconn;
  26. }
  27. //将连接放回连接池
  28. publicvoid releaseConnection(Connection conn) {
  29. dataSources.add(conn);
  30. }
  31. }


       2、使用连接池重构我们的用户查询函数       

  1. //查询所有用户
  2. Public void FindAllUsers(){
  3. //1、使用连接池建立数据库连接
  4. MyDataSource dataSource = new MyDataSource();
  5. Connection conn =dataSource.getConnection();
  6. //2、创建状态
  7. Statement state =con.createStatement();
  8. //3、查询数据库并返回结果
  9. ResultSet result =state.executeQuery( "select * from users");
  10. //4、输出查询结果
  11. while(result.next()){
  12. System.out.println(result.getString( "email"));
  13. }
  14. //5、断开数据库连接
  15. result.close();
  16. state.close();
  17. //6、归还数据库连接给连接池
  18. dataSource.releaseConnection(conn);
  19. }

       这就是数据库连接池的原理,它大大提供了数据库连接的利用率,减小了内存吞吐的开销。我们在开发过程中,就不需要再关心数据库连接的问题,自然有数据库连接池帮助我们处理,这回放心了吧。但连接池需要考虑的问题不仅仅如此,下面我们就看看还有哪些问题需要考虑。

三.连接池还要考虑更多的问题

       1、并发问题

       为了使连接管理服务具有最大的通用性,必须考虑多线程环境,即并发问题。这个问题相对比较好解决,因为java语言自身提供了对并发管理的支持,使用synchronized关键字即可确保线程是同步的。使用方法为直接在类方法前面加上synchronized关键字,如:

      publicsynchronized connection getconnection()

 

       2、多数据库服务器和多用户

       对于大型的企业级应用,常常需要同时连接不同的数据库(如连接oraclesybase)。如何连接不同的数据库呢?我们采用的策略是:设计一个符合单例模式的连接池管理类,在连接池管理类的唯一实例被创建时读取一个资源文件,其中资源文件中存放着多个数据库的url地址等信息。根据资源文件提供的信息,创建多个连接池类的实例,每一个实例都是一个特定数据库的连接池。连接池管理类实例为每个连接池实例取一个名字,通过不同的名字来管理不同的连接池。

       对于同一个数据库有多个用户使用不同的名称和密码访问的情况,也可以通过资源文件处理,即在资源文件中设置多个具有相同url地址,但具有不同用户名和密码的数据库连接信息。

 

       3、事务处理

       我们知道,事务具有原子性,此时要求对数据库的操作符合“all-all-nothing”原则即对于一组sql语句要么全做,要么全不做。

       java语言中,connection类本身提供了对事务的支持,可以通过设置connectionautocommit属性为false 然后显式的调用commitrollback方法来实现。但要高效的进行connection复用,就必须提供相应的事务支持机制。可采用每一个事务独占一个连接来实现,这种方法可以大大降低事务管理的复杂性。

 

       4、连接池的分配与释放

       连接池的分配与释放,对系统的性能有很大的影响。合理的分配与释放,可以提高连接的复用度,从而降低建立新连接的开销,同时还可以加快用户的访问速度。

       对于连接的管理可使用空闲池。即把已经创建但尚未分配出去的连接按创建时间存放到一个空闲池中。每当用户请求一个连接时,系统首先检查空闲池内有没有空闲连接。如果有就把建立时间最长(通过容器的顺序存放实现)的那个连接分配给他(实际是先做连接是否有效的判断,如果可用就分配给用户,如不可用就把这个连接从空闲池删掉,重新检测空闲池是否还有连接);如果没有则检查当前所开连接池是否达到连接池所允许的最大连接数(maxconn如果没有达到,就新建一个连接,如果已经达到,就等待一定的时间(timeout)。如果在等待的时间内有连接被释放出来就可以把这个连接分配给等待的用户,如果等待时间超过预定时间timeout 则返回空值(null)。系统对已经分配出去正在使用的连接只做计数,当使用完后再返还给空闲池。对于空闲连接的状态,可开辟专门的线程定时检测,这样会花费一定的系统开销,但可以保证较快的响应速度。也可采取不开辟专门线程,只是在分配前检测的方法。

 

       5、连接池的配置与维护

       连接池中到底应该放置多少连接,才能使系统的性能最佳?系统可采取设置最小连接数(minconn)和最大连接数(maxconn)来控制连接池中的连接。最小连接数是系统启动时连接池所创建的连接数。如果创建过多,则系统启动就慢,但创建后系统的响应速度会很快;如果创建过少,则系统启动的很快,响应起来却慢。这样,可以在开发时,设置较小的最小连接数,开发起来会快,而在系统实际使用时设置较大的,因为这样对访问客户来说速度会快些。最大连接数是连接池中允许连接的最大数目,具体设置多少,要看系统的访问量,可通过反复测试,找到最佳点。

       如何确保连接池中的最小连接数呢?有动态和静态两种策略。动态即每隔一定时间就对连接池进行检测,如果发现连接数量小于最小连接数,则补充相应数量的新连接以保证连接池的正常运转。静态是发现空闲连接不够时再去检查。

四.实际开发中有成熟的开源连接池供我们使用

       理解了连接池的原理就可以了,没有必要什么都从头写一遍,那样会花费很多时间,并且性能及稳定性也不一定满足要求。事实上,已经存在很多流行的性能优良的第三方数据库连接池jar包供我们使用。如:

       1.Apache commons-dbcp 连接池

        下载:http://commons.apache.org/proper/commons-dbcp/

 

       2.c3p0 数据库连接池

        下载:http://sourceforge.net/projects/c3p0/

二、详细介绍

连接池的作用就是为了提高性能。

       连接池的作用:连接池是将已经创建好的连接保存在池中,当有请求来时,直接使用已经创建好的连接对数据库进行访问。这样省略了创建连接和销毁连接的过程。这样性能上得到了提高。

基本原理是这样的:

1)建立数据库连接池对象(服务器启动)。
(2)按照事先指定的参数创建初始数量的数据库连接(即:空闲连接数)。
(3)对于一个数据库访问请求,直接从连接池中得到一个连接。如果数据库连接池对象中没有空闲的连接,且连接数没有达到最大(即:最大活跃连接数),创建一个新的数据库连接。
(4)存取数据库。
(5)关闭数据库,释放所有数据库连接(此时的关闭数据库连接,并非真正关闭,而是将其放入空闲队列中。如实际空闲连接数大于初始空闲连接数则释放连接)。
(6)释放数据库连接池对象(服务器停止、维护期间,释放数据库连接池对象,并释放所有连接)。

1 .连接池的概念和为什么要使用连接池?
    连接池放了N个Connection对象,本质上放在内存当中,在内存中划出一块缓存对象,应用程序每次从池里获得Connection对象,而不是直接从数据里获得,这样不占用服务器的内存资源。

2 .如果不使用连接池会出现的情况:
a.占用服务器的内存资源
b.导致服务器的速度非常慢

3 .应用连接池的三种方式:
a.自定义连接池
b.使用第三方连接池
c.使用服务器自带的连接池

       连接池一般比直接连接更有优越性,因为它提高了性能的同时还保存了宝贵的资源。在整个应用程序的使用过程,当中重复的打开直接连接将导致性能的下降。而池连接只在服务器启动时打开一次,从而消除了这种性能问题。
        连接池主要考虑的是性能,每次获取连接和释放连接都有很大的工作量,会对性能有很大影响;而对资源来说起的是反作用,因为保存一定数量的连接是要消耗内存的。应用程序每次从池里获得Connection对象,而不是直接从数据里获得,这样不占用服务器的内存资源。所以一般要建立连接池,而连接的数量要适当,不能太大,太大会过多消耗资源。(所以,考虑2个方面,一个是内存,另一个是资源)。

       连接池就是为了避免重复多次的打开数据库连接而造成的性能的下降和系统资源的浪费。

接下来,我们来学习三种连接池的配置:dbcp、c3p0、druid

1、dbcp连接池

连接配置文件

#数据库驱动
driverClassName=com.mysql.jdbc.Driver
#数据库连接地址
url=jdbc:mysql://localhost/ctg
#用户名
username=root
#密码
password=123456

#连接池的最大数据库连接数。设为0表示无限制
maxActive=30
#最大空闲数,数据库连接的最大空闲时间。超过空闲时间,数据库连
#接将被标记为不可用,然后被释放。设为0表示无限制
maxIdle=10
#最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制
maxWait=1000

#超过removeAbandonedTimeout时间后,是否进 行没用连接(废弃)的回收(默认为false,调整为true) 
removeAbandoned=true
#超过时间限制,回收没有用(废弃)的连接(默认为 300秒,调整为180)
removeAbandonedTimeout=180


连接池管理类

package cn.songxinqiang.samples.commonsdbcp.util;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory;

/**
 * tomcat数据库连接池管理类<br>
 * 使用为tomcat部署环境<br>
 * 需要在类路径下准备数据库连接配置文件dbcp.properties
 * 
 * @author 宋信强
 * @mail songxinqiang123@gmail.com
 * 
 * @time 2013-12-27
 * 
 */
public class DBManager {
 private static final Log log = LogFactory.getLog(DBManager.class);
 private static final String configFile = "dbcp.properties";

 private static DataSource dataSource;

 static {
  Properties dbProperties = new Properties();
  try {
   dbProperties.load(DBManager.class.getClassLoader()
     .getResourceAsStream(configFile));
   dataSource = BasicDataSourceFactory.createDataSource(dbProperties);

   Connection conn = getConn();
   DatabaseMetaData mdm = conn.getMetaData();
   log.info("Connected to " + mdm.getDatabaseProductName() + " "
     + mdm.getDatabaseProductVersion());
   if (conn != null) {
    conn.close();
   }
  } catch (Exception e) {
   log.error("初始化连接池失败:" + e);
  }
 }

 private DBManager() {
 }

 /**
  * 获取链接,用完后记得关闭
  * 
  * @see {@link DBManager#closeConn(Connection)}
  * @return
  */
 public static final Connection getConn() {
  Connection conn = null;
  try {
   conn = dataSource.getConnection();
  } catch (SQLException e) {
   log.error("获取数据库连接失败:" + e);
  }
  return conn;
 }

 /**
  * 关闭连接
  * 
  * @param conn
  *            需要关闭的连接
  */
 public static void closeConn(Connection conn) {
  try {
   if (conn != null && !conn.isClosed()) {
    conn.setAutoCommit(true);
    conn.close();
   }
  } catch (SQLException e) {
   log.error("关闭数据库连接失败:" + e);
  }
 }

}

 

 

以上是dbcp连接池的配置和使用方式,以上的方式仅供参考。

 

2、c3p0连接池

 

数据库连接配置

c3p0.jdbcUrl=jdbc:oracle:thin:@127.0.0.1:1521:orcl c3p0.driverClass=oracle.jdbc.driver.OracleDriver c3p0.user=test c3p0.password=test

c3p0.acquireIncrement=3  c3p0.idleConnectionTestPeriod=60  c3p0.initialPoolSize=10  c3p0.maxIdleTime=60  c3p0.maxPoolSize=20  c3p0.maxStatements=100  c3p0.minPoolSize=5 


C3P0获取连接公共类

public class MyC3P0Utils{
	private static DataSource ds;

	private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
	static {
		ds = new ComboPooledDataSource();//直接使用即可,不用显示的配置,其会自动识别配置文件
	}

	public static DataSource getDataSource() {
		return ds;
	}

	public static Connection getConnection() {
		try {
//			 得到当前线程上绑定的连接
			Connection conn = tl.get();
			if (conn == null) { // 代表线程上没有绑定连接
				conn = ds.getConnection();
				tl.set(conn);
			}
			return conn;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public static void startTransaction() {
		try {
			// 得到当前线程上绑定连接开启事务
			Connection conn=getConnection();
			conn.setAutoCommit(false);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public static void commitTransaction() {
		try {
			Connection conn = tl.get();
			if (conn != null) {
				conn.commit();
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public static void closeConnection() {
		try {
			Connection conn = tl.get();
			if (conn != null) {
				conn.close();
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			tl.remove(); // 千万注意,解除当前线程上绑定的链接(从threadlocal容器中移除对应当前线程的链接)
		}
	}
}


 

另外提醒一下:不要忘记导入jar包

导入C3P0相关jar包

 

上面就是c3p0连接池的配置方式,仅供参考哦!

当然连接池的配置方式是不同的,上面配置的方式都是将配置放入到property的文件中,还有一种就是使用XML文件的方式,这里不介绍了。

 

下面来讲解到druid连接池的配置方法

 

配置文件

driverClassName=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@192.168.97.51:1521:lc8
username=admin8
password=adminpwd8
filters=stat
initialSize=2
maxActive=300
maxWait=60000
timeBetweenEvictionRunsMillis=60000
minEvictableIdleTimeMillis=300000
validationQuery=SELECT 1
testWhileIdle=true
testOnBorrow=false
testOnReturn=false
poolPreparedStatements=false
maxPoolPreparedStatementPerConnectionSize=200


测试连接:

 

package taobao_druid;

import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Properties;

import javax.sql.DataSource;

import com.alibaba.druid.pool.DruidDataSourceFactory;

/**  * The Class DataSourceUtil.  */ public class DataSourceUtil {

    /** 使用配置文件构建Druid数据源. */     public static final int DRUID_MYSQL_SOURCE = 0;

    /** 使用配置文件构建Druid数据源. */     public static final int DRUID_MYSQL_SOURCE2 = 1;

    /** 使用配置文件构建Dbcp数据源. */     public static final int DBCP_SOURCE = 4;     public static String confile = "druid.properties";     public static Properties p = null;

    static {         p = new Properties();         InputStream inputStream = null;         try {             //java应用             confile = DataSourceUtil.class.getClassLoader().getResource("").getPath()                     + confile;             System.out.println(confile);             File file = new File(confile);             inputStream = new BufferedInputStream(new FileInputStream(file));             p.load(inputStream);         } catch (Exception e) {             e.printStackTrace();         } finally {             try {                 if (inputStream != null) {                     inputStream.close();                 }             } catch (IOException e) {                 e.printStackTrace();             }         }     }

    /**      * 根据类型获取数据源      *      * @param sourceType      *            数据源类型      * @return druid或者dbcp数据源      * @throws Exception      *             the exception      */     public static final DataSource getDataSource(int sourceType) throws Exception {         DataSource dataSource = null;         switch (sourceType) {         case DRUID_MYSQL_SOURCE:             dataSource = DruidDataSourceFactory.createDataSource(p);             break;         case DRUID_MYSQL_SOURCE2:             dataSource = DruidDataSourceFactory.createDataSource(p);             break;         case DBCP_SOURCE:             // dataSource = BasicDataSourceFactory.createDataSource(             // MySqlConfigProperty.getInstance().getProperties());             break;         }         return dataSource;     } }



  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值