我的Java Web之路 - JDBC连接池(Druid)初步使用

JDBC 同时被 2 个专栏收录
2 篇文章 0 订阅
2 篇文章 0 订阅

本系列文章旨在记录和总结自己在Java Web开发之路上的知识点、经验、问题和思考,希望能帮助更多码农和想成为码农的人。
本文转发自头条号【普通的码农】的文章,大家可以关注一下,直接在今日头条的移动端APP中阅读。因为平台不同,会出现有些格式、图片、链接无效方面的问题,我尽量保持一致。
原文链接:https://www.toutiao.com/i6764181754967228931/

介绍

上篇文章我们使用了Spring JDBC中的JdbcTemplate类来简化我们的租房网应用,其中涉及到JDBC规范中的DataSource接口,当时配置的是H2数据库JDBC驱动的 org.h2.jdbcx.JdbcDataSource 这个类。
然而,直接使用它的话仍然会存在 “每次访问都要建立数据库连接,性能低下” 的问题。本篇文章我们就来解决此问题。

资源“池化”的思维

我们经常会听到连接池、内存池、对象池、线程池等概念,所谓资源的池化,对应英文单词 pool :

n.水坑,水塘,池塘(尤指自然形成的); 一滩(液体); 一小片(液体或光); 共用的资源(或资金);
v.集中资源(或材料等);

但池化应该是属于一个动作,所以用它的现在分词更合适些 pooling 。
顾名思义,即将要使用到的资源预先放到一个池子里,使用时直接从池子里取出一定量的资源即可。那这样做有什么好处呢?

显然,第一个好处是快,因为资源的申请和销毁都是需要耗费时间的,使用时直接从池子里拿预先申请好的资源,比使用时才申请,当然要快得多;而使用完之后也不用花费时间来销毁资源,只需返还给池子即可。
第二个好处也是显而易见的,就是资源的重复使用,当然这个跟第一个好处也是相关的。

不过,我们使用或者设计一个资源池的时候也会面临很多问题,比如:
• 最基本的问题,这个池子该建多大才合适呢?
• 万一池子里的资源不够用怎么办?需要继续申请资源扩大该资源池吗?
• 池子里有很多空闲的资源怎么办?需要销毁多余的空闲资源吗?
• 万一某些配置改变,需要重建资源池怎么办?
很容易看出,池化的思维本质上是一种空间换时间的思维。

本篇文章我们就初步使用连接池来进一步改造我们的租房网应用。我们之前的设计中,每一个请求到来时,如果需要访问数据库,就先建立数据库连接,访问完数据库之后再销毁该连接。这在访问量不大的时候当然没有问题,一旦访问量到达一定程度的时候,频繁建立和销毁数据库连接就会耗费大量的CPU时间,从而达到性能瓶颈。

那么我们怎么就敢肯定会这样呢?答案就是我们已经站在巨人的肩膀上了,不过我们还是可以自行验证一下,但是要费一番功夫,或者自己编写代码,或者使用某种工具,先要模拟一定的访问量,然后再监控应用中最耗费时间的部分在哪。这就属于性能测试了,有很多现成的工具可以帮我们,以后再介绍吧。

反正,我们使用连接池肯定就没错了。

JDBC规范的连接池设计

JDBC规范中设计了一个连接池的接口,即 ConnectionPoolDataSource 接口,千万不要认为这个接口是扩展 DataSource 接口的。查看此接口的定义,就会发现这两个接口没有任何关系。
DataSource:

package javax.sql;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Wrapper;

public interface DataSource extends CommonDataSource, Wrapper {

 Connection getConnection() throws SQLException;
 Connection getConnection(String username, String password) throws SQLException;
}

ConnectionPoolDataSource:

package javax.sql;

import java.sql.SQLException;

public interface ConnectionPoolDataSource extends CommonDataSource {
 
 PooledConnection getPooledConnection() throws SQLException;
 PooledConnection getPooledConnection(String user, String password) throws SQLException;
 }

原来我们使用的不采用连接池的DataSource接口实现时,JDBC应用调用DataSource接口,是直接向JDBC驱动获取Connection对象,获取的Connection对象确实代表了一个实际的物理的数据库连接:
在这里插入图片描述
而采用连接池的DataSource接口实现(可能是应用服务器,也可以是第三方的JDBC连接池实现)时,JDBC应用调用DataSource接口,则是先向该连接池获取Connection对象,如果有空闲的物理数据库连接,则直接包装成Connection对象返回即可;

否则需要继续调用 ConnectionPoolDataSource 接口来向更底层的JDBC驱动来获取物理数据库连接。因此,Connection对象代表的是一个逻辑的而非真正的数据库连接,而 ConnectionPoolDataSource 接口返回的 PooledConnection 对象代表的才是物理数据库连接。
大家也可以查阅JDBC规范,其下载参考这篇文章
在这里插入图片描述
图片来自JDBC规范

上图中间的应用服务器可以是第三方的JDBC连接池的实现,比如我们将要使用的 Druid 。

这样设计有什么好处呢?显然这样做很符合分层、职责单一的思维,因为池化显然跟建立数据库连接是两件不同的事情。可是我们的JDBC应用应该是不关心如何获取到数据库连接的,你可以从连接池中拿到一个空闲的连接,也可以每次都直接建立一个连接,这样JDBC应用就可以很灵活的进行配置,要么配置连接池实现的DataSource接口,要么配置JDBC驱动直接实现的DataSource接口。

Druid概述

JDBC连接池的实现有很多,比如Apache软件基金会旗下的Commons DBCP、C3P0、HikariCP等等。

我将要介绍和使用的是阿里巴巴开源的 Druid ,它的官方网页在 https://github.com/alibaba/druid

