JDBC (Java DataBase Connectivity)数据库连接池原理解析与实现

一、应用程序直接获取数据库连接的缺点

  用户每次请求都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。假设网站一天 10 万访问量,数据库服务器就需要创建 10 万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、拓机。

二、使用数据库连接池优化程序性能

数据库连接池的基本概念
  数据库连接是一种关键的有限的昂贵的资源, 这一点在多用户的网页应用程序中体现的尤为突出. 对数据库连接的管理能显著影响到整个应用程序的伸缩性和健壮性, 影响到程序的性能指标. 数据库连接池正式针对这个问题提出来的. 数据库连接池负责分配, 管理和释放数据库连接, 它允许应用程序重复使用一个现有的数据库连接, 而不是重新建立一个。

  数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中, 这些数据库连接的数量是由最小数据库连接数来设定的. 无论这些数据库连接是否被使用, 连接池都将一直保证至少拥有这么多的连接数量. 连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数, 当应用程序向连接池请求的连接数超过最大连接数量时, 这些请求将被加入到等待队列中.
数据库连接池的最小连接数和最大连接数的设置要考虑到以下几个因素:
  最小连接数: 是连接池一直保持的数据库连接, 所以如果应用程序对数据库连接的使用量不大, 将会有大量的数据库连接资源被浪费.
  最大连接数: 是连接池能申请的最大连接数, 如果数据库连接请求超过次数, 后面的数据库连接请求将被加入到等待队列中, 这会影响以后的数据库操作
  如果最小连接数与最大连接数相差很大: 那么最先连接请求将会获利, 之后超过最小连接数量的连接请求等价于建立一个新的数据库连接. 不过, 这些大于最小连接数的数据库连接在使用完不会马上被释放, 他将被放到连接池中等待重复使用或是空间超时后被释放.

 

编写数据库连接池

 

DBManage 这个类用来读取 properties 配置文件,创建 connection 对象,管理 connection 对象

 

 1 import java.io.IOException;
 2 import java.sql.Connection;
 3 import java.sql.DriverManager;
 4 import java.sql.ResultSet;
 5 import java.sql.SQLException;
 6 import java.sql.Statement;
 7 import java.util.Properties;
 8 
 9 import orm.bean.Configuration;
10 import orm.pool.DBConnPool;
11 
12 /**
13  * 根据配置信息,维持连接对象的管理
14  * @author Haidnor
15  *
16  */
17 @SuppressWarnings("all")
18 public class DBManage {
19     /**
20      * 配置信息
21      */
22     private static Configuration conf;
23     
24     /**
25      * 连接池对象
26      */
27     private static DBConnPool pool = null;
28     /**
29      * 初始化,加载指定的文件
30      */
31     static {
32         Properties pros = new Properties();    
33         
34         try {
35             pros.load(Thread.currentThread().getContextClassLoader().getSystemResourceAsStream("database.properties"));
36         } catch (IOException e) {
37             e.printStackTrace();
38         }
39         conf = new Configuration();    //读取配置文件,将配置文件内的信息保存在配置对象中
40         conf.setDriver(pros.getProperty("driver"));
41         conf.setUrl(pros.getProperty("url"));
42         conf.setUser(pros.getProperty("user"));
43         conf.setPassword(pros.getProperty("password"));
44         conf.setUsingDB(pros.getProperty("usingDB"));
45         conf.setSrcPath(pros.getProperty("srcPath"));
46         conf.setPoPackage(pros.getProperty("poPackage"));
47         conf.setQueryClass(pros.getProperty("queryClass"));
48         conf.setPoolMinSize(Integer.parseInt(pros.getProperty("poolMinSize")));
49         conf.setPoolMaxSize(Integer.parseInt(pros.getProperty("poolMaxSize")));
50     }
51 
52     /**
53      * 在连接池中获得 Connection 对象
54      * @return
55      */
56     public static Connection getConnetion() {
57         if(pool == null) {
58             pool = new DBConnPool();            
59         }
60         return pool.getConnection();
61     }
62     
63     /**
64      * 创建新的 Connection 对象
65      * @return
66      */
67     public static Connection createConnetion() {
68         try {
69             Class.forName(conf.getDriver());
70             return DriverManager.getConnection(conf.getUrl(),conf.getUser(),conf.getPassword());     //目前直接建立连接,后期加入连接池处理,提高效率
71         } catch (Exception e) {
72             e.printStackTrace();
73             return null;
74         }
75     }
76     
77     /**
78      * 关闭数据库连接 ResultSet  Statement Connection
79      * @param rs
80      * @param pa
81      * @param conn
82      */
83     public static void close(ResultSet rs,Statement pa,Connection conn){
84         if(conn != null){
85             pool.close(conn);
86         }
87     }
88     
90     /**
91      * 返回 Configuration 对象
92      * @return Configuration 对象
93      */
94     public static Configuration getConf(){
95         return conf;
96     }
97 }

 

