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