Java手写数据库连接池

  PS. 各位大虾,小弟初来咋到,如有不足,敬请谅解,还需各位大虾一一指教出来。   

  首先,数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。这项技术能明显提高对数据库操作的性能。(ps. 百度百科)

   先说说数据库连接池的流程,首先是配置文件包含(数据库连接地址,数据库用户名,数据库密码,数据库驱动,连接池数量,开始初始化数量,自动增长数量)这些内容。

然后读取配置文件,初始化线程池,获取连接池中连接使用时更改其状态,如连接池中连接不够用则自动创建对应配置文件中数量的连接,并判断是否超过连接池最大数量,当连接使用完后,重新将连接放入池中供其他线程使用,从而达到可复用的效果,下面有使用连接池和没有使用连接池的对比测试。好了,废话不多说,直接上代码:

  1. 首先配置文件

    对应的数据库的各种信息,以及数据库连接池初始化时数量,最大连接数量以及自动增长数量:

 

jdbcDriver = com.mysql.jdbc.Driver
jdbcUrl = jdbc:mysql://localhost:3306/boot?useUnicode=true&characterEncoding=UTF-8
username = root
password = 123456
initConnectCount = 20
maxConnects = 100
incrementCount = 3

  2. 封装一个连接类

   该类中包含一个数据库连接,一个是否使用的标记以及一个 close 方法,用来将该连接置为可用状态,从而达到数据库连接的可复用,减少持续创建新连接的资源消耗

package com.example.demo.pool;

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
import java.util.Vector;

/**

  • 连接类

  • @author Administrator
    */
    public class PoolConnection {

    /**

    • 数据库连接
      */
      private Connection conn = null;

    /**

    • 标记该连接是否使用
      */
      private boolean isUse = false;

    /*

    • 构造方法
    • */
      public PoolConnection(Connection conn, boolean isUse) {
      this.conn = conn;
      this.isUse = isUse;
      }

    /**

    • 查询实现
      */
      public ResultSet queryBySql(String sql) {
      Statement sm
      = null;
      ResultSet rs
      = null;
      try {
      sm
      = conn.createStatement();
      rs
      = sm.executeQuery(sql);

      } catch (SQLException e) {
      e.printStackTrace();
      }

      return rs;
      }

    public Connection getConn() {
    return conn;
    }

    public void setConn(Connection conn) {
    this.conn = conn;
    }

    public boolean isUse() {
    return isUse;
    }

    public void setUse(boolean use) {
    isUse
    = use;
    }

    /**

    • 将该连接置为可用状态
      */
      public void close() {
      this.isUse = false;
      }
      }

 

 3. 连接池接口

 

  对外提供的连接池的接口
package com.example.demo.pool;

import java.sql.Connection;

public interface IPool {

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
*  获取连接池中可用连接
* </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
PoolConnection getConnection();


  /**
  * 获取一个数据库连接(不使用连接池)
  * */
  Connection getConnectionNoPool();

}

  4. 连接池实现类以及对应方法

   首先加载对应配置文件中信息,初始化数据库连接池,然后用 synchronized 来实现多线程情况下线程安全的获取可用连接

  

package com.example.demo.pool;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import java.util.Vector;

