SpringBoot强化篇(二)--HikariCP连接池

池化思想分析

池化思想是我们项目开发过程中的一种非常重要的思想,如整数池,字符串池,对象池、连接池、线程池等都是池化思想的一种应用,都是通过复用对象,以减少因创建和释放对象所带来的资源消耗,进而来提升系统性能。例如Integer对象的内部池应用,代码如下:

package com.cy.java.pool;
public class TestInteger01 {
    public static void main(String[] args) {
        Integer n1=100;//Integer.valueOf(100) 编译时优化
        Integer n2=100;
        Integer n3=200;
        Integer n4=200;//池中没有则new Integer(200)
        System.out.println(n1==n2);//true
        System.out.println(n3==n4);//false 
    }
    
}

Integer对象的内部池范围在-128~127之间,所以n1,n2指向的是池中数据,所以两者地址相同。

数据库连接池简介

背景分析

开发过程中应用程序与数据库交互时,“获得连接”或“释放连接”是非常消耗系统资源的两个过程,频繁地进行数据库连接的建立和关闭会极大影响系统的性能,若多线程并发量很大,这样耗时的数据库连接就可能让系统变得卡顿。因为TCP连接的创建开支十分昂贵,并且数据库所能承载的TCP并发连接数也有限制,针对这种场景,数据库连接池应运而生。如下图所示:

思考:假如现在是让你去设计一个连接池,你会从什么角度进行设计?

第一:物理存储结构(基于什么结构去存储数据)
第二:基于什么算法从池中取连接?
第三:基于什么算法从池中移除连接?
第四:当池中没有连接时,基于什么方式处理连接请求?
第五:池是可以共享,我们需要考虑池在访问的时并发安全?

连接池原理分析

在系统初始化的时候,在内存中开辟一片空间,将一定数量的数据库连接作为对象存储在对象池里,并对外提供数据库连接的获取和归还方法。用户访问数据库时,并不是建立一个新的连接,而是从数据库连接池中取出一个已有的空闲连接对象;使用完毕归还后的连接也不会马上关闭,而是由数据库连接池统一管理回收,为下一次借用做好准备。如果由于高并发请求导致数据库连接池中的连接被借用完毕,其他线程就会等待,直到有连接被归还。整个过程中,连接并不会关闭,而是源源不断地循环使用,有借有还。数据库连接池还可以通过设置其参数来控制连接池中的初始连接数、连接的上下限数,以及每个连接的最大使用次数、最大空闲时间等,也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。

Java中的连接池

Java官方,为了在应用程序中更好的应用连接池技术,定义了一套数据源规范,例如javax.sql.DataSource接口,基于这个接口,很多团队或个人创建了不同的连接池对象。然后我们的应用程序中通过耦合与DataSource接口,便可以方便的切换不同厂商的连接池。Java项目中通过连接池获取连接的一个基本过程,如下图所示:

在上图中,用户通过DataSource对象的getConnection()方法,获取一个连接。假如池中有连接,则直接将连接返回给用户。假如池中没有连接,则会调用Dirver(驱动,由数据库厂商进行实现)对象的connect方法从数据库获取,拿到连接以后,可以将连接在池中放一份,然后将连接返回给调用方。连接需求方再次需要连接时,可以从池中获取,用完以后再还给池对象。

数据库连接池在Java数据库相关中间件产品群中,应该算是底层最基础的一类产品,作为企业应用开发必不可少的组件,无数天才们为我们贡献了一个又一个的优秀产品,它们有的随时代发展,功成身退,有的则还在不断迭代,老而弥坚,更有新生代产品,或性能无敌,或功能全面。目前市场上常见的连接池有DBCP、C3P0,DRUID,HikariCP等。
SpringBoot工程下HikariCP整合测试

数据初始化

打开mysql控制台,然后按如下步骤执行goods.sql文件。
第一步:登录mysql。

mysql –uroot –proot

第二步:设置控制台编码方式。

set names utf8;

第三步:执行goods.sql文件(切记不要打开文件复制到mysql客户端运行)。

source d:/goods.sql

