java基础之jdbc上篇(我的JavaWeb之路53-)
java基础之jdbc上篇(我的JavaWeb之路53-)第二个好处也是显而易见的,就是资源的重复使用,当然这个跟第一个好处也是相关的。显然,第一个好处是快,因为资源的申请和销毁都是需要耗费时间的,使用时直接从池子里拿预先申请好的资源,比使用时才申请,当然要快得多;而使用完之后也不用花费时间来销毁资源,只需返还给池子即可。v.集中资源(或材料等);但池化应该是属于一个动作,所以用它的现在分词更合适些 pooling 。顾名思义,即将要使用到的资源预先放到一个池子里,使用时直接从池子里取出一定量的资源即可。那这样做有什么好处呢?
本系列文章旨在记录和总结自己在Java Web开发之路上的知识点、经验、问题和思考,希望能帮助更多(Java)码农和想成为(Java)码农的人。目录
- 介绍
- 资源“池化”的思维
- JDBC规范的连接池设计
- Druid概述
- 添加Druid的依赖
- 生成DataSource对象
- 生成jdbcTemplate对象
- 将JdbcTemplate对象注入到其他组件
- 使用JdbcTemplate对象访问数据库
- 总结
上篇文章我们使用了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连接池还是挺不错的,它还有强大的监控功能,以后再介绍。