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