其中goods.sql文件内容如下:

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并添加相关依赖

第一步:基于IDEA创建项目Module,如图所示:

第二步:添加依赖
1) mysql数据库驱动依赖。


    mysql
    mysql-connector-java
    runtime

2) spring对象jdbc支持(此时会默认帮我们下载HiKariCP连接池)。


        org.springframework.boot
        spring-boot-starter-jdbc

配置HikariCP连接池

application.properties配置文件

#spring datasource
spring.datasource.url=jdbc:mysql:///dbgoods?serverTimezone=GMT%2B8&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=1234

hikariCP 其它额外配置(可选),代码如下(具体配置不清晰的可自行百度):

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

HikariCP 连接池测试

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

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

package com.cy.pj.common.datasource;

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.SQLException;

@SpringBootTest
public class DataSourceTests {
    /**
     * FAQ?
     * 1)DataSource 由谁定义?Java官方(甲骨文)
     * 2)DataSource 定义了什么?定义了从数据库或连接池获取连接的connection的一种规范
     * 3)我们为什么要耦合于DataSource?类与类之间存在依赖(耦合)时,尽量依赖(耦合)于抽象规范,然后基于规范的实现获取连接
     * 4)程序运行时这个变量指向的对象类型是谁?HikariDataSource,你怎么知道是这个类型?(断点,日志)
     */
    @Autowired
    private DataSource dataSource;

    @Test
    void testGetConnection() throws SQLException {
        //获取datasource对象对应的具体类型是什么?
        System.out.println(dataSource.getClass().getName());//com.zaxxer.hikari.HikariDataSource
        //通过datasource获取连接的一个过程是怎样的?
        System.out.println(dataSource.getConnection());//HikariProxyConnection@971404566 wrapping com.mysql.cj.jdbc.ConnectionImpl@77010a30
    }
}

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

业务分析

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

API架构设计

基于业务,进行API设计,如图所示:

业务时序图分析

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

业务代码设计及实现

第一步:定义GoodsDao接口,例如:

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

第二步:创建GoodsDao接口实现类,代码如下:

package com.cy.pj.goods.dao;
//公司域名(com.cy)+项目名(Project-pj)+模块(goods)+层(Dao)

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 此对象为一个商品数据层访问对象,现在要求在此类中定义一个方法,这个方法用于从数据库获取商品信息
 * 并将其封装到map集合,要求一行记录一个map对象(key为u表中字段名,值为字段名对应的值), 多个map存储到list集合
 *
 * @Repository 此诸结通常用于描述数据层实现类对象,本质上就是一个特殊的@Component,都是要交给Spring框架管理的一个Bean对象
 *
 */
@Repository
public class DefaultGoodsDao implements GoodsDao{

    @Autowired
    private DataSource dataSource;//hikariCP
    /*查询商品信息*/
    public List> findGoods() {
        //1.获取连接(从连接池中获取)
        Connection conn=null;//java.sql*
        Statement stmt=null;
        ResultSet rs=null;
        String sql="select  * from tb_goods";
        try {
            conn=dataSource.getConnection();
            //2.创建statement对象
            stmt = conn.createStatement();
            //3.发送sql
            rs=stmt.executeQuery(sql);
            //4.处理结果
            List> 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 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();}
    }
    private Map rowMap(ResultSet rs) throws SQLException{
        Map rowMap=new HashMap<>();
        ResultSetMetaData rsmd = rs.getMetaData();
        int columnCount = rsmd.getColumnCount();
        for (int i = 1; i<=columnCount;i++){
            rowMap.put(rsmd.getColumnName(i), rs.getObject(rsmd.getColumnName(i)));
        }
        return rowMap;

    }

}

测试代码的编写及运行

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

package com.cy.pj.goods.dao;

@SpringBootTest
public class GoodsDaoTests {

      @Autowired
      private GoodsDao goodsDao;

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

}

总结(Summary)

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

  • 资源重用更佳。

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

  • 系统调优更简便。

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

  • 系统响应更快。

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

  • 连接管理更灵活。

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

你可能感兴趣的:(连接池)