springboot动态选择数据源
前情回顾:
前面介绍了springboot如何配置多数据源,最近又捣鼓了一下怎么去动态选择数据源,这里先提醒一下,动态选择数据源不等于主从复制,也不等于读写分离,这三的概念必须先理清楚,动态选择数据源是读写分离的基础,而主从复制是读写分离的依据。读写分离可以减少数据访问时的压力,并提高性能,同时也是作为负载均衡的一个重要基础
基础原理
既然要动态选择数据源,本质上还是进行动态选择数据库连接。查看一下java.sql.DataSource的接口,会发现有其有返回对应的Connection
然后在IDEA中按Ctrl + Alt 同时点击getConnection()方法查看其实现类。在实现类列表中找到AbstractRoutingDataSource这个类,这个类由Spring框架提供实现,点进去
在上图,由determineTargetDataSource实现了getConnection方法,点进去
上图中,determineCurrentLookupKey是一个抽象方法,其返回值可以通过传给resolvedDataSource从而决定一个连接的DataSource。而resolvedDataSource是一个Map<Object, Object>类型,其中约摸估计应该包含有用户所定义的所有的数据库DataSource。然后点击resolvedDataSource可以看到其是一个Map<Object, Object>
继续往下看,看到一个方法afterPropertiesSet,里面会对resolvedDataSource进行初始化操作
其中的resolvedDataSource的值是由targetDataSource设置二来,而targetDataSource也是一个Map<Object, Object>类型。
看到这里,相信大家都应该知道要做什么了,先把流程理一下
- 继承AbstractRoutingDataSource,并将所有的数据库(包括主从数据库)的DataSource设置进targetDataSource,设置格式为Map<Obejct, Object>,第一个Object表示一个Key,将通过determinLookupKey进行筛选,第二个Object就是数据源
- 然后targetDataSources将自身设置给resolvedDataSources
- 通过重写determineLookupKey()方法,选择相应的数据源的key值。
- 用户通过某种自定义的方法动态选择数据源(这里使用AOP)
项目配置
- springboot框架
- SpringDataJPA
- Lombok
- Aspect
基本操作
在前面我们知道如何配置多数据源,而配置多数据源又是动态选择数据源的基础,但是此处配置又与前面有所不同(进度条充足,请继续食用)
在开始介绍之前,首先来看一下项目结构:
配置数据库连接(application.properties)
1 | spring.datasource.primary.driver-class-name=com.mysql.jdbc.Driver |
数据库连接这块与前面没有什么不同之处,为了方便,我们使用同一服务下的不同库进行模拟演示
建立实体类(entity)
在前面配置多数据源的时候,我是将entity下再进行分层primary和slave,这样是方便演示,实际上这在多数据源中是不需要进行再分包的,可以直接在entity建立实体,如下图
- User类;
- Admin类
建立数据操作类
数据操作类也不必像配置多数据源一样进行分层,可以直接在repository下建立
- repository分类
- UserRepository
1 | public interface UserRepository extends JpaRepository<User, Integer>{ |
- AdminRepository
1 | public interface AdminRepository extends JpaRepository<Admin, Integer> { |
配置动态数据源
动态数据源配置与配置多数据源的不太一样,前者是以后者为基础的,话不多说,先贴图
说明:
- Datasource:自定义一个注解
- DatasourceAspect:自定义的一个切面
- DatasourceConfig:用来扫描配置文件中所配置的多个数据源,并生成Bean
- DatasourceType:定义一个枚举类,里面有所要表示的各种配置类的名称
- DynamicDatasource:继承了AbstractRoutingDatasource,并且实现了其抽象类determineCurrentLookupKey方法
- DynamicDatasourceConfig:动态选择数据源的配置类
- DynamicDatasourceHolder:动态数据源决策类
DataSourceConfig
有多少个数据源,就配置多少个Bean
DynamicDataSouceConfig
1 | @Configuration |
DynamicDataSourceConfig里面也是主要配置三个东西
- 配置动态的DataSource(将key值与DataSource设置成targetDataSource)
- 配置EntityManagerFactory(实体管理工厂)
- 配置TransactionManager(事务管理器)
配置动态的DataSource需要依赖DynamicDataSource去提供一个key值,在里面同时将key值与相应的数据源匹配到一起,比如key值为master,value就是masterDataSource;key值是slave,value就是slaveDataSource。
其中在配置EntityManagerFactory中遇到一个坑,就是现在如代码所示在dynamicEntityFactoryBean这个方法里,通过builder去构建一个LocalContainerEntityManagerFactoryBean,但是如果直接在dynamicEntityFactoryBean里直接构建一个EntityMangerFactory时,是得通过builer.build().getObject()的方法进行构建的,**但是,getObject()在此方法中会产生NullPointerException的空指针异常。具体原因不是特别清楚,如果将其分开来的话,就如上面代码所示,通过一个dynamicEntityFactory方法可以构建一个EntityManagerFactory,这样就可以运行,至于为什么前者不行,还有待深入研究。
之后配置事务管理器,就直接将EntityManagerFactory这个Bean注入进去就可以了。
以上关于动态数据源在哪里的配置就已经结束了,但是怎么动态选择数据源的问题将在下面解决
DataSourceType
在DataSourceType里定义了Map<Object, Opject>结构里的key值,随后可以动态选择key值,然后通过DynamicDataSourceHolder决策类进行设置。
其是一个Enum类,MASTER表示masterDataSource,SLAVE表示slaveDataSource
DynamicDataSource
继承自AbstractRoutingDataSource,并且重写了determineCurrentLookupKey方法
就简单地选择了一下,其依赖DynamicDataSourceHolder决策类,通过决策类进行选择,如果决策类里有,就返回,否则默认返回masterDataSource。
DynamicDataSourceHolder
动态数据源的决策,如果用户选择了某个数据库连接,那么DataSourceType将会通过设值到DynamicDataSourceHolder,然后determineCurrentLookupKey将会取出Holder里面的值,然后再源码层面上将这个key值取出targetDataSource里面对应的DataSource,并getConnection。
注解DataSource和切面DataSourceAspect
在本次测试项目中,将通过AOP进行动态的数据源选择
- 注解DataSource
- 切面DataSourceAspect
用完清除是个好习惯~
编写测试
由于自定义了一个@DataSource,此注解应用在service层,因此,将其注解在service层,就能实现动态选择数据源
由于尽量模拟读写分离,因此读就从SLAVE读,而写就写进MASTER。
至此,关于sprinboot动态选择数据源就已经介绍到这里,下次继续向读写分离进发,最近考试月2333333333333333333