数据库连接池

  本篇讲诉数据库连接池的概念和如何使用JDBC来创建自定义的数据库连接池。

  我们在操作数据库的时候首先最重要的就是获取数据库的连接,只有获取了连接才能有后面对数据库的一系列操作。但是获取连接的过程是非常消耗数据库资源的,并且也非常耗时,这一点看看TCP三次握手取得连接也可以想象的到。多次获取连接比长连接还要耗费资源,因此在会大量操作数据库的情况下,减少数据库创建连接的次数是能极大地优化数据库的性能。

  如果不用连接池,则数据库为每一个用户的来访创建一个链接,虽然数据库本身在安装的时候会指定最大连接数,但是我们说过资源的耗费和耗时其实是在这些有限连接的不断创建和销毁的循环中。如下图所示:

  

数据库连接池(DataSource)

  数据库连接池,也称为数据源。是在应用开始前就将数据库的连接创建多个而不销毁,同时管理起来,犹如放在一个池子里,那么只要用户来访问数据库,则不在是直接通过数据库获取连接,而是从连接池中获取连接,再通过连接操纵数据库,最后再将连接返回给连接池,以便别的访问再从池子中获取连接进行操作。

  

  数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。

  数据库连接池的最小连接数和最大连接数的设置要考虑到以下几个因素:

  ① 最小连接数:是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费。

  ② 最大连接数:是连接池能申请的最大连接数,如果数据库连接请求超过次数,后面的数据库连接请求将被加入到等待队列中,这会影响以后的数据库操作。

  ③ 如果最小连接数与最大连接数相差很大:那么最先连接请求将会获利,之后超过最小连接数量的连接请求等价于建立一个新的数据库连接。不过,这些大于最小连接数的数据库连接在使用完不会马上被释放,他将被放到连接池中等待重复使用或是空间超时后被释放。

 

使用JDBC编写自定义的数据库连接池(DataSource)

  要编写自定义的数据库连接池,必须要实现DataSource接口。

  

  之前我们创建连接Connection对象都是通过DriveManager驱动管理器来获取的,但这是直接使数据库为我们创建连接,而上图DataSource接口的API也说明了,我们要想获取连接的首选方法应该是通过DataSource接口来获取连接池中的连接。

  DataSource接口自身主要就两个方法,但是DataSource还继承了别的接口,因此如果要实现DataSource接口的话,还必须将其父类的方法均实现:

  

  因此我们在编写自己的数据库连接池类时,需要添加覆写多个未实现的方法,虽然我们也不会在这些方法中编写任何内容。

 

  我们要想实现自己定义的一个连接池,需要实现以下几个步骤:

  1定义一个类,实现DataSource接口。

  2在这个类的初始化方法(例如构造函数或者静态代码块)中,通过DriveManager驱动管理器构建多个连接,这个还是跟以前获取Connection对象一样的用法。这一步是因为我们要先从数据库获取连接,才能将这些连接保存,即将多次创建连接变为一次创建而使用长连接。

  3将上面从数据库直接创建的连接存到LinkedList集合中,而这个集合就相当于一个池子,使用链表结构的集合有利于增删操作。

  4 这个自定义的类当然要实现DataSource接口中的方法,其中最重要的就是我们要覆盖getConnection()方法,这是给别的要操作数据库的方法提供Connection对象,所以这次要从刚才保存连接的LinkedList集合取出连接给别的方法(即从连接池中取出连接)。但是我们从集合取出给别的方法的又不能直接就是数据库提供的Connection对象(也就是刚才初始化存入集合中的Connection对象),因为最后必须要释放资源,而一旦直接调用数据库提供的Connection对象的close()方法则就是将该连接销毁了,因此我们通过集合(连接池)取出Connection对象应该进行功能增强,最后再在getConnection()方法返回出去给别的方法使用。而这一步也是整个步骤中最难的,也是最重要的一个知识点。

  以上的关键在于第4步,我们要返回的是一个即是Connection接口的实例对象,又不能因为调用close()方法而销毁了这个链接,而是调用close()方法能将该Connection接口的实例对象返回给集合中。因此我们必须要对Connection中的close()方法进行覆写。但如何对Connection的对象进行功能增强,主要有下面三种方法:

  ① 编写一个Connection的子类,覆写close()方法。

  ② 使用包装设计模式。

  ③ 使用动态代理。

 

  使用子类的方式不合理,且不说我们的Connection对象中封装了很多和数据库相关的信息,单是Connection接口就需要现实很多很多方法,所以这不实际。在本篇中我们使用包装设计模式来增强数据库直接提供的Connection对象,覆写close()方法,将其功能从销毁连接变为返回进集合中。关于包装设计模式请看《包装设计模式》。

  接下来我们将会在一个工程中按上面的步骤来简单地创建一个自定义的数据库连接池。

 

  ⑴ 创建一个JdbcPool类,同之前使用JDBC工具类(如《JDBC操作数据库的学习(2)》)一样,在初始化时就根据配置文件实现注册驱动,获取连接等等操作。这里我们创建一个初始连接池数为10个的连接池。

  配置文件内容如下:

  

  

 1 public class JdbcPool implements DataSource {
 2 
 3     private static LinkedList<Connection> connectionList = new LinkedList<>();   //以集合作为连接池
 4     private static Properties config = new Properties();
 5     
 6     static{
 7         InputStream in = JdbcPool.class.getClassLoader().getResourceAsStream("database.properties");
 8         try {
 9             config.load(in);
10             Class.forName(config.getProperty("driver"));  //注册驱动
11             String url = config.getProperty("url");
12             String username = config.getProperty("username");
13             String password = config.getProperty("password");
14             for(int i=0;i<10;i++) {    //获取十个连接
15                 Connection conn = DriverManager.getConnection(url, username, password);
16                 connectionList.addLast(conn);  //将每个连接都添加到集合(池)中
17             }
18             
19         } catch (Exception e) {
20             throw new ExceptionInInitializerError(e);
21         }
22     }
23     
24     @Override
25     public Connection getConnection() throws SQLException {
26         if(connectionList.size()<1) {
27             throw new RuntimeException("数据库连接忙");
28         }
29         Connection conn = connectionList.removeFirst();
30         MyConnection myConn = new MyConnection(conn); //从池中取出连接并使用包装类增强close方法
31         
32         return myConn;
33     }
34     
35     class MyConnection implements Connection{  //包装设计模式的类
36         private Connection conn;
37         public MyConnection(Connection conn) {
38             this.conn = conn;
39         }
40         
41         @Override
42         public void close() throws SQLException {
43             connectionList.addFirst(this.conn);  //调用close方法时只是将连接重新返回池中,而不会销毁
44     }
45 
46 @Override
47         public Statement createStatement() throws SQLException {
48             this.conn.createStatement();  //对于不增强的方法则调用目标对象的方法即可,在MyConnection包装类中其他方法都是这样的
49             return null;
50     }
51     。。。  //以下省略Connection接口中覆写的其他方法,对于不想增强的方法都如上(createStatement方法)所示
52 }    // MyConnection包装类完成
53 
54 @Override
55     public Connection getConnection(String username, String password)
56             throws SQLException {    //实现DataSource接口的其他方法,在本案例中对我们来说并没有什么作用
57         return null;
58 }
59 。。。//以下省略DataSource接口中覆写的其他方法,这些方法对我们来说暂时没有作用
60 }
View Code

  以上就是一个简单的使用JDBC来实现一个数据库连接池的代码。在上面的代码中,我们在该类(JdbcPool)的初始化时(静态代码块里),先通过驱动管理器获取了十个连接,并将数据库提供的连接Connection对象存入集合也就是我们说的池中。

  而别的方法需要通过JdbcPool类的getConnection方法来获取池中的连接,但是我们通过一个包装类,同时在上面也是内部类,将池中的连接取出,并做了包装,覆写了close方法,最后再返回出去,这时候别的方法获取的并不是数据库直接提供的连接,而是我们自己包装过的类,这个包装类除了close方法不同外,其他还是一样的和原来数据库提供的连接具有相同的方法,以为就是在调用原来连接的方法。

  使用包装设计模式虽然能满足我们的需求,但是如果要包装的接口方法过多,例如Connection接口,而我们只想覆写其中一两个方法,则程序会造成极大的冗余,因此最佳的方式应该是使用动态代理。

 

 

 

                  

 

posted @ 2016-03-13 20:34  fjdingsd  阅读(2478)  评论(0编辑  收藏  举报