数据库连接池原理

本文介绍了JDBC,它是用于执行SQL语句的Java API,但频繁建立、关闭连接会降低系统性能。数据库连接池可解决这些问题,它能分配、管理和释放连接,有资源重用、响应快等优势。还实现了简单连接池,并列举了Java中多种开源数据库连接池。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、JDBC介绍

1.jdbc的定义
JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。
2.jdbc的用途
简单地说,JDBC 可做三件事:与数据库建立连接、发送 操作数据库的语句并处理结果。
这里写图片描述
下列代码段给出了以上三步的基本示例:

Connection con = DriverManager.getConnection("jdbc:odbc:wombat","login",
"password");
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM Table1");
while (rs.next()) {
int x = rs.getInt("a");
String s = rs.getString("b");
float f = rs.getFloat("c");
}

这里写图片描述
3.操作jdbc的问题
jdbc每一次建立连接都非常耗时,对于一个复杂的数据库应用,频繁的建立、关闭连接,会极大的减低系统的性能,因为对于连接的使用成了系统性能的瓶颈。另外,每次由线程建立连接、关闭连接,总连接数的控制等等都是非常繁琐且容易出错,对数据库连接的管理能显著影响到整个应用程序的伸缩性和健壮性,影响到程序的性能指标。针对这些问题,需要一个统一管理的框架,防止这些复杂性的扩散。而数据库连接池就是解决这些问题最有效的方式。

二、数据库连接池基本原理

1.数据库连接池基本概念
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。这项技术能明显提高对数据库操作的性能。
2.连接池的优势
数据库连接池技术带来的优势:
2.1. 资源重用
由于数据库连接得到重用,避免了频繁创建、释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增进了系统运行环境的平稳性(减少内存碎片以及数据库临时进程/线程的数量)。
2.2. 更快的系统响应速度
数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而缩减了系统整体响应时间。
2.3. 新的资源分配手段
对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接的配置,实现数据库连接池技术。某一应用最大可用数据库连接数的限制,避免某一应用独占所有数据库资源。
2.4. 统一的连接管理,避免数据库连接泄漏
在较为完备的数据库连接池实现中,可根据预先的连接占用超时设定,强制收回被占用连接。从而避免了常规数据库连接操作中可能出现的资源泄漏。
3.数据库连接池的工作原理
这里写图片描述
关键的配置参数功能:
这里写图片描述

三、实现一个简单的连接池

基于上面的理解,我们实现一个简单的连接池。

/*
1.线程安全
2.有空闲连接的数量
3.有正在使用的连接数量
*/
6
public class PoolConfig{

  /*
  数据库jdbc属性

  */

  private String driverName;//数据库的驱动类

  private String url;//数据库的连接地址

  private String userName;//数据库用户名

  private String password;//数据库密码


  /*

  连接池配置

  */

  private int minConn = 1;//空闲集合中最少连接数

  private int maxConn = 5;//空闲集合最多的连接数

  private int initConn = 5;//初始连接数

  private int maxActiveConn = 10;//整个连接池(数据库)允许的最大连接数

  private int waitTime = 1000;//单位毫秒,连接数不够时,线程等待的时间

  private boolean isCheck = false;//数据库连接池是否启用自检机制(间隔一段时间检测连接池状态)

  private long checkPeriod = 1000*30;//自检周期



  //以下省略getter、setter方法...

}
db.properties配置文件

jdbc.driverName = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncode=true&characterEncoding=utf-8
jdbc.userName=root
jdbc.password=root
DBUtil.java数据库工具包(数据库开发者调用)

public class DBUtil{

  /* 静态数据库配置实体对象,程序运行时加载进内存 */

  private static PoolConfig config = new PoolConfig(); 



  static{//初始化加载配置文件

      Properties prop = new Properties();

      try{

          prop.load(DBUtil.class.getClassLoader().

                     getResourceAsStream

                    ("com/mypath/db/db.properties"));

          //获取配置文件信息传入config连接池配置对象

          config.setDriverName(prop.getProperty("jdbc.driverName"));

          config.setUrl(prop.getProperty("jdbc.url"));

          config.setUserName(prop.getProperty("jdbc.userName"));

          config.setPassword(prop.getProperty("jdbc.password"));

          //反射加载这个驱动(使用的是JDBC的驱动加载方式)

          Class.forName(config.getDriverName());

      }catch(IOException e){

          e.printStackTrace();

      }

  }



  private static ConnectionPool connPool = new ConnectionPool(config);



  public static connection gerConnection(){

      return connPool.getConnection();

  }



  public static connection gerCurrentConnection(){

      return connPool.getCurrentConnection();

  }



  public static void closeConnection(Connection conn){

      connPool.releaseConnection(conn);

  }

}
ConnectionPool.java数据库连接池对象(根据配置创建对应连接池)


public class ConnectionPool{

 private PoolConfig config;//连接池的配置对象

 private int count;//记录连接池的连接数

 private boolean isActive;//连接池是否被激活

 //空闲连接集合

 private Vector<Connection> freeConn = new Vector<Connection>();

 //正在使用的连接集合

 private Vector<Connection> userConn = new Vector<Connection>();

 //同一个线程无论请求多少次都使用同一个连接(使用ThreadLocal确保)

 //每一个线程都私有一个连接

 private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();

 /*

 初始化连接池配置

 */

 public ConnectionPool(PoolConfig config){

     this.config = config;

 }



 /*

 数据库连接池初始化

 */

