Java数据库连接池

一、关于数据库连接池

一个普通的java程序,要查询数据库的数据,基本流程是这样的:

查询数据基本流程.jpg

可以看到,进行一次查询,要进行很多次网络交互,这样的缺点是:

  1. 网络IO多;
  2. 响应时间长,导致QPS降低;
  3. 频繁创建连接和关闭连接,浪费数据库资源,影响服务器性能。

什么是数据库连接池?
顾名思义,就是一个池子,里面放着数据库连接,应用服务需要的时候就去池子里面拿,用完之后归还给池子。数据库连接池负责分配、管理、释放数据库连接,它允许应用服务重复使用数据库连接,而非重新建立。

使用连接池之后,流程是这样的:

使用连接池之后,查询流程.jpg

数据库的连接创建和关闭连接均由连接池来实现。这样做的优点有:

  1. 减少网络开销;
  2. 提升数据库性能;

那数据库连接池是如何管理池子中的数据库连接的呢?

  1. 应用启动时,根据配置的最小连接数,在连接池将创建此数目的数据库连接放到池中。
  2. 应用访问时,首先查看连接池中是否有空闲连接,如果存在空闲连接,则将连接分配给客户使用;如果没有空闲连接,则查看当前所开的连接数是否已经达到最大连接数,如果没达到就重新创建一个连接给请求的客户;如果达到就按设定的最大等待时间进行等待,如果超出最大等待时间,则抛出异常给客户。 当客户释放数据库连接时,先判断该连接的引用次数是否超过了规定值,如果超过就从连接池中删除该连接,否则保留等待再次使用。
流程图.jpg
  1. 应用关闭时,关闭池中所有连接,释放所有资源。
释放资源.jpg

此过程会涉及的配置:最小连接数,最大连接数,最长等待时间,均配置在应用服务配置文件中。

二、DataSource的实现

现在很多WEB服务器(Weblogic, WebSphere, Tomcat)都提供了DataSoruce的实现,即连接池的实现。通常我们把DataSource的实现,按其英文含义称之为数据源,数据源中都包含了数据库连接池的实现。也有一些开源组织提供了数据源的独立实现:

  1. DBCP 数据库连接池
  2. C3P0 数据库连接池

在使用了数据库连接池之后,在项目的实际开发中就不需要编写连接数据库的代码了,直接从数据源获得数据库的连接。

DBCP数据源

DBCP 是 Apache 软件基金组织下的开源连接池实现,要使用DBCP数据源,需要应用程序应在系统中增加如下两个 jar 文件:

  • Commons-dbcp.jar:连接池的实现
  • Commons-pool.jar:连接池实现的依赖库

Tomcat 的连接池正是采用该连接池来实现的。该数据库连接池既可以与应用服务器整合使用,也可由应用程序独立使用。

在应用程序中加入dbcp连接池

  1. 导入相关jar包commons-dbcp-1.2.2.jar、commons-pool.jar
  2. 在类目录下加入dbcp的配置文件:dbcpconfig.properties

dbcpconfig.properties的配置信息如下:

#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcstudy
username=root
password=XDP

#<!-- 初始化连接 -->
initialSize=10

#最大连接数量
maxActive=50

#<!-- 最大空闲连接 -->
maxIdle=20

#<!-- 最小空闲连接 -->
minIdle=5

#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=60000

#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;] 
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=UTF8

#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true

#driver default 指定由连接池所创建的连接的只读(read-only)状态。
#如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
defaultReadOnly=

#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED

在获取数据库连接的工具类的静态代码块中创建池

//数据库连接工具类
public class JdbcDBCP {
    /**
     * 在java中,编写数据库连接池需实现java.sql.DataSource接口,每一种数据库连接池都是DataSource接口的实现
     * DBCP连接池就是java.sql.DataSource接口的一个具体实现
     */
    private static DataSource ds = null;
    //在静态代码块中创建数据库连接池
    static{
        try{
            //加载dbcpconfig.properties配置文件
            InputStream in = JdbcDBCP.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
            Properties prop = new Properties();
            prop.load(in);
            //创建数据源
            ds = BasicDataSourceFactory.createDataSource(prop);
        }catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }
    
   //从数据源中获取数据库连接
    public static Connection getConnection() throws SQLException{
        //从数据源中获取数据库连接
        return ds.getConnection();
    }
    
 /**
   * 释放资源,释放的资源包括Connection数据库连接对象,负责执行SQL命 
   *令的Statement对象,存储查询结果的ResultSet对象
   */
    public static void release(Connection conn,Statement st,ResultSet rs){
        if(rs!=null){
            try{
                //关闭存储查询结果的ResultSet对象
                rs.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
            rs = null;
        }
        if(st!=null){
            try{
                //关闭负责执行SQL命令的Statement对象
                st.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
        
        if(conn!=null){
            try{
                //将Connection连接对象还给数据库连接池
                conn.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

三、配置Tomcat数据源

在实际开发中,我们有时候还会使用服务器提供给我们的数据库连接池,比如我们希望Tomcat服务器在启动的时候可以帮我们创建一个数据库连接池,那么在应用程序中就不需要手动去创建数据库连接池,直接使用Tomcat服务器创建好的数据库连接池即可。要想让Tomcat服务器在启动的时候创建一个数据库连接池,那么需要简单配置一下Tomcat服务器。

JNDI技术简介

JNDI(Java Naming and Directory Interface),Java命名和目录接口,它对应于J2SE中的javax.naming包。这套API的主要作用在于:它可以把Java对象放在一个容器中(JNDI容器),并为容器中的java对象取一个名称,以后程序想获得Java对象,只需通过名称检索即可。其核心API为Context,它代表JNDI容器,其lookup方法为检索容器中对应名称的对象。

Tomcat服务器创建的数据源是以JNDI资源的形式发布的,所以说在Tomat服务器中配置一个数据源实际上就是在配置一个JNDI资源,通过查看Tomcat文档,我们知道使用如下的方式配置tomcat服务器的数据源:

<Context>
  <Resource name="jdbc/datasource" auth="Container"
            type="javax.sql.DataSource" username="root" password="XDP"
            driverClassName="com.mysql.jdbc.Driver" 
            url="jdbc:mysql://localhost:3306/jdbcstudy"
            maxActive="8" maxIdle="4"/>
</Context>

服务器创建好数据源之后,我们的应用程序又该怎么样得到这个数据源呢?Tomcat服务器创建好数据源之后是以JNDI的形式绑定到一个JNDI容器中的,可以把JNDI想象成一个大大的容器,我们可以往这个容器中存放一些对象、一些资源,JNDI容器中存放的对象和资源都会有一个独一无二的名称。应用程序想从JNDI容器中获取资源时,只需要告诉JNDI容器要获取的资源的名称,JNDI根据名称去找到对应的资源后返回给应用程序。我们平时做javaEE开发时,服务器会为我们的应用程序创建很多资源,比如request对象,response对象,服务器创建的这些资源有两种方式提供给我们的应用程序使用:

  1. 第一种是通过方法参数的形式传递进来,比如我们在Servlet中写的doPost和doGet方法中使用到的request对象和response对象就是服务器以参数的形式传递给我们的。
  2. 第二种就是JNDI的方式,服务器把创建好的资源绑定到JNDI容器中去,应用程序想要使用资源时,就直接从JNDI容器中获取相应的资源即可。

对于上面的name="jdbc/datasource"数据源资源,在应用程序中可以用如下的代码去获取:

Context initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
dataSource = (DataSource)envCtx.lookup("jdbc/datasource");

此种配置下,数据库的驱动jar文件需放置在tomcat的lib下。

配置Tomcat数据源

  1. 在Web项目的WebRoot目录下的META-INF目录创建一个context.xml文件
  2. 在context.xml文件配置tomcat服务器的数据源
<Context>
   <Resource 
       name="jdbc/datasource" 
       auth="Container"
       type="javax.sql.DataSource" 
       username="root" 
       password="XDP"
       driverClassName="com.mysql.jdbc.Driver" 
       url="jdbc:mysql://localhost:3306/jdbcstudy"
       maxActive="8" 
       maxIdle="4"/>
</Context>
  1. 将数据库的驱动jar文件需放置在tomcat的lib下
  2. 在获取数据库连接的工具类的静态代码块中获取JNDI容器中的数据源
//数据库连接工具类
public class JdbcJNDI {
    
    private static DataSource ds = null;
    //在静态代码块中创建数据库连接池
    static{
        try{
             //初始化JNDI
            Context initCtx = new InitialContext();
             //得到JNDI容器
            Context envCtx = (Context) initCtx.lookup("java:comp/env");
             //从JNDI容器中检索name为jdbc/datasource的数据源
            ds = (DataSource)envCtx.lookup("jdbc/datasource");
        }catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }
    
    //从数据源中获取数据库连接
    public static Connection getConnection() throws SQLException{
        //从数据源中获取数据库连接
        return ds.getConnection();
    }
    
    /**
    *  释放资源,
    * 释放的资源包括Connection数据库连接对象,负责执行SQL命令的Statement对象,存储查询结果的ResultSet对象
    */ 
    public static void release(Connection conn,Statement st,ResultSet rs){
        if(rs!=null){
            try{
                //关闭存储查询结果的ResultSet对象
                rs.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
            rs = null;
        }
        if(st!=null){
            try{
                //关闭负责执行SQL命令的Statement对象
                st.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
        
        if(conn!=null){
            try{
                //将Connection连接对象还给数据库连接池
                conn.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

四、Java常见数据库连接池性能比较

目前,流行的Java数据库连接池有HikariCP,druid,tomcat-jdbc,dbcp,c3p0。性能从高到低顺序排列(单从性能角度看)。下图是HikariCP官网给出的性能对比:

性能对比.jpg

HikariCP是目前最快的Java数据库连接池,spring boot 2.x已经使用HikariCP作为默认的数据库连接池,足见其优秀。

五、HikariCP的优越性

HikariCP为什么这么快呢?

  1. 优化并精简字节码:使用Java字节码修改类库Javassist来生成委托实现动态代理,JDK Proxy生成的字节码更少。
  2. 使用更好的并发集合类ConcurrentBag。
  3. 使用FastList替代ArrayList。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,367评论 6 512
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,959评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,750评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,226评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,252评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,975评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,592评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,497评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,027评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,147评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,274评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,953评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,623评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,143评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,260评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,607评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,271评论 2 358