/**

  • @author Administrator
    */
    public class JdbcPool implements IPool {

    private static String jdbcDriver;

    private static String jdbcUrl;

    private static String username;

    private static String password;

    private static Integer initConnectCount;

    private static Integer maxConnects;

    private static Integer incrementCount;

    private static Vector<PoolConnection> connections = new Vector<>();

    /**

    • 通过实例初始化块来初始化

    • */
        {
           // 读取对应的配置文件,加载入 properties 中,并设置到对应的参数中
      InputStream is
      = JdbcPool.class.getClassLoader().getResourceAsStream("jdbc.properties");
      Properties properties
      = new Properties();
      try {
      properties.load(is);
      }
      catch (IOException e) {
      e.printStackTrace();
      }
      jdbcDriver
      = properties.getProperty("jdbcDriver");
      jdbcUrl
      = properties.getProperty("jdbcUrl");
      username
      = properties.getProperty("username");
      password
      = properties.getProperty("password");
      initConnectCount
      = Integer.valueOf(properties.getProperty("initConnectCount"));
      maxConnects
      = Integer.valueOf(properties.getProperty("maxConnects"));
      incrementCount
      = Integer.valueOf(properties.getProperty("incrementCount"));

      try {
      /
      * 注册 jdbc 驱动
      *
      /
      Driver driver
      = (Driver) Class.forName(jdbcDriver).newInstance();
      DriverManager.registerDriver(driver);
      /
      * 根据 initConnectCount 来初始化连接池
      *
      /
      createConnections(initConnectCount);
      }
      catch (ClassNotFoundException e) {
      e.printStackTrace();
      }
      catch (InstantiationException e) {
      e.printStackTrace();
      }
      catch (IllegalAccessException e) {
      e.printStackTrace();
      }
      catch (SQLException e) {
      e.printStackTrace();
      }
      }

    /**

    • 获取可用连接

    • */
      @Override
      public PoolConnection getConnection() {
      if (connections.isEmpty()) {
      System.out.println(
      "连接池中没有连接");
      throw new RuntimeException("连接池中没有连接");
      }

      return getActiveConnection();
      }

    /**

    • 同步方法来获取连接池中可用连接,在多线程情况下,只有一个线程访问该方法来获取连接,防止由于多线程情况下多个线程获取同一个连接从而引起出错
      /
      private synchronized PoolConnection getActiveConnection() {
      /
      • 通过循环来获取可用连接,若获取不到可用连接,则依靠无限循环来继续获取
      • /
        while (true) {
        for (PoolConnection con : connections) {
        if (!con.isUse()) {
        Connection trueConn
        = con.getConn();
        try {
        //验证连接是否失效 0 表示不校验超时
        if (!trueConn.isValid(0)) {
        con.setConn(DriverManager.getConnection(jdbcUrl, username, password));
        }
        }
        catch (SQLException e) {
        e.printStackTrace();
        }
        con.setUse(
        true);
        return con;
        }
        }
        /
        • 根据连接池中连接数量从而判断是否增加对应的数量的连接
        • */
          if (connections.size() <= maxConnects - incrementCount) {
          createConnections(incrementCount);
          }
          else if (connections.size() < maxConnects && connections.size() > maxConnects - incrementCount) {
          createConnections(maxConnects
          - connections.size());
          }

          }
          }

    /*

    • 创建对应数量的连接并放入连接池中

    • /
      private void createConnections(int count) {
      for (int i = 0; i < count; i++) {
      if (maxConnects > 0 && connections.size() >= maxConnects) {
      System.out.println(
      "连接池中连接数量已经达到最大值");
      throw new RuntimeException("连接池中连接数量已经达到最大值");
      }
      try {
      Connection connection
      = DriverManager.getConnection(jdbcUrl, username, password);
      /

      * 将连接放入连接池中,并将状态设为可用
      *
      */
      connections.add(
      new PoolConnection(connection, false));

        } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (SQLException e) {
            e.printStackTrace();
        }
      

      }
      }

    /*

    • 获取连接池中连接数量
    • */
      public int getSize() {
      return connections.size();
      }
  @Override
  public Connection getConnectionNoPool() {
   Connection connection = null;
   try {
   connection = DriverManager.getConnection(jdbcUrl, username, password);
   } catch (SQLException e) {
   e.printStackTrace();
   }
  
   return connection;
  }

}

  4. 连接池维护类 (单例模式)

    通过静态内部类来实现连接池的单例模式

package com.example.demo.pool;

/**

  • @author Administrator
    */
    public class PoolManager {

    /**

    • 静态内部类实现连接池的单例
    • */
      private static class CreatePool{
      private static JdbcPool pool = new JdbcPool();
      }

    public static JdbcPool getInstance(){
    return CreatePool.pool;
    }

}

  最后,上测试方法:我起了 2000 个线程,通过 new 两个 CountDownLatch(不懂的,问度娘,就不一一解释了)其中一个来实现线程的同时并发执行,不要问为什么是 2000 不是 20000,因为 20000 个线程本人的渣电脑直接 OOM 了,所以就 2000 个测试,然后通过调用另外一个 CountDownLatch 的 await 方法来实现等待所有线程全完成从而统计总共花费时间。

  熟话说,没有对比就没有伤害,我们先来用普通没有连接池的测试一下 2000 个连接并行执行的时间,代码如下:

       

package com.example.demo.pool;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.CountDownLatch;

