javascript
Spring boot 多数据源
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
網(wǎng)上多是基于XML文件,本文使用基于配置類的方式使用動(dòng)態(tài)數(shù)據(jù)源。
多數(shù)據(jù)源原理
Spring作為項(xiàng)目的應(yīng)用容器,也對(duì)多數(shù)據(jù)源提供了很好的支持,當(dāng)我們的持久化框架需要數(shù)據(jù)庫(kù)連接時(shí),我們需要做到動(dòng)態(tài)的切換數(shù)據(jù)源,這些Spring的AbstractRoutingDataSource都給我們留了拓展的空間,可以先來(lái)看看抽象類AbstractRoutingDataSource在獲取數(shù)據(jù)庫(kù)連接時(shí)做了什么。
//從配置文件讀取到的DataSources的Map private Map<Object, DataSource> resolvedDataSources; //默認(rèn)數(shù)據(jù)源 private DataSource resolvedDefaultDataSource;public Connection getConnection() throws SQLException {return determineTargetDataSource().getConnection(); }public Connection getConnection(String username, String password) throws SQLException {return determineTargetDataSource().getConnection(username, password); }protected DataSource determineTargetDataSource() {Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");Object lookupKey = determineCurrentLookupKey();DataSource dataSource = this.resolvedDataSources.get(lookupKey);if (dataSource == null && (this.lenientFallback || lookupKey == null)) {dataSource = this.resolvedDefaultDataSource;}if (dataSource == null) {throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");}return dataSource; }protected abstract Object determineCurrentLookupKey();可以看到AbstractRoutingDataSource在決定目標(biāo)數(shù)據(jù)源的時(shí)候,會(huì)先調(diào)用determineCurrentLookupKey()方法得到一個(gè)key,我們通過這個(gè)key從配置好的resolvedDataSources(Map結(jié)構(gòu))拿到這次調(diào)用對(duì)應(yīng)的數(shù)據(jù)源,而determineCurrentLookupKey()開放出來(lái)讓我們實(shí)現(xiàn)。
簡(jiǎn)單實(shí)現(xiàn)AbstractRoutingDataSource
基于上述分析,繼承抽象類AbstractRoutingDataSource,并實(shí)現(xiàn)determineCurrentLookupKey()方法。
public class MyDataSource extends AbstractRoutingDataSource {private static final ThreadLocal<String> dataSourceKey = new ThreadLocal<String>();public static void setDataSourceKey(String dataSource) {dataSourceKey.set(dataSource);}protected Object determineCurrentLookupKey() {String dsName = dataSourceKey.get();//這里需要注意的時(shí),每次我們返回當(dāng)前數(shù)據(jù)源的值得時(shí)候都需要移除ThreadLocal的值,//這是為了避免同一線程上一次方法調(diào)用對(duì)之后調(diào)用的影響dataSourceKey.remove(); return dsName;}}實(shí)際項(xiàng)目中與mybatis的結(jié)合
在application.yml中配置Spring數(shù)據(jù)庫(kù)連接相關(guān)屬性。
Springboot 默認(rèn)會(huì)自動(dòng)加載classpath下的application.yml和application.properties文件。
問題:當(dāng)兩個(gè)文件同時(shí)使用,會(huì)同時(shí)加載嗎?出現(xiàn)沖突會(huì)優(yōu)先選擇哪個(gè)文件的?
兩種文件同時(shí)使用時(shí)都會(huì)加載,如果兩文件出現(xiàn)相同的屬性名,則application.properties中的會(huì)為最終值(測(cè)試過。)。
多數(shù)據(jù)源配置:
datasource:type: com.alibaba.druid.pool.DruidDataSource.classwrite:name: pushopturl: jdbc:mysql://127.0.0.1:3306/pushoptusername: rootpassword: hgfgooddriver-class-name: com.mysql.jdbc.Drivermax-active: 20initial-size: 1max-wait: 6000pool-prepared-statements: truemax-open-prepared-statements: 20read1:name: testurl: jdbc:mysql://127.0.0.1:3306/testusername: rootpassword: hgfgooddriver-class-name: com.mysql.jdbc.Drivermax-active: 20initial-size: 1max-wait: 6000pool-prepared-statements: truemax-open-prepared-statements: 20生成DataSource Bean:
package com.meituan.service.web.opt.config;import com.meituan.service.web.opt.enums.DataSourceType; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import org.springframework.context.annotation.Primary; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager;import javax.sql.DataSource; import java.util.HashMap; import java.util.Map;/*** Created by hgf on 16/7/29.*/ @Configuration public class DataSourceConfiguration {private Class<? extends DataSource> datasourceType = com.alibaba.druid.pool.DruidDataSource.class;@Bean(name = "writeDataSource")@ConfigurationProperties(prefix = "datasource.write")public DataSource writeDataSource() {return DataSourceBuilder.create().type(datasourceType).build();}@Bean(name = "readDataSource1")@ConfigurationProperties(prefix = "datasource.read1")public DataSource readDataSource1() {return DataSourceBuilder.create().type(datasourceType).build();}/*** 有多少個(gè)數(shù)據(jù)源就要配置多少個(gè)bean** @return*/@Bean@Primary@DependsOn({"writeDataSource", "readDataSource1"})public DynamicDataSource dynamicDataSource() {DynamicDataSource proxy = new DynamicDataSource();//表示可用的數(shù)據(jù)源,包括寫和讀數(shù)據(jù)源Map<Object, Object> targetDataSources = new HashMap<Object, Object>();// 寫targetDataSources.put(DataSourceType.WRITE.getType(), writeDataSource());//如果有多個(gè)DataSource,需要設(shè)置多個(gè)targetDataSources.put(DataSourceType.READ.getType(), readDataSource1());//設(shè)置默認(rèn)的數(shù)據(jù)源為寫數(shù)據(jù)源proxy.setDefaultTargetDataSource(writeDataSource());proxy.setTargetDataSources(targetDataSources);return proxy;}@Beanpublic SqlSessionFactory sqlSessionFactorys() throws Exception {SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));sqlSessionFactoryBean.setDataSource(dynamicDataSource());sqlSessionFactoryBean.setTypeAliasesPackage("com.meituan.service.web.opt.model");PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver();sqlSessionFactoryBean.setMapperLocations(pathMatchingResourcePatternResolver.getResources("classpath:/mapper/*.xml"));SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBean.getObject();return sqlSessionFactory;}@BeanDataSourceTransactionManager dataSourceTransactionManager() {DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dynamicDataSource());return dataSourceTransactionManager;}}注:此處有兩個(gè)坑,其一是配置DataSource的時(shí)候有坑,在實(shí)例化AbstractRoutingDataSource的時(shí)候不要在屬性上設(shè)置@Autowired注入,直接使用屬性注入或者調(diào)用Bean的配置函數(shù)否則會(huì)產(chǎn)生循環(huán)依賴。
正確使用方法:
錯(cuò)誤方法:
@Autowired@Qualifier("writeDataSource")DataSource writeDataSource;@Autowired@Qualifier("readDataSource1")DataSource readDataSource1;@Bean(name = "dynamicDataSource")public AbstractRoutingDataSource dynamicDataSource() {DynamicDataSource proxy = new DynamicDataSource();...return proxy;}錯(cuò)誤異常:
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'mybatisConfiguration': Unsatisfied dependency expressed through field 'writeDataSource':
Error creating bean with name 'writeDataSource' defined in class path resource [com/meituan/service/web/opt/config/DataSourceConfiguration.class]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'dataSourceInitializer': Invocation of init method failed; nested exception is org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'dynamicDataSource' defined in class path resource [com/meituan/service/web/opt/config/MybatisConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource]: Circular reference involving containing bean 'mybatisConfiguration' - consider declaring the factory method as static for independence from its containing instance. Factory method 'dynamicDataSource' threw exception; nested exception is java.lang.IllegalArgumentException: [Assertion failed] - this argument is required; it must not be null; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'writeDataSource' defined in class path resource [com/meituan/service/web/opt/config/DataSourceConfiguration.class]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'dataSourceInitializer': Invocation of init method failed; nested exception is org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'dynamicDataSource' defined in class path resource [com/meituan/service/web/opt/config/MybatisConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource]: Circular reference involving containing bean 'mybatisConfiguration' - consider declaring the factory method as static for independence from its containing instance. Factory method 'dynamicDataSource' threw exception; nested exception is java.lang.IllegalArgumentException: [Assertion failed] - this argument is required; it must not be null
主要意思就是:mybatisConfigurationbean需要先注入writeDataSourceBean,該Bean依賴dataSourceInitializer,而dataSourceInitializer依賴dynamicDataSource,此時(shí)dynamicDataSource由于依賴writeDataSource而沒有初始化,所以依賴注入writeDataSource此時(shí)沒有正確的生成bean,而是null。所以造成初始化Bean失敗。
其二就是需要自定義Bean加載順序。由于DataSource使用DataSourceBuilder創(chuàng)建,該類依賴datasource實(shí)例,所以容易產(chǎn)生循環(huán)依賴,特別是在先加載DynamicDataSource,的同時(shí)加載writeDataSource時(shí)。解決方法:使用Spring提供的@DependsOn注解,注解DynamicDataSource。當(dāng)加載DynamicDataSource,會(huì)等待加載writeDataSource,等writeDataSource加載完成后,再加載DynamicDataSource。就不會(huì)出現(xiàn)DynamicDataSource->DatasourceInitlizer->writeDataSource->DataSourceInitlizer循環(huán)依賴了。
注: 當(dāng)spring容器中有多個(gè)datasource時(shí),使用@Primary決定當(dāng)有同類別的beans時(shí),如何選擇注入那個(gè)類。
多數(shù)據(jù)源集成mybatis
生成AbstractRoutingDataSource的Bean后,使用該Bean配置SqlSessionFactory,就能使動(dòng)態(tài)數(shù)據(jù)源生效。
注:如果手動(dòng)配置SqlSessionFactoryBean,那么Spring boot默認(rèn)會(huì)從Ioc容器中選擇一個(gè)(一般是最先生成的Datasource Bean)DataSource注入到默認(rèn)的自動(dòng)加載的SqlSessionFactory中,此時(shí)動(dòng)態(tài)數(shù)據(jù)源不能生效。
自定義SqlSessionFactory,使用SqlSessionFactoryBean來(lái)生成SqlSessionFactory:
@Beanpublic SqlSessionFactory sqlSessionFactorys(AbstractRoutingDataSource dynamicDataSource) throws Exception {SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();//設(shè)置mybatis的配置文件路徑sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));//設(shè)置數(shù)據(jù)源為動(dòng)態(tài)數(shù)據(jù)源sqlSessionFactoryBean.setDataSource(dynamicDataSource);//設(shè)置類型前綴包名,在mapper文件中就不用使用詳細(xì)的包名了,直接使用類名。sqlSessionFactoryBean.setTypeAliasesPackage("com.meituan.service.web.opt.model");//配置路徑匹配器,獲取匹配的文件PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver();sqlSessionFactoryBean.setMapperLocations(pathMatchingResourcePatternResolver.getResources("classpath:/mapper/*.xml"));SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBean.getObject();return sqlSessionFactory;}多源數(shù)據(jù)源是無(wú)法跨聲明式事務(wù)的,一般事務(wù)是針對(duì)某個(gè)DBMS的,需要實(shí)現(xiàn)跨DBMS的事務(wù)需要使用JTA。
參考
- spring 所數(shù)據(jù)源切換
- spring 集成mybatis
- Springboot 多數(shù)據(jù)源整合mybatis
轉(zhuǎn)載于:https://my.oschina.net/hgfdoing/blog/741897
總結(jié)
以上是生活随笔為你收集整理的Spring boot 多数据源的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: zabbix JMX监控Tomcat及错
- 下一篇: JS求多个数组的重复数据