在上一篇文章《Spring Boot (三): ORM 框架 JPA 與連接池 Hikari》 我們介紹了 JPA 與連接池 Hikari 的整合使用,在國內(nèi)使用比較多的連接池還有一個(gè)是阿里開源的 Druid 。本篇文章我們就來聊一聊 Druid 的一些使用姿勢。
1. Druid 是什么?
我們先來看一下官方的回答:
Druid 是 Java 語言中最好的數(shù)據(jù)庫連接池。 Druid 能夠提供強(qiáng)大的監(jiān)控和擴(kuò)展功能。
說 Druid 是 Java 語言中最好的數(shù)據(jù)庫連接池,這個(gè)筆者個(gè)人覺得有些吹牛了,至少在性能上和我們上一篇介紹的 Hikari 是沒得比的,相關(guān)的性能測試在網(wǎng)上能找到很多,筆者這邊就不列舉了。但是 Druid 在其他的一些方面就做的比較出色了,功能非常豐富:
- 可以監(jiān)控?cái)?shù)據(jù)庫訪問性能, Druid 內(nèi)置提供了一個(gè)功能強(qiáng)大的StatFilter插件,能夠詳細(xì)統(tǒng)計(jì) SQL 的執(zhí)行性能,這對于線上分析數(shù)據(jù)庫訪問性能有幫助。
- 數(shù)據(jù)庫密碼加密。直接把數(shù)據(jù)庫密碼寫在配置文件中,這是不好的行為,容易導(dǎo)致安全問題。 DruidDruiver 和 DruidDataSource 都支持 PasswordCallback 。
- SQL 執(zhí)行日志, Druid 提供了不同的 LogFilter ,能夠支持 Common-Logging 、 Log4j 和 JdkLog ,你可以按需要選擇相應(yīng)的 LogFilter ,監(jiān)控你應(yīng)用的數(shù)據(jù)庫訪問情況。
- 擴(kuò)展 JDBC ,如果你要對 JDBC 層有編程的需求,可以通過 Druid 提供的 Filter 機(jī)制,很方便編寫 JDBC 層的擴(kuò)展插件。
2. Spring Boot 應(yīng)用中如何使用
目前 Druid 官方為我們提供了兩種使用依賴方式,一種是基于傳統(tǒng) Java 工程提供的依賴包, maven 坐標(biāo)如下:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.20</version>
</dependency>
還有一種是基于 Spring Boot 提供的依賴包, maven 坐標(biāo)如下:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.20</version>
</dependency>
下面的這種依賴包除了包含了上面的那種 Druid 基礎(chǔ)包,還包含了 Spring Boot 自動(dòng)配置的依賴包以及 sl4j-api ,我們在 Spring Boot 中使用 Druid ,當(dāng)然是推薦各位讀者使用第二種方式引入依賴。
3. 工程實(shí)戰(zhàn)
3.1 創(chuàng)建父工程 spring-boot-jpa-druid
父工程 pom.xml 如下:
代碼清單:spring-boot-jpa-druid/pom.xml
***
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.springcloud</groupId>
<artifactId>spring-boot-jpa-druid</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-jpa-druid</name>
<description>spring-boot-jpa-druid</description>
<properties>
<druid.version>1.1.20</druid.version>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 筆者這里使用的 Druid 依賴包是
druid-spring-boot-starter,版本號為 1.1.20。
3.2 數(shù)據(jù)庫密碼不加密的配置文件 application-pass.yml 如下:
代碼清單:spring-boot-jpa-druid/src/main/resources/application-pass.yml
***
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://192.168.0.128:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: 123456
driverClassName: com.mysql.cj.jdbc.Driver
druid:
# 連接池的配置信息
# 初始化時(shí)建立物理連接的個(gè)數(shù)
initial-size: 3
# 連接池最小連接數(shù)
min-idle: 3
# 連接池最大連接數(shù)
max-active: 20
# 獲取連接時(shí)最大等待時(shí)間,單位毫秒
max-wait: 60000
# 申請連接的時(shí)候檢測,如果空閑時(shí)間大于timeBetweenEvictionRunsMillis,執(zhí)行validationQuery檢測連接是否有效。
test-while-idle: true
# 既作為檢測的間隔時(shí)間又作為testWhileIdel執(zhí)行的依據(jù)
time-between-connect-error-millis: 60000
# 銷毀線程時(shí)檢測當(dāng)前連接的最后活動(dòng)時(shí)間和當(dāng)前時(shí)間差大于該值時(shí),關(guān)閉當(dāng)前連接
min-evictable-idle-time-millis: 30000
# 用來檢測連接是否有效的sql 必須是一個(gè)查詢語句
# mysql中為 select 'x'
# oracle中為 select 1 from dual
validation-query: select 'x'
# 申請連接時(shí)會(huì)執(zhí)行validationQuery檢測連接是否有效,開啟會(huì)降低性能,默認(rèn)為true
test-on-borrow: false
# 歸還連接時(shí)會(huì)執(zhí)行validationQuery檢測連接是否有效,開啟會(huì)降低性能,默認(rèn)為true
test-on-return: false
# 是否緩存preparedStatement,mysql5.5+建議開啟
pool-prepared-statements: true
# 當(dāng)值大于0時(shí)poolPreparedStatements會(huì)自動(dòng)修改為true
max-pool-prepared-statement-per-connection-size: 20
# 合并多個(gè)DruidDataSource的監(jiān)控?cái)?shù)據(jù)
use-global-data-source-stat: false
# 配置擴(kuò)展插件
filters: stat,wall,slf4j
# 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 定時(shí)輸出統(tǒng)計(jì)信息到日志中,并每次輸出日志會(huì)導(dǎo)致清零(reset)連接池相關(guān)的計(jì)數(shù)器。
time-between-log-stats-millis: 300000
# 配置DruidStatFilter
web-stat-filter:
enabled: true
url-pattern: '/*'
exclusions: '*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*'
# 配置DruidStatViewServlet
stat-view-servlet:
# 是否啟用StatViewServlet(監(jiān)控頁面)默認(rèn)值為false(考慮到安全問題默認(rèn)并未啟動(dòng),如需啟用建議設(shè)置密碼或白名單以保障安全)
enabled: true
url-pattern: '/druid/*'
# IP白名單(沒有配置或者為空,則允許所有訪問)
allow: 127.0.0.1,192.168.0.1
# IP黑名單 (存在共同時(shí),deny優(yōu)先于allow)
deny: 192.168.0.128
# 禁用HTML頁面上的“Reset All”功能
reset-enable: false
# 登錄名
login-username: admin
# 登錄密碼
login-password: admin
- 相關(guān)配置的含義已經(jīng)寫在注釋中了,這里有一點(diǎn)要講一下,當(dāng)我們要配置統(tǒng)計(jì)信息(包括監(jiān)控信息)
time-between-log-stats-millis輸出至日志中,合并多個(gè)DruidDataSource的監(jiān)控?cái)?shù)據(jù)use-global-data-source-stat不可開啟,否則啟動(dòng)會(huì)報(bào)錯(cuò)。 spring.datasource.druid.filters:因?yàn)?Druid 的擴(kuò)展是通過 Filter 插件的形式來開啟的,這里我們開啟了stat和wall,這倆個(gè)分別為監(jiān)控和防御 SQL 注入攻擊。 Druid 還提供了一些其他默認(rèn)的 Filter ,如下表:
| Filter類名 | 別名 |
|---|---|
| default | com.alibaba.druid.filter.stat.StatFilter |
| stat | com.alibaba.druid.filter.stat.StatFilter |
| mergeStat | com.alibaba.druid.filter.stat.MergeStatFilter |
| encoding | com.alibaba.druid.filter.encoding.EncodingConvertFilter |
| log4j | com.alibaba.druid.filter.logging.Log4jFilter |
| log4j2 | com.alibaba.druid.filter.logging.Log4j2Filter |
| slf4j | com.alibaba.druid.filter.logging.Slf4jLogFilter |
| commonlogging | com.alibaba.druid.filter.logging.CommonsLogFilter |
| wall | com.alibaba.druid.wall.WallFilter |
從名稱上可以看出來,主要是一些編碼和日志的相關(guān) Filter 。
3.3 數(shù)據(jù)庫密碼加密
在生產(chǎn)環(huán)境中,直接在配置文件中暴露明文密碼是一件非常危險(xiǎn)的事情,出于兩點(diǎn)考慮:對外,即使應(yīng)用服務(wù)被入侵,數(shù)據(jù)庫還是安全的;對內(nèi),生產(chǎn)環(huán)境的數(shù)據(jù)庫密碼理論上應(yīng)該只有 dba 知道,但是代碼都是在代碼倉庫中放著的,如果密碼沒有加密,每次發(fā)布前 dba 都需要手動(dòng)修改配置文件后再進(jìn)行打包編譯。
首先,我們需要生成數(shù)據(jù)庫密碼的密文,需要在命令行中執(zhí)行如下命令:
java -cp druid-1.0.16.jar com.alibaba.druid.filter.config.ConfigTools you_password
輸出如下:
privateKey:MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAh12hnaZuMe76Yb4pi7ogSAEMOcavmz7Blo8DYxeipxeZQhnrXngxc0gAQ6ORlofLWtDm6S7bI7wfDT2EFy/2DwIDAQABAkABMRjYK3vy4pi/vY3eFhBssd2qsI4hPsczjSTJfY7IC9Dc1f7g0axTM6Cx68tRUwv0rSnUiJ5EcDEhuD0JusSZAiEAwX1HpCTq8QgBV1WriHQC7Cd/9Qqp1V4yJeA/jdvXhbsCIQCzGS6wdTQCXDZKLvjRLeSUyTmmIqV/wckqdnpMUZ2BvQIgBIamr1tBt6OlTGKvoYB9NQLzhkrakCgk6ifltK7IytMCIBIbf67zipiafhqt+RYdD7lDRwLXCeiKzS3v4JmKvuP5AiEAr+zqD6sdXv7rWjqu50n+LXbWtNP/M4JzzO1mJOHEhoE=
publicKey:MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAIddoZ2mbjHu+mG+KYu6IEgBDDnGr5s+wZaPA2MXoqcXmUIZ6154MXNIAEOjkZaHy1rQ5uku2yO8Hw09hBcv9g8CAwEAAQ==
password:Y464AerH8tabxQg5DlkUej6gQ64KY73ahgiPyaB0vguLBLjUEEkVu6VBueiXxcnMfVjh1Nbd+lJNUTnS1a3/xg==
這里我們需要將生成的公鑰 publicKey 和密碼 password 加入配置文件中, application-decrypt.yml 如下:
代碼清單:spring-boot-jpa-druid/src/main/resources/application-decrypt.yml
***
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://192.168.0.128:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
# 加密后密文,原密碼為 123456
password: Y464AerH8tabxQg5DlkUej6gQ64KY73ahgiPyaB0vguLBLjUEEkVu6VBueiXxcnMfVjh1Nbd+lJNUTnS1a3/xg==
driverClassName: com.mysql.cj.jdbc.Driver
druid:
filter:
config:
enabled: true
connection-properties: config.decrypt=true;config.decrypt.key=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAIddoZ2mbjHu+mG+KYu6IEgBDDnGr5s+wZaPA2MXoqcXmUIZ6154MXNIAEOjkZaHy1rQ5uku2yO8Hw09hBcv9g8CAwEAAQ==
# 剩余配置省略
- 已省略部分配置,有需要的讀者可以訪問 Github 倉庫獲取。
3.4 配置文件 application.yml 如下:
代碼清單:spring-boot-jpa-druid/src/main/resources/application.yml
***
server:
port: 8080
spring:
application:
name: spring-boot-jpa-druid
profiles:
active: decrypt
jpa:
database: mysql
show-sql: true
generate-ddl: true
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
hibernate:
ddl-auto: update
properties:
hibernate:
format_sql: true
其余的測試代碼同上一篇文章《Spring Boot (三): ORM 框架 JPA 與連接池 Hikari》,有興趣的讀者可以訪問 Github 倉庫獲取,筆者這里就不一一列舉了。
4. 測試
我們在主配置文件中,選擇密碼加密的配置文件啟動(dòng),將 spring.profiles.active 配置為 decrypt ,點(diǎn)擊啟動(dòng),可以看到工程正常啟動(dòng),查看控制臺輸出日志,其中有這么一句:
2019-09-22 21:21:54.501 INFO 16972 --- [-Log-1465691120] c.a.d.p.DruidDataSourceStatLoggerImpl : {"url":"jdbc:mysql://192.168.0.128:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false","dbType":"mysql","name":"DataSource-1465691120","activeCount":0,"poolingCount":3,"poolingPeak":3,"poolingPeakTime":"2019-09-22 21:21:54","connectCount":0,"closeCount":0,"physicalConnectCount":3}
可以看到,我們配置的監(jiān)控信息輸出會(huì)在系統(tǒng)啟動(dòng)的時(shí)候先輸出一次,我們在配置文件中配置的是每5分鐘輸出一次,等十分鐘看一下控制臺的輸出信息,結(jié)果如下:
2019-09-22 21:26:54.503 INFO 16972 --- [-Log-1465691120] c.a.d.p.DruidDataSourceStatLoggerImpl : {"url":"jdbc:mysql://192.168.0.128:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false","dbType":"mysql","name":"DataSource-1465691120","activeCount":0,"activePeak":1,"activePeakTime":"2019-09-22 21:21:54","poolingCount":3,"poolingPeak":3,"poolingPeakTime":"2019-09-22 21:21:54","connectCount":2,"closeCount":2,"connectionHoldTimeHistogram":[0,0,2]}
2019-09-22 21:31:54.505 INFO 16972 --- [-Log-1465691120] c.a.d.p.DruidDataSourceStatLoggerImpl : {"url":"jdbc:mysql://192.168.0.128:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false","dbType":"mysql","name":"DataSource-1465691120","activeCount":0,"poolingCount":3,"connectCount":0,"closeCount":0}
2019-09-22 21:36:54.505 INFO 16972 --- [-Log-1465691120] c.a.d.p.DruidDataSourceStatLoggerImpl : {"url":"jdbc:mysql://192.168.0.128:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false","dbType":"mysql","name":"DataSource-1465691120","activeCount":0,"poolingCount":3,"connectCount":0,"closeCount":0}
從時(shí)間上可以看出,確實(shí)是每5分鐘會(huì)輸出一次。
打開瀏覽器訪問:http://localhost:8080/druid/ ,查看 Druid 監(jiān)控頁面,結(jié)果如圖:
我們可以進(jìn)行一些接口測試,在查看監(jiān)控頁面,可以看到所有的 SQL 都正常記錄,如圖:
同時(shí),我們看一下后臺的日志打印,是否正常打出記錄的日志,截取打印部分,如下:
2019-09-22 21:51:54.506 INFO 16972 --- [-Log-1465691120] c.a.d.p.DruidDataSourceStatLoggerImpl : {"url":"jdbc:mysql://192.168.0.128:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false","dbType":"mysql","name":"DataSource-1465691120","activeCount":0,"activePeak":1,"activePeakTime":"2019-09-22 21:47:28","poolingCount":3,"poolingPeak":3,"poolingPeakTime":"2019-09-22 21:47:28","connectCount":4,"closeCount":4,"executeCount":4,"commitCount":4,"pstmtCacheHitCount":2,"pstmtCacheMissCount":2,"startTransactionCount":4,"transactionHistogram":[0,1,2,1],"connectionHoldTimeHistogram":[0,1,0,3],"sqlList":[{"sql":"insert into user (age, nick_name, id) values (?, ?, ?)","executeCount":2,"executeMillisMax":1,"executeMillisTotal":2,"executeHistogram":[1,1],"executeAndResultHoldHistogram":[1,1],"concurrentMax":1,"updateCount":2,"updateCountMax":1,"updateHistogram":[0,2],"inTransactionCount":2},{"sql":"select usermodel0_.id as id1_0_, usermodel0_.age as age2_0_, usermodel0_.nick_name as nick_nam3_0_ from user usermodel0_ order by usermodel0_.id desc","executeCount":2,"executeMillisMax":3,"executeMillisTotal":4,"executeHistogram":[0,2],"executeAndResultHoldHistogram":[2],"concurrentMax":1,"fetchRowCount":4,"fetchRowCountMax":2,"fetchRowHistogram":[0,2],"inTransactionCount":2}]}
可以看到,日志中打印了我們執(zhí)行的 SQL 相關(guān)的信息,和我們在監(jiān)控頁面看到的信息完全一致。
至此,測試成功,篇幅原因,一些測試過程未列出,各位感興趣的讀者朋友可以自己動(dòng)手嘗試一下。
5. 示例代碼
示例代碼-Github
示例代碼-Gitee
6. 參考
《Druid 官方文檔》
|轉(zhuǎn)載請注明來源地址:蜘蛛池出租 http://www.wholesalehouseflipping.com/專注于SEO培訓(xùn),快速排名黑帽SEO https://www.heimao.wiki
