javascript
Spring boot 多数据源
2019獨角獸企業重金招聘Python工程師標準>>>
網上多是基于XML文件,本文使用基于配置類的方式使用動態數據源。
多數據源原理
Spring作為項目的應用容器,也對多數據源提供了很好的支持,當我們的持久化框架需要數據庫連接時,我們需要做到動態的切換數據源,這些Spring的AbstractRoutingDataSource都給我們留了拓展的空間,可以先來看看抽象類AbstractRoutingDataSource在獲取數據庫連接時做了什么。
//從配置文件讀取到的DataSources的Map private Map<Object, DataSource> resolvedDataSources; //默認數據源 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在決定目標數據源的時候,會先調用determineCurrentLookupKey()方法得到一個key,我們通過這個key從配置好的resolvedDataSources(Map結構)拿到這次調用對應的數據源,而determineCurrentLookupKey()開放出來讓我們實現。
簡單實現AbstractRoutingDataSource
基于上述分析,繼承抽象類AbstractRoutingDataSource,并實現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();//這里需要注意的時,每次我們返回當前數據源的值得時候都需要移除ThreadLocal的值,//這是為了避免同一線程上一次方法調用對之后調用的影響dataSourceKey.remove(); return dsName;}}實際項目中與mybatis的結合
在application.yml中配置Spring數據庫連接相關屬性。
Springboot 默認會自動加載classpath下的application.yml和application.properties文件。
問題:當兩個文件同時使用,會同時加載嗎?出現沖突會優先選擇哪個文件的?
兩種文件同時使用時都會加載,如果兩文件出現相同的屬性名,則application.properties中的會為最終值(測試過。)。
多數據源配置:
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();}/*** 有多少個數據源就要配置多少個bean** @return*/@Bean@Primary@DependsOn({"writeDataSource", "readDataSource1"})public DynamicDataSource dynamicDataSource() {DynamicDataSource proxy = new DynamicDataSource();//表示可用的數據源,包括寫和讀數據源Map<Object, Object> targetDataSources = new HashMap<Object, Object>();// 寫targetDataSources.put(DataSourceType.WRITE.getType(), writeDataSource());//如果有多個DataSource,需要設置多個targetDataSources.put(DataSourceType.READ.getType(), readDataSource1());//設置默認的數據源為寫數據源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;}}注:此處有兩個坑,其一是配置DataSource的時候有坑,在實例化AbstractRoutingDataSource的時候不要在屬性上設置@Autowired注入,直接使用屬性注入或者調用Bean的配置函數否則會產生循環依賴。
正確使用方法:
錯誤方法:
@Autowired@Qualifier("writeDataSource")DataSource writeDataSource;@Autowired@Qualifier("readDataSource1")DataSource readDataSource1;@Bean(name = "dynamicDataSource")public AbstractRoutingDataSource dynamicDataSource() {DynamicDataSource proxy = new DynamicDataSource();...return proxy;}錯誤異常:
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,此時dynamicDataSource由于依賴writeDataSource而沒有初始化,所以依賴注入writeDataSource此時沒有正確的生成bean,而是null。所以造成初始化Bean失敗。
其二就是需要自定義Bean加載順序。由于DataSource使用DataSourceBuilder創建,該類依賴datasource實例,所以容易產生循環依賴,特別是在先加載DynamicDataSource,的同時加載writeDataSource時。解決方法:使用Spring提供的@DependsOn注解,注解DynamicDataSource。當加載DynamicDataSource,會等待加載writeDataSource,等writeDataSource加載完成后,再加載DynamicDataSource。就不會出現DynamicDataSource->DatasourceInitlizer->writeDataSource->DataSourceInitlizer循環依賴了。
注: 當spring容器中有多個datasource時,使用@Primary決定當有同類別的beans時,如何選擇注入那個類。
多數據源集成mybatis
生成AbstractRoutingDataSource的Bean后,使用該Bean配置SqlSessionFactory,就能使動態數據源生效。
注:如果手動配置SqlSessionFactoryBean,那么Spring boot默認會從Ioc容器中選擇一個(一般是最先生成的Datasource Bean)DataSource注入到默認的自動加載的SqlSessionFactory中,此時動態數據源不能生效。
自定義SqlSessionFactory,使用SqlSessionFactoryBean來生成SqlSessionFactory:
@Beanpublic SqlSessionFactory sqlSessionFactorys(AbstractRoutingDataSource dynamicDataSource) throws Exception {SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();//設置mybatis的配置文件路徑sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));//設置數據源為動態數據源sqlSessionFactoryBean.setDataSource(dynamicDataSource);//設置類型前綴包名,在mapper文件中就不用使用詳細的包名了,直接使用類名。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;}多源數據源是無法跨聲明式事務的,一般事務是針對某個DBMS的,需要實現跨DBMS的事務需要使用JTA。
參考
- spring 所數據源切換
- spring 集成mybatis
- Springboot 多數據源整合mybatis
轉載于:https://my.oschina.net/hgfdoing/blog/741897
總結
以上是生活随笔為你收集整理的Spring boot 多数据源的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: zabbix JMX监控Tomcat及错
- 下一篇: html5,表格与框架综合布局