第 3-7 課:Spring Boot 集成 Druid 監控數據源

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 原生包自行進行封裝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章