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)