SpringBoot多數據源問題打破沙鍋講到底
本文轉載自微信公眾號「 編程新說」,轉載本文請聯系 編程新說公眾號。
解決問題的“兩步方針”
第一步,將現有狀況徹底搞清楚。
第二步,結合實際情況和現有狀況給出方案。
可能有些人會認為第二步是比較難的,其實非也,第一步才是最難的。我就不解釋了,理解不了的慢慢就會懂了。
問題抽象后也就兩類
第一類,看起來不復雜,但是很難解決。
第二類,看起來很復雜,但是較易解決。
和SpringBoot相關的很多問題大抵都屬于第二類。
SpringBoot的核心思想
SpringBoot是一個集成化程度很高的框架,它背后采用的是自動配置(autoconfigure)來實現的。為了這個自動配置,它引入了條件判斷(Condition)機制。
這些條件判斷,粗略的分為三類:
第一類:對于application.yml配置文件里的配置屬性進行檢測,如果有的話怎么做,如果沒有的話怎么做。
第二類,對類路徑里面引入的class類進行檢測,如果有的話怎么做,如果沒有的話怎么做。
第三類,對容器中已經注冊的Bean進行檢測,如果有的話怎么做,如果沒有的話怎么做。
其實就相當于許多的if/else互相嵌套交織在一起,在SpringBoot啟動時,會逐個的計算所有的條件,最終從里面“殺出一條血路來”。
常用的數據庫訪問方案
基于SpringBoot最常用的方案從底向上分為:
最底部一層,數據庫,如MySQL
倒數第二層,數據源,就是DataSource
倒數第三層,事務管理器,就是TransactionManager
倒數第四層,就是ORM框架,如MyBatis
倒數第五層,就是分頁組件,如PageHelper
如果數據庫只有一個,那數據源也就是單一數據源,事務自然也就是本地事務。
如果數據庫有多個,那數據源也就變成了多數據源,事務自然也變成了分布式事務。
按照微服務的理論,同一份代碼是不會直接訪問到其它數據源的,應該是通過接口去訪問其它數據源里的數據。
但是實際情況呢,當然是在保證沒有問題的情況下,怎樣簡單怎樣來了,只要自己明白自己是在干什么就行了。
SpringBoot官方支持的數據源
想要了解一個東西,最好的資料就是官方文檔。想要深入的了解一個東西,恐怕只能看源碼了。
SpringBoot對于數據源的自動配置類是:
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
默認支持兩種類型的數據源的配置:內嵌數據庫(EmbeddedDatabaseConfiguration)和池化數據源(PooledDataSourceConfiguration)。
這兩種數據源到底會選擇誰,還要看各自條件的計算結果,看誰的條件會滿足。
我們注意到每個類上都有四個注解,來看下它們的作用:
@Configuration,標明這個類會被Spring框架進行處理。
@Conditional,這是一個條件,需要指定一個條件類,這個條件類需要被計算。
@ConditionalOnMissingBean,這是一個條件,用來檢測指定的Bean的注冊情況,沒有被注冊時符合條件。
@Import,用來引入其它類,被引入的類會被Spring框架進行處理。
可以看到共有兩個條件,下面來看看這兩種數據源配置的具體條件分別是什么。
池化數據源的條件一:
- @Conditional(PooledDataSourceCondition.class)
可以看到指定的條件類是PooledDataSourceCondition,該類內容如下:
可以看到它繼承自AnyNestedCondition類,意思是這個類的條件依賴于它的內部嵌套類的條件,因此它就定義了兩個內部嵌套類,而且每個嵌套類上都有條件注解。
內部嵌套類一的條件是:
- @ConditionalOnProperty(prefix = "spring.datasource", name = "type")
這是關于application.yml配置文件里的屬性的檢測,如果配置了spring.datasource.type這個屬性,則該條件就是符合的,否則就是不符合的。
這個條件的意思就是,是否顯式指定了數據源的類型。日常開發中一般都不指定這個,所以這個條件一般情況下是不符合的。
內部嵌套類二的條件是:
- @Conditional(PooledDataSourceAvailableCondition.class)
這又指定了一個條件類,PooledDataSourceAvailableCondition,該類的相關內容如下:
它的核心思想是通過類加載器去分別加載下面三個數據源類:
- com.zaxxer.hikari.HikariDataSource
- org.apache.tomcat.jdbc.pool.DataSource
- org.apache.commons.dbcp2.BasicDataSource
如果能有一個加載成功的,那么此條件就是符合的。一般情況下我們都不使用這三個數據源,所以一般情況下此條件是不符合的。
一般情況下,這兩個嵌套類的條件都是不符合的,所以它們的外部類的條件一般情況下也是不符合的。
池化數據源的條件二:
- @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
這個條件就是檢測Spring的容器里是否注冊了類型為DataSource或XADataSource的Bean,沒有注冊就是符合,這要根據實際情況了。
@Import引入的類:
- @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
- DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class,
- DataSourceJmxConfiguration.class })
可以看到這些引入的類就是每種數據源的配置或注冊類了。這里共引入五個類,它們也都是帶有條件的,也會被按順序計算,最多只會有一個符合,或者都不符合。
下面來看一個SpringBoot官方推薦的數據源,Hikari的配置,它的內容如下:
它共包含三個條件:
@ConditionalOnClass(HikariDataSource.class),表明HikariDataSource這個類必須存在,也就是說明要引入Hikari的相關jar包。
@ConditionalOnMissingBean(DataSource.class),表明DataSource類型的Bean不存在,即截止到目前還沒有注冊過數據源。
- @ConditionalOnProperty(name = "spring.datasource.type",
havingValue = "com.zaxxer.hikari.HikariDataSource", matchIfMissing = true),表明指定了數據源的類型是Hikari,但是如果沒有指定的話也認為是符合的。
如果這三個條件都符合,就會往容器里注冊一個HikariDataSource類型的數據源Bean。
@ConfigurationProperties(prefix = "spring.datasource.hikari")的作用就是,在這個數據源Bean實例化時,把application.yml配置文件里以spring.datasource.hikari開頭的配置屬性,都按setter的規則設置給這個數據源Bean實例。
其它類型的數據源的注冊細節和這個Hikari是一模一樣的,所以上述引入的五個數據源配置類的條件都會被計算一邊,但是最多只會有一個配置類的條件是符合的。
因此,從某種意義來說,SpringBoot的條件在某種情況下不具有“短路”的特性。
池化數據源的部分已經講完了。再來看看內嵌數據源。
內嵌數據源條件一:
@Conditional(EmbeddedDatabaseCondition.class)
這里指定的條件類是EmbeddedDatabaseCondition,它的相關內容如下:
它的核心思想就是,先去判斷看池化數據源的條件是否符合,如果池化數據源符合的話,那內嵌數據源肯定是不符合的,因此池化數據源的優先級高。
然后再去分別加載下面三個內嵌數據源類:
- org.h2.Driver
- org.apache.derby.jdbc.EmbeddedDriver
- org.hsqldb.jdbcDriver
只要有一個加載成功,就算是符合。實際當中一般很少使用內嵌數據源,所以這個條件一般情況下是不符合的。
內嵌數據源條件二:
- @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
這個想必都已經知道是什么意思了,就是如果此時容器中還沒有注冊數據源類型的Bean,那就符合。
@Import引入的類:
- @Import(EmbeddedDataSourceConfiguration.class)
由于內嵌數據源一般開發中很少使用,所以就不再看了。
其實一般情況下,SpringBoot官方默認支持的三種池化數據源和三種內嵌數據源的這些條件都是不會符合的。
因為一般情況下,我們都使用阿里的Druid數據源。
阿里的Druid數據源
Druid數據源的自動配置內容如下:
這里面有兩個條件:
@ConditionalOnClass(DruidDataSource.class),表明DruidDataSource類需要存在,即已經引入了Druid數據源的jar包。
@ConditionalOnMissingBean,表明容器中沒有被注冊過類型為DataSource的Bean。
自動配置除了和條件有關,還和順序也緊密相關,因為順序靠前的先計算條件,一旦條件符合,就會向容器中注冊Bean,一旦注冊了特定類型的Bean,后面的可能就沒有機會再注冊了。
自動配置順序:
- @AutoConfigureBefore(DataSourceAutoConfiguration.class)
表明Druid數據源的自動配置先于SpringBoot官方的數據源自動配置進行,因此Druid數據源往容器里注冊了類型為DataSource的Bean。
所以,SpringBoot官方的數據源自動配置再也沒有機會注冊數據源Bean了。這樣我們使用的就是Druid數據源了。