一.背景分析

我们访问数据库时,需要通过TCP协议与数据库建立连接,连接使用完以后要释放连接,TCP协议是一个面向连接的协议,而建立连接需要三次握手,释放连接需要四次挥手,这个过程是比较耗时的,假如频繁访问数据库,每次都是直接与数据建立连接,会带来很大的性能问题,对于这样的问题如何解决呢?连接池就此诞生了。

二.什么是连接池?

连接池是池化思想的一种应用,基于亨元模式做了落地的实现,就是在内存中开辟一块区域,来存储创建好的连接,让这此连接可以得到重用,进而提高数据库的访问性能。

三.Java中连接池的实现?

在java中所有连接池的实现都必须遵守java中的一个设计规范,此规范为javax.sql.DataSource,通过这个规范获取连接池的具体实现,进而取到连接,实现与数据库通讯。

什么是HikariCP

HikariCP是由日本程序员开源的一个数据库连接池组件,代码非常轻量,并且速度非常快。根据官方提供的数据,在i7,开启32个连接的情况下,进行随机数据库读写操作,单从性能角度看,性能从高到低分别为:HikariCP,druid,tomcat-jdbc,dbcp,c3p0;HikariCP的速度 是目前常用连接池中最快的。
HikariCP是SpringBoot2.0默认的连接池,全世界使用范围也非常广,不过对于大部分业务来说,使用哪一款连接池,要看业务的需求,自由选择。
下图是HikariCP官网给出的性能对比:
image.png

SpringBoot 工程中HikariCP的入门实现

具体应用步骤分析:

准备工作:数据初始化

第一步:cmd登录mysql
mysql -uroot -proot
第二步:调协控制台编码方式
set names utf8;
第三步:执行goods.sql文件
source d:/goods.sql
goods文件内容如下:
drop database if exists dbgoods;
create database dbgoods default character set utf8;
use dbgoods;
create table tb_goods(
 id bigint primary key auto_increment,
 name varchar(100) not null,
 remark text,
 createdTime datetime not null
)engine=InnoDB;
insert into tb_goods values (null,'java','very good',now());
insert into tb_goods values (null,'mysql','RDBMS',now());
insert into tb_goods values (null,'Oracle','RDBMS',now());
insert into tb_goods values (null,'java','very good',now());
insert into tb_goods values (null,'mysql','RDBMS',now());
insert into tb_goods values (null,'Oracle','RDBMS',now());
insert into tb_goods values (null,'java','very good',now());
第四步:创建项目module

image.png

第五步:在pom.xml文件中添加项目依赖(mysql,driver,spring data jdbc)
<dependency>
     <groupId>mysql</groupId>
     <artifactId>mysql-connector-java</artifactId>
     <scope>runtime</scope>
</dependency>
<dependency>
     <groupId>mysql</groupId>
     <artifactId>mysql-connector-java</artifactId>
     <scope>runtime</scope>
</dependency>
第六步:在application.properties配置文件中配置连接参数(url,username,password...)
spring.main.banner-mode=off
spring.datasource.url=jdbc:mysql:///dbgoods?serverTimezone=GMT%2B8&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
其他额外配置(可不配置)
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=15
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.pool-name=DatebookHikariCP
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.connection-test-query=SELECT 1
第七步:编写单元测试类,对连接池进行单元测试

单元测试API设计及应用分析,如图所示:
image.png

在项目中添加单元测试类及测试方法,代码如下:

package com.cy.pj.common;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
@SpringBootTest
public class DataSourceTests {
    /**DataSource为java中定义的一个连接池规范,所有连接池
 * 产品中必须定义一个这个规范的实现
 */
 @Autowired
 private DataSource dataSource; //请问这个dataSource指向的具体对象是谁
 @Test
 public void testGetConnection() throws SQLException {
        //获取dataSource变量指向的对象的具体类型的名字
 //com.zaxxer.hikari.HikariDataSource
 System.out.println(dataSource.getClass().getName());
 //请问,通过dataSource获取连接的大概过程是怎样的?
 //1.通过dataSource获取连接池(连接池不存在时则创建池)-HikariPool-->ConnectionBag-->CopyOnWriteArrayList
 //2.底层基于jdbc获取与数据库的连接,并将连接存储到池中
 //3.返回池中连接.
 Connection conn = dataSource.getConnection();
 System.out.println(conn); //HikariProxyConnection@1865982601
 }
}

测试BUG分析

数据库不存在,如图所示:

image.png

类编译错误,DataSource为javax.sql包中的类型,如图所示:

image.png

连接错误:数据库连接不上,如图所示:

image.png

基于HikariCP实现JDBC操作(练习)

业务分析

基于HikariCP,借助JDBC技术访问商品闸中的数据

API架构设计

API设计,如图所示:
image.png

业务时序图分析

基于业务需求,进行商品查询过程的时序图设计,如图所示:
image.png

业务代码设计及实现

第一步:定义GoodsDao接口

package com.cy.pj.goods.dao;
import java.util.List;
import java.util.Map;
/**
 * 商品模块数据访问层接口
 */
public interface GoodsDao {
    /**
 * 查询所有商品信息,将每一行记录存储到一个map对象,然后将多个存储到list集合.
 */ List<Map<String,Object>> findGoods();
}

第二步:创建GoodsDao接口实现类