实际上,这不是阿里巴巴专门为 Druid 自建的网站,而是托管在一个提供版本管理和协作开发服务的网站上(这个网站功能很强大,大部分开源软件都会托管在其上,它的后台使用的是 Git 这个强大的版本管理和协作开发工具,目前是相当流行,以后我们会介绍)。
在这里插入图片描述
从其官网上可以看到 Druid 号称是为监控而生的数据库连接池。点击右侧的链接可以到达它的文档首页:
在这里插入图片描述
因为 Druid 是我们中国人开发的,所以其主要文档都是用中文写的,可以点击红色圆圈内的“中文”这个链接。

右侧是导航栏,底下还有更多项目没有展示出来,点击红色箭头所指的链接即可展示。

更多内容,大家可以自行查阅它的文档。

添加Druid的依赖

我们以后都是使用Maven来管理项目的依赖,不知Maven为何物,请参考这篇文章
我们依旧可以从Maven的中央仓库(https://mvnrepository.com/)中搜索到 Druid :
在这里插入图片描述
从这里也可以看到 Druid 的官网地址。
好,我们在租房网应用的POM文件中添加如下配置即可:

<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
 <groupId>com.alibaba</groupId>
 <artifactId>druid</artifactId>
 <version>1.1.21</version>
</dependency>

生成DataSource对象

上篇文章中,我们使用Spring IoC来生成DataSource对象的配置是这样的:

	<bean id="dataSource" class="org.h2.jdbcx.JdbcDataSource">
		<property name="url" value="jdbc:h2:~/h2db/houserenter" />
		<property name="user" value="sa" />
		<property name="password" value="" />
	</bean>

原来使用的是DataSource接口的实现是 org.h2.jdbcx.JdbcDataSource ,现在肯定是要用 Druid 对它的实现了,那么Druid的这个实现类是什么呢?我们可以去查看它的官网文档啊:
在这里插入图片描述
很明显,红色箭头所指的链接就是我们要使用的DataSource实现类的配置说明页面,进入该页面即可看到它是如何配置的:

	<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
		<property name="url" value="jdbc:h2:~/h2db/houserenter" />
		<property name="username" value="sa" />
		<property name="password" value="" />

		<property name="filters" value="stat" />

		<property name="maxActive" value="20" />
		<property name="initialSize" value="1" />
		<property name="maxWait" value="60000" />
		<property name="minIdle" value="1" />

		<property name="timeBetweenEvictionRunsMillis" value="60000" />
		<property name="minEvictableIdleTimeMillis" value="300000" />

		<property name="testWhileIdle" value="true" />
		<property name="testOnBorrow" value="false" />
		<property name="testOnReturn" value="false" />

		<property name="poolPreparedStatements" value="true" />
		<property name="maxOpenPreparedStatements" value="20" />

		<property name="asyncInit" value="true" />
	</bean>

可以看到它的参数还真够多的,不过,文档中说:

通常只需配置 url 、username 、password 、maxActive 这三项即可

显然这里有笔误,明明是四项,却写成三项!
每个参数到底是何意义呢?我们也可以在官网文档中找到答案:
在这里插入图片描述
值得关注的是,还有一个 driverClassName 参数,文档说它默认是根据 url 的值去识别和寻找JDBC驱动,因此不必配置,那我们就先不配置试试。当然,必须得确保可以找到识别出来的JDBC驱动的JAR包。
我的配置是这样的:

	<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
		<property name="url" value="jdbc:h2:~/h2db/houserenter" />
		<property name="username" value="sa" />
		<property name="password" value="" />

		<property name="maxActive" value="20" />
		<property name="initialSize" value="1" />
		<property name="maxWait" value="60000" />
		<property name="minIdle" value="1" />
	</bean>

我们当然也可以查看 com.alibaba.druid.pool.DruidDataSource 的源码来确定如何配置它,就像上篇文章配置 org.h2.jdbcx.JdbcDataSource 一样。

生成JdbcTemplate对象

上篇文章一样。

将JdbcTemplate对象注入到其他组件

上篇文章一样。

使用JdbcTemplate对象访问数据库

上篇文章一样。

总结

我之所以仍然将后面的几个步骤作为标题列举出来,却没有写任何实际内容(因为跟上篇文章是一样的,无需任何修改)。完整的代码请看下篇文章

从这个例子中,我们可以直观的体会到接口的作用、Spring IoC的作用、JDBC的 DataSource 和 ConnectionPoolDataSource 那样设计的作用。

我们只需要配置不同的实现类,就可以享受到不同的服务,一个是每次都建立数据库连接,一个是从连接池中获取数据库连接。其他代码一点都不用修改!这就是可插拔!

我们现在可以重新发布应用、启动Tomcat验证一下,完全没有问题。原来浏览器端每次访问数据库时还会感觉有点卡,现在完全没有这种感觉!这样我们就解决了因数据库访问而产生的性能低下的问题。

• 我们要充分利用资源 “池化” 的思维;
• “池化” 的思维本质上是空间换时间的思维;
• JDBC连接池的规范是这样设计的:应用调用DataSource接口获取Connection对象,再提供一个 ConnectionPoolDataSource 接口给JDBC驱动去实现;具体的连接池实现DataSource接口,同时调用 ConnectionPoolDataSource 接口向JDBC驱动获取物理的数据库连接;
• 应用只需要配置采用何种DataSource接口的实现即可;
• 其他任何使用到DataSource对象的组件,比如你的应用组件、JdbcTemplate、后续的ORM组件等等,都可以注入DataSource对象。

• 阿里的Druid连接池还是挺不错的,它还有强大的监控功能,以后再介绍。

  • 1
    点赞
  • 0
    评论
  • 1
    收藏
  • 扫一扫,分享海报

参与评论
请先登录 后发表评论~
©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值