快捷搜索:  汽车  科技

java基础之jdbc上篇(我的JavaWeb之路53-)

java基础之jdbc上篇(我的JavaWeb之路53-)第二个好处也是显而易见的,就是资源的重复使用,当然这个跟第一个好处也是相关的。显然,第一个好处是快,因为资源的申请和销毁都是需要耗费时间的,使用时直接从池子里拿预先申请好的资源,比使用时才申请,当然要快得多;而使用完之后也不用花费时间来销毁资源,只需返还给池子即可。v.集中资源(或材料等);但池化应该是属于一个动作,所以用它的现在分词更合适些 pooling 。顾名思义,即将要使用到的资源预先放到一个池子里,使用时直接从池子里取出一定量的资源即可。那这样做有什么好处呢?

本系列文章旨在记录和总结自己在Java Web开发之路上的知识点、经验、问题和思考,希望能帮助更多(Java)码农和想成为(Java)码农的人。目录

  1. 介绍
  2. 资源“池化”的思维
  3. JDBC规范的连接池设计
  4. Druid概述
  5. 添加Druid的依赖
  6. 生成DataSource对象
  7. 生成jdbcTemplate对象
  8. 将JdbcTemplate对象注入到其他组件
  9. 使用JdbcTemplate对象访问数据库
  10. 总结
介绍

上篇文章我们使用了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对象确实代表了一个实际的物理的数据库连接:

java基础之jdbc上篇(我的JavaWeb之路53-)(1)

而采用连接池的DataSource接口实现(可能是应用服务器,也可以是第三方的JDBC连接池实现)时,JDBC应用调用DataSource接口,则是先向该连接池获取Connection对象,如果有空闲的物理数据库连接,则直接包装成Connection对象返回即可;

否则需要继续调用 ConnectionPoolDataSource 接口来向更底层的JDBC驱动来获取物理数据库连接。因此,Connection对象代表的是一个逻辑的而非真正的数据库连接,而 ConnectionPoolDataSource 接口返回的 PooledConnection 对象代表的才是物理数据库连接。

大家也可以查阅JDBC规范,其下载参考这篇文章。

java基础之jdbc上篇(我的JavaWeb之路53-)(2)

图片来自JDBC规范

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

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

Druid概述

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

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

实际上,这不是阿里巴巴专门为 Druid 自建的网站,而是托管在一个提供版本管理和协作开发服务的网站上(这个网站功能很强大,大部分开源软件都会托管在其上,它的后台使用的是 Git 这个强大的版本管理和协作开发工具,目前是相当流行,以后我们会介绍)。

java基础之jdbc上篇(我的JavaWeb之路53-)(3)

从其官网上可以看到 Druid 号称是为监控而生的数据库连接池。点击右侧的链接可以到达它的文档首页:

java基础之jdbc上篇(我的JavaWeb之路53-)(4)

因为 Druid 是我们中国人开发的,所以其主要文档都是用中文写的,可以点击红色圆圈内的“中文”这个链接。

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

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

添加Druid的依赖

我们以后都是使用Maven来管理项目的依赖,不知Maven为何物,请参考这篇文章。

我们依旧可以从Maven的中央仓库(https://mvnrepository.com/)中搜索到 Druid :

java基础之jdbc上篇(我的JavaWeb之路53-)(5)

从这里也可以看到 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的这个实现类是什么呢?我们可以去查看它的官网文档啊:

java基础之jdbc上篇(我的JavaWeb之路53-)(6)

很明显,红色箭头所指的链接就是我们要使用的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 这三项即可

显然这里有笔误,明明是四项,却写成三项!

每个参数到底是何意义呢?我们也可以在官网文档中找到答案:

java基础之jdbc上篇(我的JavaWeb之路53-)(7)

值得关注的是,还有一个 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连接池还是挺不错的,它还有强大的监控功能,以后再介绍。

猜您喜欢: