池化思想是咱們項目開發過程當中的一種很是重要的思想,如整數池,字符串池,對象池、鏈接池、線程池等都是池化思想的一種應用,都是經過複用對象,以減小因建立和釋放對象所帶來的資源消耗,進而來提高系統性能。例如Integer對象的內部池應用,代碼以下:java
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 } }
目開發過程當中應用程序與數據庫交互時,「得到鏈接」或「釋放鏈接」是很是消耗系統資源的兩個過程,頻繁地進行數據庫鏈接的創建和關閉會極大影響系統的性能,若多線程併發量很大,這樣耗時的數據庫鏈接就可能讓系統變得卡頓。由於TCP鏈接的建立開支十分昂貴,而且數據庫所能承載的TCP併發鏈接數也有限制,針對這種場景,數據庫鏈接池應運而生。以下圖所示:mysql
思考:假如如今是讓你去設計一個鏈接池,你會從什麼角度進行設計?
第一:物理存儲結構(基於什麼結構去存儲數據)
第二:基於什麼算法從池中取鏈接?
第三:基於什麼算法從池中移除鏈接?
第四:當池中沒有鏈接時,基於什麼方式處理鏈接請求?
第五:池是能夠共享,咱們須要考慮池在訪問的時併發安全?算法
在系統初始化的時候,在內存中開闢一片空間,將必定數量的數據庫鏈接做爲對象存儲在對象池裏,並對外提供數據庫鏈接的獲取和歸還方法。用戶訪問數據庫時,並非創建一個新的鏈接,而是從數據庫鏈接池中取出一個已有的空閒鏈接對象;使用完畢歸還後的鏈接也不會立刻關閉,而是由數據庫鏈接池統一管理回收,爲下一次借用作好準備。若是因爲高併發請求致使數據庫鏈接池中的鏈接被借用完畢,其餘線程就會等待,直到有鏈接被歸還。整個過程當中,鏈接並不會關閉,而是源源不斷地循環使用,有借有還。數據庫鏈接池還能夠經過設置其參數來控制鏈接池中的初始鏈接數、鏈接的上下限數,以及每一個鏈接的最大使用次數、最大空閒時間等,也能夠經過其自身的管理機制來監視數據庫鏈接的數量、使用狀況等。spring
Java官方,爲了在應用程序中更好的應用鏈接池技術,定義了一套數據源規範,例如javax.sql.DataSource接口,基於這個接口,不少團隊或我的建立了不一樣的鏈接池對象。而後咱們的應用程序中經過耦合與DataSource接口,即可以方便的切換不一樣廠商的鏈接池。Java項目中經過鏈接池獲取鏈接的一個基本過程,以下圖所示:sql
在上圖中,用戶經過DataSource對象的getConnection()方法,獲取一個鏈接。假如池中有鏈接,則直接將鏈接返回給用戶。假如池中沒有鏈接,則會調用Dirver(驅動,由數據庫廠商進行實現)對象的connect方法從數據庫獲取,拿到鏈接之後,能夠將鏈接在池中放一份,而後將鏈接返回給調用方。鏈接需求方再次須要鏈接時,能夠從池中獲取,用完之後再還給池對象。數據庫
數據庫鏈接池在Java數據庫相關中間件產品羣中,應該算是底層最基礎的一類產品,做爲企業應用開發必不可少的組件,無數天才們爲咱們貢獻了一個又一個的優秀產品,它們有的隨時代發展,功成身退,有的則還在不斷迭代,老而彌堅,更有新生代產品,或性能無敵,或功能全面。目前市場上常見的鏈接池有DBCP、C3P0,DRUID,HikariCP等。
打開mysql控制檯,而後按以下步驟執行goods.sql文件。
第一步:登陸mysql。segmentfault
mysql –uroot –proot
第二步:設置控制檯編碼方式。api
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());
第一步:基於IDEA建立項目Module,如圖所示:
第二步:添加依賴
1) mysql數據庫驅動依賴。
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
2) spring對象jdbc支持(此時會默認幫咱們下載HiKariCP鏈接池)。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency>
打開application.properties配置文件,添加以下內容(必寫)。
spring.datasource.url=jdbc:mysql:///dbgoods?serverTimezone=GMT%2B8&characterEncoding=utf8 spring.datasource.username=root spring.datasource.password=root
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
單元測試API設計及應用分析,如圖所示:
在項目中添加單元測試類及測試方法,代碼以下:
package com.cy.pj.common.datasource; import java.sql.SQLException; import javax.sql.DataSource; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest public class DataSourceTests { @Autowired private DataSource dataSource; @Test public void testConnection() throws Exception{ System.out.println(dataSource.getConnection()); } }
在當前測試類中咱們須要:
基於HikariCP,藉助JDBC技術訪問商品庫中的數據。
基於業務,進行API設計,如圖所示:
基於業務需求,進行商品查詢過程的的時序圖設計,如圖所示:
第一步:定義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); } } }
對測試過程當中出現的問題進行記錄,分析,總結.
總之,數據庫鏈接池的爲咱們的項目開發及運行帶來了不少優勢,具體以下:
因爲數據庫鏈接獲得複用,減小了大量建立和關閉鏈接帶來的開銷,也大大減小了內存碎片和數據庫臨時進程、線程的數量,使得總體系統的運行更加平穩。
使用了數據庫鏈接池之後,因爲資源重用,大大減小了頻繁關閉鏈接的開銷,大大下降了TIME_WAIT的出現頻率。
數據庫鏈接池在應用初始化的過程當中通常都會提早準備好一些數據庫鏈接,業務請求能夠直接使用已經建立的鏈接,而不須要等待建立鏈接的開銷。初始化數據庫鏈接配合資源重用,使得數據庫鏈接池能夠大大縮短系統總體響應時間。
數據庫鏈接池做爲一款中間件,用戶能夠自行配置鏈接的最小數量、最大數量、最大空閒時間、獲取鏈接超時間、心跳檢測等。另外,用戶也能夠結合新的技術趨勢,增長數據庫鏈接池的動態配置、監控、故障演習等一系列實用的功能。