数据库连接池类 DBManage ,将多个 Connection 对象存放在一个 List 集合中。在程序初始化前,会自动根据配置文件中设置的连接池对象最小数,往 list 集合中创建多个 Connection 对象。

以后使用 Connection 对象就可以直接从 list 集合中获取。关闭 Connection 对象不再使用 close()方法,而是将 Connection 对象再放回到 list 集合中。

 1 import java.sql.Connection;
 2 import java.sql.SQLException;
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 
 6 import orm.core.DBManage;
 7 
 8 /**
 9  * 连接池类
10  * @author Haidnor
11  *
12  */
13 
14 public class DBConnPool {
15     
16     /**
17      * 连接池对象
18      */
19     private List<Connection> pool; 
20     
21     /**
22      * 最大连接数
23      */
24     private static final int POOL_MAX_SIZE = DBManage.getConf().getPoolMaxSize();
25     
26     /**
27      * 最小连接数
28      */
29     private static final int POOL_MIN_SIZE = DBManage.getConf().getPoolMinSize();
30     
31     /**
32      * 初始化连接池,使池中的连接数达到最小值
33      */
34     private void initPool() {
35         if(pool == null) {
36             pool = new ArrayList<Connection>();
37         }
38         while(pool.size() <= DBConnPool.POOL_MIN_SIZE){
39             pool.add(DBManage.createConnetion());
40         }
41     }
42     
43     /**
44      * 从连接池中取出一个连接对象
45      * @return
46      */
47     public synchronized Connection getConnection() {
48         int last_index = pool.size() - 1;
49         Connection connection = pool.get(last_index);
50         pool.remove(last_index);
51         return connection;
52     }
53     
54     /**
55      * 将连接放回池中
56      */
57     public synchronized void close(Connection connection) {
58         if(pool.size() >= POOL_MAX_SIZE){
59             try {
60                 if(connection != null){
61                     connection.close();
62                 }
63             } catch (SQLException e) {
64                 e.printStackTrace();
65             }
66         }else{
67             pool.add(connection);
68         }
69     }
70     
71     
72     public DBConnPool(){
73         initPool();
74     }
75     
76 }

最后建立一个测试类。分别使用数据库连接池 Connection 对象和直接 new  Connection 对象对数据库某个表进行 3000 次查询测试

使用连接池耗时 952 毫秒,不用连接池耗时 12742 毫秒。

import java.util.List;

import orm.core.MySqlQuery;
import orm.po.User_yinbiao1;

public class Test {
//不加连接池耗时 12742 毫秒
//加连接池耗时:952 毫秒
public static void main(String[] args) {
long a = System.currentTimeMillis();
for(int i=0; i<3000; i++){
testQueryRows();
}
long b = System.currentTimeMillis();
System.out.println(
"使用数据库连接池运行时间:" + (b - a) + "毫秒");
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> testQueryRows() {
    List list </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> MySqlQuery().queryRows("SELECT * FROM user_yinbiao1", User_yinbiao1.<span style="color: rgba(0, 0, 255, 1)">class</span>, <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Object[] {});
    User_yinbiao1 user </span>=  (User_yinbiao1)list.get(2<span style="color: rgba(0, 0, 0, 1)">);
}

}