日常開發(fā)中,數(shù)據(jù)庫連接池是個必不可少的配置,使用優(yōu)秀的數(shù)據(jù)庫連接池,可以有效的提高數(shù)據(jù)庫訪問效
率,降低連接異常等,本篇就來學(xué)習一下Spirngboot自帶連接池和阿里Druid兩個最常見的連接池
什么是HikariCP
HikariCP是由日本程序員開源的一個數(shù)據(jù)庫連接池組件,代碼非常輕量,并且速度非常的快。根據(jù)官方提供的數(shù)據(jù),在i7,開啟32個線程32個連接的情況下,進行隨機數(shù)據(jù)庫讀寫操作,HikariCP的速度是現(xiàn)在常用的C3P0數(shù)據(jù)庫連接池的數(shù)百倍。在SpringBoot2.0中,官方默認也是使用的HikariCP作為數(shù)據(jù)庫連接池,可見HikariCP連接池的目的就是為了極致的數(shù)據(jù)庫連接性能體驗,下面附上一張HikariCP和其他連接池的比較圖:

從圖中的結(jié)果可以看出來,HikariCP從性能來說的確一騎絕塵,那么HikariCP是如何做到這么極致的性能呢?主要依托于HikariCP自身所做的優(yōu)化機制
HikariCP優(yōu)化機制
字節(jié)碼精簡
HikariCP優(yōu)化了代碼,盡量減少了生成的字節(jié)碼,使得cpu可以加載更多程序代碼
優(yōu)化了攔截和代理機制
HikariCP對攔截器機制和代理機制進行了代碼優(yōu)化處理,例如Statement proxy只有100行代碼,大大減少了代碼量,只有其他連接池例如BoneCP的十分之一
自定義數(shù)組
HikariCP針對數(shù)組操作進行了自定義數(shù)組--FastStatementList,用來替代jdk的ArrayList,優(yōu)化了get、remove等方法,避免了每次調(diào)用get的時候進行范圍檢查,也避免了每次remove操作的時候會將數(shù)據(jù)從頭到尾進行掃描的性能問題
自定義集合
同樣的,針對jdk自帶的集合類,HikariCP專門封裝了無鎖的集合類型 ,用來提高在使用中的讀寫并發(fā)的效率,減少并發(fā)造成的資源競爭問題
CPU時間片算法優(yōu)化
HikariCP也對cpu時間片分配算法進行了優(yōu)化,盡可能使得一個時間片內(nèi)完成相關(guān)的操作
使用HikariCP
了解了HikariCP以后,我們開始使用吧,首先找到HikariCP的坐標:
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.7.6</version>
</dependency>
然后配置HikariCP對應(yīng)的配置文件,用來讀取/加載連接池配置:
/**
* HikariCP連接池配置
*/
@Configuration
public class DataSourceConfig {
@Value("${spring.datasource.url}")
private String dataSourceUrl;
@Value("${spring.datasource.username}")
private String user;
@Value("${spring.datasource.password}")
private String password;
@Bean
public DataSource primaryDataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(dataSourceUrl); //數(shù)據(jù)源url
config.setUsername(user); //用戶名
config.setPassword(password); //密碼
config.addDataSourceProperty("cachePrepStmts", "true"); //是否自定義配置,為true時下面兩個參數(shù)才生效
config.addDataSourceProperty("prepStmtCacheSize", "250"); //連接池大小默認25,官方推薦250-500
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); //單條語句最大長度默認256,官方推薦2048
config.addDataSourceProperty("useServerPrepStmts", "true"); //新版本MySQL支持服務(wù)器端準備,開啟能夠得到顯著性能提升
config.addDataSourceProperty("useLocalSessionState", "true");
config.addDataSourceProperty("useLocalTransactionState", "true");
config.addDataSourceProperty("rewriteBatchedStatements", "true");
config.addDataSourceProperty("cacheResultSetMetadata", "true");
config.addDataSourceProperty("cacheServerConfiguration", "true");
config.addDataSourceProperty("elideSetAutoCommits", "true");
config.addDataSourceProperty("maintainTimeStats", "false");
HikariDataSource ds = new HikariDataSource(config);
return ds;
}
}
接著我們在SpringBoot的application.properties 文件中進行配置:
server.port=8090
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.max-idle=10
spring.datasource.max-active=15
spring.datasource.max-lifetime=86430000
spring.datasource.log-abandoned=true
spring.datasource.remove-abandoned=true
spring.datasource.remove-abandoned-timeout=60
spring.datasource.initialize=false
spring.datasource.sqlScriptEncoding=UTF-8
配置完畢,此時我們啟動工程,即可看到控制臺已經(jīng)將我們配置的HikariCP數(shù)據(jù)庫連接池信息打印出來了
阿里巴巴Druid
提到大名鼎鼎的Druid連接池,相信很多人都不陌生,因為該連接池是阿里開源的優(yōu)秀的連接池,幾乎已經(jīng)成為現(xiàn)在使用最多的連接池之一。我們先打開Druid的官方github:
https://github.com/alibaba/druid
可以看到此項目已經(jīng)有19.4k的star數(shù),并且是2019最受歡迎的開源之一,經(jīng)歷過真實線上雙十一的考驗,可以說是個很成熟的開源連接池,而Druid連接池專為監(jiān)控而生,內(nèi)置強大的監(jiān)控功能,監(jiān)控特性不影響性能。功能強大,能防SQL注入,內(nèi)置Logging能診斷Hack應(yīng)用行為等。
快速使用
Druid 0.1.18 之后版本都發(fā)布到maven中央倉庫中,所以你只需要在項目的pom.xml中加上dependency就可以了,中央倉庫坐標如下:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid-version}</version>
</dependency>
這里我們使用了springBoot,由于默認支持的數(shù)據(jù)連接池只有四種:dbcp,dbcp2, tomcat, hikariCP,并不包含druid,所以我們這里也可以選擇直接使用阿里官方編寫的druid-spring-boot-starter,并且我們添加對應(yīng)的mybatis和pageHelper的依賴:
<!-- 數(shù)據(jù)庫驅(qū)動 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<!--Mybatis 分頁插件 pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<!-- Druid連接池包 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
在application.properties中 進行數(shù)據(jù)源配置:
# 數(shù)據(jù)庫訪問配置
# 主數(shù)據(jù)源,默認的
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=123456
# 下面為連接池的補充設(shè)置,應(yīng)用到上面所有數(shù)據(jù)源中
# 初始化大小,最小,最大
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
# 配置獲取連接等待超時的時間
spring.datasource.maxWait=60000
# 配置間隔多久才進行一次檢測,檢測需要關(guān)閉的空閑連接,單位是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=60000
# 配置一個連接在池中最小生存的時間,單位是毫秒
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
# 打開PSCache,并且指定每個連接上PSCache的大小
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
# 配置監(jiān)控統(tǒng)計攔截的filters,去掉后監(jiān)控界面sql無法統(tǒng)計,'wall'用于防火墻
spring.datasource.filters=stat,wall,log4j
# 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合并多個DruidDataSource的監(jiān)控數(shù)據(jù)
#spring.datasource.useGlobalDataSourceStat=true
配置完畢以后,由于這里我們使用了mybatis,所以我們還要在application.properties中配置一下mybatis相關(guān):
#mybatis
#entity掃描的包名
mybatis.type-aliases-package=com.xiaolyuh.domain.model
#Mapper.xml所在的位置
mybatis.mapper-locations=classpath*:/mybaits/*Mapper.xml
#開啟MyBatis的二級緩存
mybatis.configuration.cache-enabled=true
#pagehelper
pagehelper.helperDialect=mysql
pagehelper.reasonable=true
pagehelper.supportMethodsArguments=true
pagehelper.params=count=countSql
準備就緒后,我們來編寫一個測試類:
@RunWith(SpringRunner.class)
@SpringBootTest
public class DataSourceTests {
@Autowired
ApplicationContext applicationContext;
@Test
public void testDataSource() throws Exception {
// 獲取配置的數(shù)據(jù)源
DataSource dataSource = applicationContext.getBean(DataSource.class);
System.out.println(dataSource.getClass().getName());
}
}
將測試方法運行起來,即可在控制臺中看到對應(yīng)的數(shù)據(jù)源的輸出信息
Druid開啟監(jiān)控統(tǒng)計功能
druid最強大的功能就是自身提供了對sql的數(shù)據(jù)監(jiān)控功能,并且內(nèi)置了很多詳細的攔截器,可以實現(xiàn)多個角度的攔截處理,那么如何開啟監(jiān)控?在Druid中內(nèi)置提供了一個StatViewServlet用于展示Druid的統(tǒng)計信息,這個StatViewServlet的用途包括:
- 提供監(jiān)控信息展示的html頁面
- 提供監(jiān)控信息的JSON API
如果是ssm工程,則可以在web.xml中配置StatViewServlet,如下:
<servlet>
<servlet-name>DruidStatView</servlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DruidStatView</servlet-name>
<url-pattern>/druid/*</url-pattern>
</servlet-mapping>
配置完畢以后,啟動工程則可以按照配置的監(jiān)控地址訪問監(jiān)控信息,默認為:http://ip:port/project-name/druid/index.html
同樣的,在StatViewServlet中我們可以添加訪問密碼的設(shè)置,只需要配置Servlet的 loginUsername 和 loginPassword這兩個初始參數(shù) 即可,例如:
<!-- 配置 Druid 監(jiān)控信息顯示頁面 -->
<servlet>
<servlet-name>DruidStatView</servlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
<init-param>
<!-- 允許清空統(tǒng)計數(shù)據(jù) -->
<param-name>resetEnable</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<!-- 用戶名 -->
<param-name>loginUsername</param-name>
<param-value>druid</param-value>
</init-param>
<init-param>
<!-- 密碼 -->
<param-name>loginPassword</param-name>
<param-value>druid</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>DruidStatView</servlet-name>
<url-pattern>/druid/*</url-pattern>
</servlet-mapping>
此時訪問監(jiān)控頁面的時候就需要輸入我們設(shè)置的用戶名和密碼了,如果還想針對用戶有敏感信息配置和訪問權(quán)限控制,我們還可以配置allow和deny參數(shù),例如:
<servlet>
<servlet-name>DruidStatView</servlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
<init-param>
<param-name>allow</param-name>
<param-value>128.242.127.1/24,128.242.128.1</param-value>
</init-param>
<init-param>
<param-name>deny</param-name>
<param-value>128.242.127.4</param-value>
</init-param>
</servlet>
這里的訪問規(guī)則為:
1.deny配置優(yōu)先于allow,即deny為優(yōu)先拒絕,即使在allow中配置了白名單,但是只要存在于deny中,一樣也會被拒絕訪問
2.如果allow沒有配置,或者配置為空,默認為全部都可以訪問,不進行白名單限制
使用SpringBoot配置監(jiān)控
由于我們這里使用的是SpringBoot,所以我們僅需要在application.properties 添加配置統(tǒng)計攔截的filters:
# 配置監(jiān)控統(tǒng)計攔截的filters,去掉后監(jiān)控界面sql無法統(tǒng)計,'wall'用于防火墻
spring.datasource.druid.filters=stat,wall,log4j
這里的配置是通過別名方式配置擴展支持的插件,如下:
- 監(jiān)控統(tǒng)計用的filter:stat
- 日志用的filter:log4j
- 防御sql注入的filter:wall
接著我們需要在application.properties繼續(xù)添加WebStatFilter和StatViewServlet的配置項:
# WebStatFilter配置,說明請參考Druid Wiki,配置_配置WebStatFilter
#啟動項目后訪問 http://127.0.0.1:8080/druid
#是否啟用StatFilter默認值true
spring.datasource.druid.web-stat-filter.enabled=true
spring.datasource.druid.web-stat-filter.url-pattern=/*
spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*
#缺省sessionStatMaxCount是1000個
spring.datasource.druid.web-stat-filter.session-stat-max-count=1000
#關(guān)閉session統(tǒng)計功能
spring.datasource.druid.web-stat-filter.session-stat-enable=false
#配置principalSessionName,使得druid能夠知道當前的session的用戶是誰
#如果你session中保存的是非string類型的對象,需要重載toString方法
spring.datasource.druid.web-stat-filter.principalSessionName=xxx.user
#如果user信息保存在cookie中,你可以配置principalCookieName,使得druid知道當前的user是誰
spring.datasource.druid.web-stat-filter.principalCookieName=xxx.user
#druid 0.2.7版本開始支持profile,配置profileEnable能夠監(jiān)控單個url調(diào)用的sql列表。
spring.datasource.druid.web-stat-filter.profile-enable=false
# StatViewServlet配置,說明請參考Druid Wiki,配置_StatViewServlet配置
#啟動項目后訪問 http://127.0.0.1:8080/druid
#是否啟用StatViewServlet默認值true
spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.urlPattern=/druid/*
#禁用HTML頁面上的“Reset All”功能
spring.datasource.druid.stat-view-servlet.resetEnable=false
#用戶名
spring.datasource.druid.stat-view-servlet.loginUsername=admin
#密碼
spring.datasource.druid.stat-view-servlet.loginPassword=admin
#IP白名單(沒有配置或者為空,則允許所有訪問)
spring.datasource.druid.stat-view-servlet.allow=127.0.0.1,192.168.163.1
#IP黑名單 (存在共同時,deny優(yōu)先于allow)
spring.datasource.druid.stat-view-servlet.deny=192.168.1.73
接著我們啟動工程,訪問http://localhost/druid ,輸入配置的用戶名:admin以及密碼:admin,即可看到druid的監(jiān)控頁面:

慢sql日志打印
在開發(fā)過程中,往往會遇到sql時間過長問題,為了定位慢sql,我們往往會定義固定時長作為慢sql的時長,而Druid支持慢sql查詢,在Druid中內(nèi)置提供了一個StatFilter,用于統(tǒng)計監(jiān)控信息 ,我們可以利用這個StatFilter來統(tǒng)計慢sql:
StatFilter的別名是stat,這個別名映射配置信息保存在druid-xxx.jar!/META-INF/druid-filter.properties
我們需要在Spring中加入以下配置:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="filters" value="stat" />
</bean>
當然如果需要我們可以同時開啟多個Filter進行組合使用,中間用,隔開即可,如下:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="filters" value="stat,log4j" />
</bean>
而如果開啟慢sql的記錄,我們需要先定義slowSqlMillis 來配置sql慢查詢的標準,如下:
<bean id="stat-filter" class="com.alibaba.druid.filter.stat.StatFilter">
<property name="slowSqlMillis" value="10000" />
<property name="logSlowSql" value="true" />
</bean>
配置完畢以后,所有的超過10s的sql都會在監(jiān)控頁面的慢sql模塊記錄,可以查看具體的sql以及執(zhí)行時間等,快速定位開發(fā)過程中的慢sql
DruidDataSource配置
如果我們根據(jù)業(yè)務(wù)的不同,需要更改不同的配置,這個時候我們就需要參考DriudDataSource的配置,通用的配置項如下:
| 配置 | 缺省值 | 描述 |
|---|---|---|
| name | 如果存在多個數(shù)據(jù)源,監(jiān)控的時候可以通過名字來區(qū)分開來。如果沒有配置,將會生成一個名字,格式是:"DataSource-" + System.identityHashCode(this) | |
| url | 連接數(shù)據(jù)庫的url | |
| username | 連接數(shù)據(jù)庫的用戶名 | |
| password | 連接數(shù)據(jù)庫的密碼 | |
| driverClassName | 根據(jù)url自動識別 | 可配可不配,如果不配置druid會根據(jù)url自動識別dbType,然后選擇相應(yīng)的driverClassName |
| initialSize | 0 | 初始化時建立物理連接的個數(shù)。初始化發(fā)生在顯示調(diào)用init方法,或者第一次getConnection時 |
| maxActive | 8 | 最大連接池數(shù)量 |
| maxIdle | 8 | 已經(jīng)棄用 |
| minIdle | 最小連接池數(shù)量 | |
| maxWait | 獲取連接時最大等待時間,單位毫秒。配置了maxWait之后,缺省啟用公平鎖,并發(fā)效率會有所下降,如果需要可以通過配置useUnfairLock屬性為true使用非公平鎖 | |
| poolPreparedStatements | false | 是否緩存preparedStatement,也就是PSCache。PSCache對支持游標的數(shù)據(jù)庫性能提升巨大,比如說oracle。在mysql下建議關(guān)閉。 |
| maxPoolPreparedStatementPerConnectionSize | -1 | 要啟用PSCache,必須配置大于0,當大于0時,poolPreparedStatements自動觸發(fā)修改為true。在Druid中,不會存在Oracle下PSCache占用內(nèi)存過多的問題,可以把這個數(shù)值配置大一些,比如說100 |
| validationQuery | 用來檢測連接是否有效的sql,要求是一個查詢語句,常用select 'x'。如果validationQuery為null,testOnBorrow、testOnReturn、testWhileIdle都不會起作用。 | |
| validationQueryTimeout | 單位:秒,檢測連接是否有效的超時時間。 | |
| testOnBorrow | true | 申請連接時執(zhí)行validationQuery檢測連接是否有效,做了這個配置會降低性能。 |
| testOnReturn | false | 歸還連接時執(zhí)行validationQuery檢測連接是否有效,做了這個配置會降低性能。 |
| testWhileIdle | false | 建議配置為true,不影響性能,并且保證安全性。申請連接的時候檢測,如果空閑時間大于timeBetweenEvictionRunsMillis,執(zhí)行validationQuery檢測連接是否有效 |
| keepAlive | false | 連接池中的minIdle數(shù)量以內(nèi)的連接,空閑時間超過minEvictableIdleTimeMillis,則會執(zhí)行keepAlive操作 |
| timeBetweenEvictionRunsMillis | 60s | 有兩個含義: 1) Destroy線程會檢測連接的間隔時間,如果連接空閑時間大于等于minEvictableIdleTimeMillis則關(guān)閉物理連接。 2) testWhileIdle的判斷依據(jù),詳細看testWhileIdle屬性的說明 |
| numTestsPerEvictionRun | 30分鐘 | 已經(jīng)棄用 |
| minEvictableIdleTimeMillis | 連接保持空閑而不被驅(qū)逐的最小時間 | |
| connectionInitSqls | 物理連接初始化的時候執(zhí)行的sql | |
| exceptionSorter | 根據(jù)dbType自動識別 | 當數(shù)據(jù)庫拋出一些不可恢復(fù)的異常時,拋棄連接 |
| filters | 屬性類型是字符串,通過別名的方式配置擴展插件,常用的插件有: 監(jiān)控統(tǒng)計用的filter:stat 日志用的filter:log4j 防御sql注入的filter:wall | |
| proxyFilters | 類型是List<com.alibaba.druid.filter.Filter>,如果同時配置了filters和proxyFilters,是組合關(guān)系,并非替換關(guān)系 |
Druid常見配置與問題
除了我們已經(jīng)了解的druid常見知識以外,開發(fā)中經(jīng)常還會遇到很多其他常見需求,如開啟druid的防sql注入功能、記錄每次執(zhí)行的sql、數(shù)據(jù)庫加密、log輸出執(zhí)行的sql等常見需求,這個時候我們就可以在官方的github的文檔中查找,官方已經(jīng)給我們整理好了一些開發(fā)常見的問題,地址如下:
https://github.com/alibaba/druid/wiki/常見問題
總結(jié)
在實際開發(fā)過程中,我們往往會根據(jù)自身需求或者項目本身來選擇最適合的連接池,這里我們將常見的數(shù)據(jù)連接池從可擴展性、可靠穩(wěn)定性、性能、可運維性以及自身功能幾個方向進行了比較,可供參考:
