Druid 介紹
Druid 是阿里巴巴開源平臺上的一個項目,整個項目由數據庫連接池、插件框架和 SQL 解析器組成,該項目主要是爲了擴展 JDBC 的一些限制,可以讓程序員實現一些特殊的需求,比如向密鑰服務請求憑證、統計 SQL 信息、SQL 性能收集、SQL 注入檢查、SQL 翻譯等,程序員可以通過定製來實現自己需要的功能。
Druid 首先是一個數據庫連接池,但它不僅僅是一個數據庫連接池,還包含了一個 ProxyDriver,一系列內置的 JDBC 組件庫,一個 SQL Parser。在 Java 的世界中 Druid 是監控做的最好的數據庫連接池,在功能、性能、擴展性方面,也有不錯的表現。
Druid 可以做什麼
- 替換其他 Java 連接池,Druid 提供了一個高效、功能強大、可擴展性好的數據庫連接池。
- 可以監控數據庫訪問性能,Druid 內置提供了一個功能強大的 StatFilter 插件,能夠詳細統計 SQL 的執行性能,這對於線上分析數據庫訪問性能有很大幫助。
- 數據庫密碼加密。直接把數據庫密碼寫在配置文件中,這是不好的行爲,容易導致安全問題,DruidDruiver 和 DruidDataSource 都支持 PasswordCallback。
- SQL 執行日誌,Druid 提供了不同的 LogFilter,能夠支持 Common-Logging、Log4j 和 JdkLog,可以按需要選擇相應的 LogFilter,監控應用的數據庫訪問情況。
- 擴展 JDBC,如果你要對 JDBC 層有編程的需求,可以通過 Druid 提供的 Filter 機制,很方便編寫 JDBC 層的擴展插件。
Spring Boot 集成 Druid
非常令人高興的是,阿里爲 Druid 也提供了 Spring Boot Starter 的支持。官網這樣解釋:Druid Spring Boot Starter 用於幫助你在 Spring Boot 項目中輕鬆集成 Druid 數據庫連接池和監控。
Druid Spring Boot Starter 主要做了哪些事情呢?其實這個組件包很簡單,主要提供了很多自動化的配置,按照 Spring Boot 的理念對很多內容進行了預配置,讓我們在使用的時候更加的簡單和方便。
MyBatis 中使用 Druid 作爲連接池
在前面課程中的 spring-boot-mybatis-annotation 上去集成。
引入依賴包
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
- druid-spring-boot-starter 的最新版本爲 1.1.10,會自動依賴 Druid 相關包。
application 配置
Druid Spring Boot Starter 配置屬性的名稱完全遵照 Druid,可以通過 Spring Boot 配置文件來配置 Druid 數據庫連接池和監控,如果沒有配置則使用默認值。
# 實體類包路徑
mybatis.type-aliases-package=com.neo.model
spring.datasource.type: com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 初始化大小、最小、最大連接數
spring.datasource.druid.initial-size=3
spring.datasource.druid.min-idle=3
spring.datasource.druid.max-active=10
# 配置獲取連接等待超時的時間
spring.datasource.druid.max-wait=60000
# 監控後臺賬號和密碼
spring.datasource.druid.stat-view-servlet.login-username=admin
spring.datasource.druid.stat-view-servlet.login-password=admin
# 配置 StatFilter
spring.datasource.druid.filter.stat.log-slow-sql=true
spring.datasource.druid.filter.stat.slow-sql-millis=2000
在以前項目的基礎上,增加了對 Druid 連接池的配置,以及 SQL 監控的配置,druid-spring-boot-starter 默認情況下開啓 StatFilter 的監控功能。Druid Spring Boot Starter 不限於對以上配置屬性提供支持,DruidDataSource 內提供 setter 方法的可配置屬性都將被支持。
更多配置內容請參考 druid-spring-boot-starter。
配置完成後,直接啓動項目訪問地址:http://localhost:8080/druid,就會出現 Druid 監控後臺的登錄頁面,輸入賬戶和密碼後,就會進入首頁。
首頁會展示項目使用的 JDK 版本、數據庫驅動、JVM 相關統計信息。根據上面的菜單可以看出 Druid 的功能非常強大,支持數據源、SQL 監控、SQL 防火牆、URI 監控等很多功能。
我們這裏重點介紹一下 SQL 監控,具體的展示信息如下:
這裏的 SQL 監控會將項目中具體執行的 SQL 打印出來,展示此 SQL 執行了多少次、每次返回多少數據、執行的時間分佈是什麼。這些功能非常的實用,方便我們在實際生產中查找出慢 SQL,最後對 SQL 進行調優。
從這個例子可發現,使用 Spring Boot 集成 Druid 非常的簡單,只需要添加依賴,簡單配置就可以。
MyBatis + Druid 多數據源
接下來爲大家介紹 MyBatis 多數據源中是如何使用 Druid 數據庫連接池的。
配置文件
首先我們需要配置兩個不同的數據源:
spring.datasource.druid.one.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.one.url = jdbc:mysql://localhost:3306/test1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.druid.one.username = root
spring.datasource.druid.one.password = root
spring.datasource.druid.two.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.two.url = jdbc:mysql://localhost:3306/test2?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.druid.two.username = root
spring.datasource.druid.two.password = root
第一個數據源以 spring.datasource.druid.one.* 爲前綴連接數據庫 test1,第二個數據源以 spring.datasource.druid.two.* 爲前綴連接數據庫 test2。
強烈注意:Spring Boot 2.X 版本不再支持配置繼承,多數據源的話每個數據源的所有配置都需要單獨配置,否則配置不會生效。
# StatViewServlet 配置
spring.datasource.druid.stat-view-servlet.login-username=admin
spring.datasource.druid.stat-view-servlet.login-password=admin
# 配置 StatFilter
spring.datasource.druid.filter.stat.log-slow-sql=true
spring.datasource.druid.filter.stat.slow-sql-millis=2000
# Druid 數據源 1 配置
spring.datasource.druid.one.initial-size=3
spring.datasource.druid.one.min-idle=3
spring.datasource.druid.one.max-active=10
spring.datasource.druid.one.max-wait=60000
# Druid 數據源 2 配置
spring.datasource.druid.two.initial-size=6
spring.datasource.druid.two.min-idle=6
spring.datasource.druid.two.max-active=20
spring.datasource.druid.two.max-wait=120000
filter 和 stat 作爲 Druid 的公共信息配置,其他數據源的配置需要各個數據源單獨配置。
注入多數據源
首先爲兩個數據源創建不同的 Mapper 包路徑,將以前的 UserMapper 複製到包 com.neo.mapper.one 和 com.neo.mapper.two 路徑下,並且分別重命名爲 UserOneMapper、UserTwoMapper。
定義一個 MultiDataSourceConfig 類,對兩個不同的數據源進行加載:
@Configuration
public class MultiDataSourceConfig {
@Primary
@Bean(name = "oneDataSource")
@ConfigurationProperties("spring.datasource.druid.one")
public DataSource dataSourceOne(){
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "twoDataSource")
@ConfigurationProperties("spring.datasource.druid.two")
public DataSource dataSourceTwo(){
return DruidDataSourceBuilder.create().build();
}
}
必須指明一個爲默認的主數據源,使用註解:@Primary。加載配置兩個數據源的 DataSourceConfig 和前面課程中 MyBatis 多數據源使用的配置一致沒有變化。
注意:在多數據源的情況下,我們不需要再啓動類添加 @MapperScan("com.xxx.mapper") 的註解。
測試使用
以上所有的配置內容都完成後,啓動項目訪問這個地址:http://localhost:8080/druid,單擊數據源查看數據庫連接信息。
如果數據源沒有信息,先訪問地址:http://localhost:8080/getUsers,用來觸發數據庫連接。在沒有 SQL 使用的情況下,頁面監控不到數據源的配置信息,SQL 監控頁面也監控不到 SQL 的執行。
顯示效果如下:
摘錄自數據源1的顯示信息
Keyword | value | 解釋 |
---|---|---|
連接地址 | jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf-8 | JDBC 連接字符串 |
初始化連接大小 | 3 | 連接池建立時創建的初始化連接數 |
最小空閒連接數 | 3 | 連接池中最小的活躍連接數 |
最大連接數 | 10 | 連接池中最大的活躍連接數 |
MaxWait | 10000 | 配置獲取連接等待超時的時間 |
摘錄自數據源2的顯示信息
Keyword | value | 解釋 |
---|---|---|
連接地址 | jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf-8 | JDBC 連接字符串 |
初始化連接大小 | 6 | 連接池建立時創建的初始化連接數 |
最小空閒連接數 | 6 | 連接池中最小的活躍連接數 |
最大連接數 | 20 | 連接池中最大的活躍連接數 |
MaxWait | 120000 | 配置獲取連接等待超時的時間 |
通過這兩個數據源的連接信息來看,兩個數據源的配置信息已經生效。
Spring Data JPA 中使用 Druid 作爲連接池
Spring Data JPA 集成 Druid 的方式和 MyBatis 大體相同。
引入相關依賴包:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
添加 Web 依賴是因爲需要在啓動的時候保持容器運行,同時在項目中添加了 Web 訪問,內容如下:
@RestController
public class UserController {
@Autowired
private UserRepository userRepository;
@RequestMapping("/getUsers")
public List<User> getUsers() {
List<User> users=userRepository.findAll();
return users;
}
}
內容比較簡單獲取所有的用戶信息並展示出來。
Application 中添加以下信息:
# 初始化大小、最小、最大鏈接數
spring.datasource.druid.initial-size=3
spring.datasource.druid.min-idle=3
spring.datasource.druid.max-active=10
# 配置獲取連接等待超時的時間
spring.datasource.druid.max-wait=60000
# StatViewServlet 配置
spring.datasource.druid.stat-view-servlet.login-username=admin
spring.datasource.druid.stat-view-servlet.login-password=admin
# 配置 StatFilter
spring.datasource.druid.filter.stat.log-slow-sql=true
spring.datasource.druid.filter.stat.slow-sql-millis=2000
好了,這樣就成功的在 JPA 項目中配置好了 Druid 的使用。啓動項目先訪問地址:http://localhost:8080/getUsers,再訪問 http://localhost:8080/druid,查看 SQL 執行記錄,如下:
會發現有 create table addres... 和 drop table if exist... 這樣的 SQL 語句,這是因爲我們將 JPA 的策略設置爲 create,spring.jpa.properties.hibernate.hbm2ddl.auto=create,意味着每次重啓的時候對會重新創建表,方便我們在測試的時候使用。
JPA + Druid + 多數據源
因爲 Druid 官方還沒有針對 Spring Boot 2.0 進行優化,在某些場景下使用就會出現問題,比如在 JPA 多數據源的情況下直接使用 Druid 提供的 druid-spring-boot-starter 就會報錯,既然 druid-spring-boot-starter 不支持,那麼我們就使用 Druid 的原生包進行封裝。
在前面示例項目 spring-boot-multi-Jpa 的基礎上進行改造。
添加依賴
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>-->
刪掉對 druid-spring-boot-starter 包的依賴,添加 Druid 的依賴包,添加 log4j 的原因是因爲 Druid 依賴於 log4j 打印日誌。
多數據源配置
配置文件我們做這樣的設計,將多個數據源相同配置抽取出來共用,每個數據源個性配置信息單獨配置。
數據庫1的配置,以 spring.datasource.druid.one 開頭:
spring.datasource.druid.one.url=jdbc:mysql://localhost:3306/test1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.druid.one.username=root
spring.datasource.druid.one.password=root
spring.datasource.druid.one.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.one.initialSize=3
spring.datasource.druid.one.minIdle=3
spring.datasource.druid.one.maxActive=10
數據庫2的配置,以 spring.datasource.druid.two 開頭:
spring.datasource.druid.two.url=jdbc:mysql://localhost:3306/test2?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.druid.two.username=root
spring.datasource.druid.two.password=root
spring.datasource.druid.two.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.two.initialSize=6
spring.datasource.druid.two.minIdle=20
spring.datasource.druid.two.maxActive=30
多數據源的共同配置,以 spring.datasource.druid 開頭,是多個數據源的公共配置項。
配置獲取連接等待超時的時間
spring.datasource.druid.maxWait=60000
#配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒
spring.datasource.druid.timeBetweenEvictionRunsMillis=60000
#配置一個連接在池中最小生存的時間,單位是毫秒
spring.datasource.druid.minEvictableIdleTimeMillis=600000
spring.datasource.druid.maxEvictableIdleTimeMillis=900000
spring.datasource.druid.validationQuery=SELECT 1 FROM DUAL
#y檢測連接是否有效
spring.datasource.druid.testWhileIdle=true
#是否在從池中取出連接前進行檢驗連接池的可用性
spring.datasource.druid.testOnBorrow=false
#是否在歸還到池中前進行檢驗連接池的可用性
spring.datasource.druid.testOnReturn=false
# 是否打開 PSCache,
spring.datasource.druid.poolPreparedStatements=true
#並且指定每個連接上 PSCache 的大小
spring.datasource.druid.maxPoolPreparedStatementPerConnectionSize=20
#配置監控統計攔截的 filters
spring.datasource.druid.filters=stat,wall,log4j
#通過 connectProperties 屬性來打開 mergeSQL 功能,慢 SQL 記錄
spring.datasource.druid.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=600
更多的配置信息請參考這裏。
我們定義一個 DruidConfig 來加載所有的公共配置項,如下:
@Component
@ConfigurationProperties(prefix="spring.datasource.druid")
public class DruidConfig {
protected String url;
protected String username;
protected String password;
protected String driverClassName;
protected int initialSize;
protected int minIdle;
protected int maxActive;
protected int maxWait;
protected int timeBetweenEvictionRunsMillis;
protected long minEvictableIdleTimeMillis;
protected long maxEvictableIdleTimeMillis;
protected String validationQuery;
protected boolean testWhileIdle;
protected boolean testOnBorrow;
protected boolean testOnReturn;
protected boolean poolPreparedStatements;
protected int maxPoolPreparedStatementPerConnectionSize;
protected String filters;
protected String connectionProperties;
// 省略 getter setter
}
再定義一個 DruidOneConfig 來加載數據源 1 的配置項,並繼承 DruidConfig:
@Component
@ConfigurationProperties(prefix="spring.datasource.druid.one")
public class DruidOneConfig extends DruidConfig{
private String url;
private String username;
private String password;
private String driverClassName;
private int initialSize;
private int minIdle;
private int maxActive;
// 省略 getter setter
}
再定義一個 DruidTwoConfig 來加載數據源 2 的配置項並繼承 DruidConfig,代碼和 DruidOneConfig 類基本一致。
啓動時加載
創建類 DruidDBConfig 在啓動的時候注入配置的多數據源信息。
@Configuration
public class DruidDBConfig {
@Autowired
private DruidConfig druidOneConfig;
@Autowired
private DruidConfig druidTwoConfig;
@Autowired
private DruidConfig druidConfig;
}
在類中創建 initDruidDataSource() 方法,初始化 Druid 數據源各屬性。各個數據庫的個性化配置從 config 對讀取,公共配置項從 druidConfig 對象獲取。
private DruidDataSource initDruidDataSource(DruidConfig config) {
DruidDataSource datasource = new DruidDataSource();
datasource.setUrl(config.getUrl());
datasource.setUsername(config.getUsername());
datasource.setPassword(config.getPassword());
datasource.setDriverClassName(config.getDriverClassName());
datasource.setInitialSize(config.getInitialSize());
datasource.setMinIdle(config.getMinIdle());
datasource.setMaxActive(config.getMaxActive());
// common config
datasource.setMaxWait(druidConfig.getMaxWait());
datasource.setTimeBetweenEvictionRunsMillis(druidConfig.getTimeBetweenEvictionRunsMillis());
datasource.setMinEvictableIdleTimeMillis(druidConfig.getMinEvictableIdleTimeMillis());
datasource.setMaxEvictableIdleTimeMillis(druidConfig.getMaxEvictableIdleTimeMillis());
datasource.setValidationQuery(druidConfig.getValidationQuery());
datasource.setTestWhileIdle(druidConfig.isTestWhileIdle());
datasource.setTestOnBorrow(druidConfig.isTestOnBorrow());
datasource.setTestOnReturn(druidConfig.isTestOnReturn());
datasource.setPoolPreparedStatements(druidConfig.isPoolPreparedStatements());
datasource.setMaxPoolPreparedStatementPerConnectionSize(druidConfig.getMaxPoolPreparedStatementPerConnectionSize());
try {
datasource.setFilters(druidConfig.getFilters());
} catch (SQLException e) {
logger.error("druid configuration initialization filter : {0}", e);
}
datasource.setConnectionProperties(druidConfig.getConnectionProperties());
return datasource;
}
啓動時調用 initDruidDataSource() 方法構建不同的數據源。
@Bean(name = "primaryDataSource")
public DataSource dataSource() {
return initDruidDataSource(druidOneConfig);
}
@Bean(name = "secondaryDataSource")
@Primary
public DataSource secondaryDataSource() {
return initDruidDataSource(druidTwoConfig);
}
下面通過不同的數據源構建 entityManager,最後注入到 Repository 的邏輯和以前一樣,變化的地方只是在數據源構建和開啓監控頁面。
開啓監控頁面
因爲我們使用了原生的 Druid 包,因此需要手動開啓監控、配置統計相關內容。
@Configuration
public class DruidConfiguration {
@Bean
public ServletRegistrationBean<StatViewServlet> druidStatViewServlet() {
ServletRegistrationBean<StatViewServlet> servletRegistrationBean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
servletRegistrationBean.addInitParameter("loginUsername", "admin");
servletRegistrationBean.addInitParameter("loginPassword", "admin");
servletRegistrationBean.addInitParameter("resetEnable", "false");
return servletRegistrationBean;
}
@Bean
public FilterRegistrationBean<WebStatFilter> druidStatFilter() {
FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(new WebStatFilter());
filterRegistrationBean.setName("DruidWebStatFilter");
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
}
配置完成後,重啓啓動項目訪問地址 http://localhost:8080/druid/sql.html 就可以看到有兩個數據源的 SQL 操作語句,證明多數據源 SQL 監控配置成功。
到此 JPA + Druid + 多數據源的集成完成了。
總結
Druid 是一款非常優秀的數據庫連接池開源軟件,使用 Druid 提供的 druid-spring-boot-starter 可以非常簡單地對 Druid 進行集成。Druid 提供了很多預置的功能,非常方便我們對 SQL 進行監控、分析。Druid 對 Spring Boot 2.0 的支持還不夠完善,對於使用 Druid 的特殊場景,可以使用 Druid 原生包自行進行封裝。