Srping Boot工程中對HikariCP鏈接池的整合

一.背景分析

咱們訪問數據庫時,須要經過TCP協議與數據庫創建鏈接,鏈接使用完之後要釋放鏈接,TCP協議是一個面向鏈接的協議,而創建鏈接須要三次握手,釋放鏈接須要四次揮手,這個過程是比較耗時的,假如頻繁訪問數據庫,每次都是直接與數據創建鏈接,會帶來很大的性能問題,對於這樣的問題如何解決呢?鏈接池就此誕生了。java

二.什麼是鏈接池?

鏈接池是池化思想的一種應用,基於亨元模式作了落地的實現,就是在內存中開闢一塊區域,來存儲建立好的鏈接,讓這此鏈接能夠獲得重用,進而提升數據庫的訪問性能。mysql

三.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.pngspring

在項目中添加單元測試類及測試方法,代碼以下:sql

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.pngapi

業務時序圖分析

基於業務需求,進行商品查詢過程的時序圖設計,如圖所示:
image.png數組

業務代碼設計及實現

第一步:定義GoodsDao接口tomcat

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的出現頻率。

系統響應更快。

數據庫鏈接池在應用初始化的過程當中通常都會提早準備好一些數據庫鏈接,業務請求能夠直接使用已經建立的鏈接,而不須要等待建立鏈接的開銷。初始化數據庫鏈接配合資源重用,使得數據庫鏈接池能夠大大縮短系統總體響應時間。

鏈接管理更靈活。

數據庫鏈接池做爲一款中間件,用戶能夠自行配置鏈接的最小數量、最大數量、最大空閒時間、獲取鏈接超時間、心跳檢測等。另外,用戶也能夠結合新的技術趨勢,增長數據庫鏈接池的動態配置、監控、故障演習等一系列實用的功能。

相關文章
相關標籤/搜索