 public void init(){

     for(int i=0;i<config.getInitConn();i++){//建立初始连接

         //获取连接对象

         Connection conn;

         try{

             conn = getNewConnection();

             freeConn.add(conn);

             count++;

         }catche(SQLException e){

             e.printStackTrace();

         }

         isActive = true;//连接池激活

     }

 }



 /*

 获取新数据库连接

 */

 private synchronized Connection getNewConnection() throws SQLException{

     Connection conn = null;

     conn = DriverManager.getConnection(config.getUrl(),

                                       config.getUserName(),

                                       config.getPassword());

     return conn;

 }



 /*

 从连接池获取连接

 */

 public synchronized Conenction getConnection(){

     Connection conn = null;

     //当前连接总数小于配置的最大连接数才去获取

     if(count<config.getMaxActiveConn()){

         //空闲集合中有连接数

         if(freeConn.size()>0){

             conn = freeConn.get(0);//从空闲集合中取出

             freeConn.remove(0);//移除该连接

         }else{

             conn = getNewConnection();//拿到新连接

             count++;

         }

         if(isEnable(conn)){

             useConn.add(conn);//添加到已经使用的连接

         }else{

             count--;

             conn = getConnection();//递归调用到可用的连接

         }

     }else{//当达到最大连接数,只能阻塞等待

         wait(config.getWaitTime());//线程睡眠了一段时间

         conn = getConnection();//递归调用

     }catch(Exception e){

         e.printStackTrace();

     }

     //将获取的conn设置到本地变量ThreadLocal

     threadLocal.set(conn);

     return conn;

 }



 /*

 把用完的连接放回连接池集合Vector中

 */

 public synchronized void releaseConnection(Connection conn){

     if(isEnable(conn)){

         if(freeConn.size()<config.getMaxConn()){//空闲连接数没有达到最大

             freeConn.add(conn);//放回集合

         }else{

             conn.close();

         }

     }

     useConn.remove(conn);

     count--;

     threadLocal.remove();

     notifyAll();//放回连接池后说明有连接可用,唤醒阻塞的线程获取连接

 }



 /*

 获取当前线程的本地变量连接

 */

 public Connection getCurrentConnection(){

     return threadLocal.get();    

 }



 /*

 判断该连接是否可用

 */

 private boolean isEnable(Connection conn){

     if(conn == null){

         return false;

     }

     if(conn.isClosed()){

         return false;

     }

     return true;

 }

}

四、市面上常用的连接池

在Java中开源的数据库连接池有以下几种 :
1、C3P0:是一个开放源代码的JDBC连接池,它在lib目录中与Hibernate [2] 一起发布,包括了实现jdbc3和jdbc2扩展规范说明的Connection 和Statement 池的DataSources 对象。
2、Proxool:是一个Java SQL Driver驱动程序,提供了对选择的其它类型的驱动程序的连接池封装。可以非常简单的移植到现存的代码中,完全可配置,快速、成熟、健壮。可以透明地为现存的JDBC驱动程序增加连接池功能。
3、Jakarta DBCP:DBCP是一个依赖Jakartacommons-pool对象池机制的数据库连接池。DBCP可以直接的在应用程序中使用。
4、DDConnectionBroker:是一个简单、轻量级的数据库连接池。
5、DBPool:是一个高效、易配置的数据库连接池。它除了支持连接池应有的功能之外,还包括了一个对象池,使用户能够开发一个满足自己需求的数据库连接池。
6、XAPool:是一个XA数据库连接池。它实现了javax.sql.XADataSource并提供了连接池工具。
7、Primrose:是一个Java开发的数据库连接池。当前支持的容器包括Tomcat4&5、Resin3与JBoss3。它同样也有一个独立的版本,可以在应用程序中使用而不必运行在容器中。Primrose通过一个WEB接口来控制SQL处理的追踪、配置,以及动态池管理。在重负荷的情况下可进行连接请求队列处理。
8、SmartPool:是一个连接池组件,它模仿应用服务器对象池的特性。SmartPool能够解决一些临界问题如连接泄漏(connection leaks)、连接阻塞、打开的JDBC对象(如Statements、PreparedStatements)等。SmartPool的特性包括:
支持多个pool
自动关闭相关联的JDBC对象
在所设定time-outs之后察觉连接泄漏
追踪连接使用情况
强制启用最近最少用到的连接
把SmartPool“包装”成现存的一个pool
9、MiniConnectionPoolManager:是一个轻量级JDBC数据库连接池。它只需要Java1.5(或更高)并且没有依赖第三方包。
10、BoneCP:是一个快速、开源的数据库连接池。帮用户管理数据连接,让应用程序能更快速地访问数据库。比C3P0/DBCP连接池速度快25倍。
11、Druid:Druid不仅是一个数据库连接池,还包含一个ProxyDriver、一系列内置的JDBC组件库、一个SQL Parser。
支持所有JDBC兼容的数据库,包括Oracle、MySql、Derby、Postgresql、SQL Server、H2等。
Druid针对Oracle和MySql做了特别优化,比如:
Oracle的PS Cache内存占用优化
MySql的ping检测优化
Druid提供了MySql、Oracle、Postgresql、SQL-92的SQL的完整支持,这是一个手写的高性能SQL Parser,支持Visitor模式,使得分析SQL的抽象语法树很方便。
简单SQL语句用时10微秒以内,复杂SQL用时30微秒。
通过Druid提供的SQL Parser可以在JDBC层拦截SQL做相应处理,比如说分库分表、审计等。Druid防御SQL注入攻击的WallFilter,就是通过Druid的SQL Parser分析语义实现的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值