package com.cy.pj.goods.dao;
/**
 * 此对象为一个商品数据层访问对象,现在要求在此类中定义一个方法,这个方法基于JDBC从从数据库获取商品信息,并将其封装到map集合,要求一个行记录一个map对象(key为表中字段名,值为字段名对应的值),多个map存储到list集合. @Repository此注解通常用于描述数据层实现类对象,本质上就是一个特殊的@Component, 都是要交给spring框架管理的一个Bean对象
 */
@Repository
public class DefaultGoodsDao implements  GoodsDao{
       @Autowired
       private DataSource dataSource;//hikariCP
       /**查询商品信息,一行记录映射为内存中的一个map对象*/
       public List<Map<String,Object>> findGoods(){
           Connection conn=null;//java.sql.*
           Statement stmt=null;
           ResultSet rs=null;
           String sql="select * from tb_goods";
           //1.获取连接(从连接池获取)
           try {
               conn=dataSource.getConnection();
               //2.创建statement对象
               stmt=conn.createStatement();
               //3.发送sql
               rs=stmt.executeQuery(sql);
               //4.处理结果
               List<Map<String,Object>> list=new ArrayList<>();
               while(rs.next()){//循环一次取一行,一行记录映射为一个map对象
                  list.add( rowMap(rs));//将存储了一行记录的map对象再存储到list集合
               }
               return list;
           }catch (SQLException e){
               e.printStackTrace();
               throw new RuntimeException(e);//转换为非检查异常(编译时不检测的异常)
           }finally{
               //5. 释放资源
               close(rs,stmt,conn);
           }
       }

定义行映射方法

       private Map<String,Object> rowMap(ResultSet rs)throws SQLException{
           Map<String,Object> rowMap=new HashMap<>();
           //方法1映射
           //rowMap.put("id",rs.getInt("id"));
           //rowMap.put("name",rs.getString("name"));
           //rowMap.put("remark",rs.getString("remark"));
           //rowMap.put("createdTime",rs.getTimestamp("createdTime"));
           //方法2映射
           ResultSetMetaData rsmd=rs.getMetaData();//获取元数据(包括表中的字段名)
           int columnCount=rsmd.getColumnCount();//获取列的数量
           for(int i=0;i<columnCount;i++){
               rowMap.put(rsmd.getColumnLabel(i+1),rs.getObject(rsmd.getColumnLabel(i+1)));
               //getColumnLabel(i+1);获取表中字段名或字段名对应的别名
           }
           return rowMap;
       }

定义释放资源的方法

       private void close(ResultSet rs,Statement stmt,Connection conn){
           if(rs!=null)try{rs.close();}catch(Exception e){e.printStackTrace();}
           if(stmt!=null)try{stmt.close();}catch(Exception e){e.printStackTrace();}
           //这里的连接是返回到了池中
           if(conn!=null)try{conn.close();}catch(Exception e){e.printStackTrace();}
       }
}

测试代码的编写及运行

定义单元测试类,并对其查询过程进行单元测试,例如:

package com.cy.pj.goods.dao;


@SpringBootTest
public class GoodsDaoTests {

      @Autowired
      private GoodsDao goodsDao;

      @Test
      void testFindGoods(){
          List<Map<String,Object>> list= goodsDao.findGoods();
          for(Map<String,Object> map:list){
              System.out.println(map);
          }
      }

}

FAQ

1.Java中与数据建立连接需要什么?

数据库驱动

2.这个数据库驱动的设计需要遵守什么规范吗?

JDBC

3.当我们通过JDBC API获取到一个连接以后,应用结束我们会将连接返回支池中吗?

4.连接池在应用时有什么弊端吗?

会带来一定的内存开销,以空间换时间

5.假如现在让你云设计一个连接池,你会考虑哪些问题?
存储结构

数组--随机访问效率比较高,增删比较慢
链表--适合随机插入和删除,查询比较慢

算法

FIFO(先进先出),FILO(先进后出)

线程安全

线程安全(锁-锁的粒度会影响性能和并发)

参数的设计

最多有多少连接,多余的连接什么时间被释放

6.程序中DataSource变量在运行时指向的具体对象是谁?
7.程序中基于DataSource对象获取连接的基本过程是怎样的?
8.HikariCP连接产品中具体的连接池对象是谁?
9.HikariCP连接斌的运行性能为什么好,它在哪此方面做了优化?
10.为什么说建立连接和释放连接是一个耗时操作?

建立连接时会通过TCP/IP协议(建立连接三次握手、释放连接四次挥手)

总结(Summary)

总之,数据库连接池的为我们的项目开发及运行带来了很多优点,具体如下:

资源重用更佳。

由于数据库连接得到复用,减少了大量创建和关闭连接带来的开销,也大大减少了内存碎片和数据库临时进程、线程的数量,使得整体系统的运行更加平稳。

系统调优更简便。

使用了数据库连接池以后,由于资源重用,大大减少了频繁关闭连接的开销,大大降低了TIME_WAIT的出现频率。

系统响应更快。

数据库连接池在应用初始化的过程中一般都会提前准备好一些数据库连接,业务请求可以直接使用已经创建的连接,而不需要等待创建连接的开销。初始化数据库连接配合资源重用,使得数据库连接池可以大大缩短系统整体响应时间。

连接管理更灵活。

数据库连接池作为一款中间件,用户可以自行配置连接的最小数量、最大数量、最大空闲时间、获取连接超时间、心跳检测等。另外,用户也可以结合新的技术趋势,增加数据库连接池的动态配置、监控、故障演习等一系列实用的功能。


JayX
4 声望0 粉丝