/**

  • @author Administrator
    */
    public class JdbcNoPoolMain {

    static final int threadSize = 2000;

    static JdbcPool jdbcPool = PoolManager.getInstance();
      
    static CountDownLatch countDownLatch1 = new CountDownLatch(1);
    static CountDownLatch countDownLatch2 = new CountDownLatch(threadSize);

    public static void main(String[] args) throws InterruptedException {
    threadTest();
    }

    public static void threadTest() throws InterruptedException {
    long time1 = System.currentTimeMillis();
    for (int i = 0; i < threadSize; i++) {
    new Thread(new Runnable() {
    @Override
    public void run() {
    try {
    // 使得线程阻塞到 coutDownLatch1 为 0 时才执行
    countDownLatch1.await();
    selectNoPool();
    // 每个独立子线程执行完后,countDownLatch2 减 1
    countDownLatch2.countDown();
    }
    catch (InterruptedException e) {
    e.printStackTrace();
    }
    catch (SQLException e) {
    e.printStackTrace();
    }
    }
    }).start();
    }
    // 将 countDownLatch1 置为 0,从而使线程并发执行
    countDownLatch1.countDown();

     </span><span style="color: rgba(0, 128, 0, 1)">//等待countDownLatch2变为0时才继续执行
    

countDownLatch2.await();
long time2 = System.currentTimeMillis();

    System.out.println(</span>"thread size: "+threadSize+" no use pool :" + (time2 -<span style="color: rgba(0, 0, 0, 1)"> time1));
}

</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> selectNoPool() <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> SQLException {

    Connection conn </span>=<span style="color: rgba(0, 0, 0, 1)"> jdbcPool.getConnectionNoPool();
    Statement sm </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
    ResultSet rs </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
        sm </span>=<span style="color: rgba(0, 0, 0, 1)"> conn.createStatement();
        rs </span>= sm.executeQuery("select * from user"<span style="color: rgba(0, 0, 0, 1)">);
    } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (SQLException e) {
        e.printStackTrace();
    }

    </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
        </span><span style="color: rgba(0, 0, 255, 1)">while</span><span style="color: rgba(0, 0, 0, 1)"> (rs.next()) {
            System.out.println(Thread.currentThread().getName() </span>+ " ==== " + "name: " + rs.getString("name") + " age: " + rs.getInt("age"<span style="color: rgba(0, 0, 0, 1)">));
        }
        Thread.sleep(</span>100<span style="color: rgba(0, 0, 0, 1)">);
    } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (SQLException e) {
        e.printStackTrace();
    } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (InterruptedException e) {
        e.printStackTrace();
    }
    rs.close();
    sm.close();
    conn.close();
}

}

  下图显示运行显示 2000 个线程同时并发运行花费 24080ms   

    

  

  下面是使用数据库连接池的测试类:

package com.example.demo.pool;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.concurrent.CountDownLatch;

/**

  • @author Administrator
    */
    public class JdbcPoolMain {

    static final int threadSize = 2000;

    static JdbcPool jdbcPool = PoolManager.getInstance();

    static CountDownLatch countDownLatch1 = new CountDownLatch(1);
    static CountDownLatch countDownLatch2 = new CountDownLatch(threadSize);

    public static void main(String[] args) throws InterruptedException {
    threadTest();
    }

    public static void select() throws SQLException {
    PoolConnection conn
    = jdbcPool.getConnection();

     ResultSet rs </span>= conn.queryBySql("select * from user"<span style="color: rgba(0, 0, 0, 1)">);
     </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
         </span><span style="color: rgba(0, 0, 255, 1)">while</span><span style="color: rgba(0, 0, 0, 1)">(rs.next()){
             System.out.println(Thread.currentThread().getName()</span>+" ==== "+"name: "+rs.getString("name")+" age: "+rs.getInt("age"<span style="color: rgba(0, 0, 0, 1)">));
         }
         Thread.sleep(</span>100<span style="color: rgba(0, 0, 0, 1)">);
     } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (SQLException e) {
         e.printStackTrace();
     } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (InterruptedException e) {
         e.printStackTrace();
     }
     rs.close();
     conn.close();
    

    }

    public static void threadTest() throws InterruptedException {
    long time1 = System.currentTimeMillis();
    for(int i=0;i<threadSize;i++){
    new Thread(new Runnable() {
    @Override
    public void run() {
    try {
                  // 阻塞到当 countDownLatch1 为 0 才执行
    countDownLatch1.await();
    select();
                  // 将 countDownLatch2 减 1
    countDownLatch2.countDown();
    }
    catch (InterruptedException e) {
    e.printStackTrace();
    }
    catch (SQLException e) {
    e.printStackTrace();
    }
    }
    }).start();
    }
         // 将 countDownLatch1 减 1,从而使所有子线程同时并发执行
    countDownLatch1.countDown();
         // 等待 countDownLatch2 为 0 时继续执行
    countDownLatch2.await();
    long time2 = System.currentTimeMillis();

     System.out.println(</span>"pool size:"+<span style="color: rgba(0, 0, 0, 1)">jdbcPool.getSize());
    
     System.out.println(</span>"thread size: "+threadSize+" use pool :" + (time2 -<span style="color: rgba(0, 0, 0, 1)"> time1));
    

    }

}

  运行结果如下,2000 个线程同时并发运行,花费了 6065ms,是没有使用线程池的 1/4,并且显示线程执行完后池中连接数量为 74,还没有达到 100,从而知道 2000 个线程获取连接基本都是连接的复用已存在的连接,从而提高代码效率。

  


 以上就是一个简单的 java 数据库连接池的实现,当然还有很多不足之处,例如没有对连接池动态回收之类的,当并发少时,连接池中连接数量还是维持在峰值,进行获取连接时候获取的前面的连接,从而使的连接池中后面的连接获取不到,造成资源的浪费等等。。。