一 簡介
在以前的博客中,我介紹了怎麼使用spring在項目中進行多數據源的切換,這裏是在前一篇文章的基礎上介紹如何使用springboot配置多數據源。爲了方便理解,請先了解上一篇文章,spring多數據源傳送門:點擊打開鏈接。
二 區別和共同點
區別: spring和springboot在多數據源上區別在於前者是在xml中進行的數據源配置,後者則是通過一個註冊類來實現多數據源的註冊。
共同點:兩者都是通過切面加註解的方式去切換數據源。
當然springboot也提供了更簡單的方法去實現數據源的切換,只是不夠靈活,只能在包上面進行切換,不能夠在類和方法上進行切換。所以這裏纔會繼續沿用切面加註解的方式。
三 上代碼
注意事項:下面的代碼是在spring多數據源的基礎上寫的,所以要想使用,先把之前的代碼考到項目裏,除了xml。如需完整的代碼請加羣 499950895 從羣文件中下載myProject.rar
第一步
在application.yml文件中添加數據源配置
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: root
initialize: true
custom:
datasource:
names: ds1,ds2
ds1:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: root
ds2:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test3?useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: root
在yml文件中必須包含一個主庫,使用spring.datasource配置,從庫則使用custom.datasource配置,names的值是從數據源的名稱,多個用逗號隔開
第二步 多數據源註冊
package com.gcx.api.common.dataSource;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.bind.RelaxedDataBinder;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
/**
*<p>Title:DynamicDataSourceRegister</p>
*<p>Description:動態數據源註冊</p>
*<p>Company:gcx</p>
*<p>Author:zhanglin</p>
*<p>Date:2018年3月27日</p>
*/
public class DynamicDataSourceRegister implements
ImportBeanDefinitionRegistrar, EnvironmentAware {
//如配置文件中未指定數據源類型,使用該默認值
private static final Object DATASOURCE_TYPE_DEFAULT = "com.alibaba.druid.pool.DruidDataSource";
private ConversionService conversionService = new DefaultConversionService();
private PropertyValues dataSourcePropertyValues;
// 默認數據源
private DataSource defaultDataSource;
private Map<String, DataSource> customDataSources = new HashMap<String, DataSource>();
public DynamicDataSourceRegister() {
// TODO Auto-generated constructor stub
}
@Override
public void setEnvironment(Environment environment) {
initDefaultDataSource(environment);
initCustomDataSources(environment);
}
private void initCustomDataSources(Environment env) {
// 讀取配置文件獲取更多數據源,也可以通過defaultDataSource讀取數據庫獲取更多數據源
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "custom.datasource.");
String dsPrefixs = propertyResolver.getProperty("names");
String dsn="";
for (String dsPrefix : dsPrefixs.split(",")) {// 多個數據源
dsn=dsPrefix+".";
Map<String, Object> dsMap = new HashMap<String, Object>();
dsMap.put("type", propertyResolver.getProperty(dsn+"type"));
dsMap.put("driverClassName", propertyResolver.getProperty(dsn+"driverClassName"));
dsMap.put("url", propertyResolver.getProperty(dsn+"url"));
dsMap.put("username", propertyResolver.getProperty(dsn+"username"));
dsMap.put("password", propertyResolver.getProperty(dsn+"password"));
DataSource ds = buildDataSource(dsMap);
customDataSources.put(dsPrefix, ds);
dataBinder(ds, env);
}
}
/**
* 加載主數據源配置
* @param env
*/
private void initDefaultDataSource(Environment env) {
//讀取主數據源
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "spring.datasource.");
Map<String, Object> dsMap = new HashMap<String, Object>();
dsMap.put("type", propertyResolver.getProperty("type"));
dsMap.put("driverClassName", propertyResolver.getProperty("driverClassName"));
dsMap.put("url", propertyResolver.getProperty("url"));
dsMap.put("username", propertyResolver.getProperty("username"));
dsMap.put("password", propertyResolver.getProperty("password"));
//創建數據源;
defaultDataSource = buildDataSource(dsMap);
dataBinder(defaultDataSource, env);
}
private void dataBinder(DataSource dataSource, Environment env) {
RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
dataBinder.setConversionService(conversionService);
dataBinder.setIgnoreNestedProperties(false);//false
dataBinder.setIgnoreInvalidFields(false);//false
dataBinder.setIgnoreUnknownFields(true);//true
if(dataSourcePropertyValues == null){
Map<String, Object> rpr = new RelaxedPropertyResolver(env, "spring.datasource").getSubProperties(".");
Map<String, Object> values = new HashMap<>(rpr);
// 排除已經設置的屬性
values.remove("type");
values.remove("driverClassName");
values.remove("url");
values.remove("username");
values.remove("password");
dataSourcePropertyValues = new MutablePropertyValues(values);
}
dataBinder.bind(dataSourcePropertyValues);
}
@SuppressWarnings("unchecked")
private DataSource buildDataSource(Map<String, Object> dsMap) {
Object type = dsMap.get("type");
if (type == null){ //默認數據源
type = DATASOURCE_TYPE_DEFAULT;// 默認DataSource
}
Class<? extends DataSource> dataSourceType;
try {
dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
String driverClassName = dsMap.get("driverClassName").toString();
String url = dsMap.get("url").toString();
String username = dsMap.get("username").toString();
String password = dsMap.get("password").toString();
DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url).username(username).password(password).type(dataSourceType);
return factory.build();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
// 將主數據源添加到更多數據源中
targetDataSources.put("dataSource", defaultDataSource);
// 添加更多數據源
targetDataSources.putAll(customDataSources);
// 創建DynamicDataSource
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DynamicDataSource.class);
beanDefinition.setSynthetic(true);
MutablePropertyValues mpv = beanDefinition.getPropertyValues();
//添加屬性:AbstractRoutingDataSource.defaultTargetDataSource
mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
mpv.addPropertyValue("targetDataSources", targetDataSources);
registry.registerBeanDefinition("dataSource", beanDefinition);
}
}
EnvironmentAware接口使用來讀取application.yml配置文件的,可以通過重寫setEnvironment方法來獲取配置文件的內容。
ImportBeanDefinitionRegistrar接口定義了registerBeanDefinitions方法,從而允許我們向Spring註冊必要的Bean,該方法主要是通過BeanDefinitionRegistry參數spring IOC中動態的裝配bean
GenericBeanDefinition是自2.5以後新加入的bean文件配置屬性定義類,是一站式服務類。除了具有指定類、可選的構造參數值和屬性參數這些其它bean definition一樣的特性外,它還具有通過parenetName屬性來靈活設置parent bean definition。
通常, GenericBeanDefinition用來註冊用戶可見的bean definition(可見的bean definition意味着可以在該類bean definition上定義post-processor來對bean進行操作,甚至爲配置parent name做擴展準備
第三 在啓動類上加上@Import註解
@SpringBootApplication
@Import({DynamicDataSourceRegister.class})
@MapperScan("com.gcx.api.dao")
public class Application extends SpringBootServletInitializer{
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@Import註解在4.2,支持導入普通的java類,並將其聲明成一個bean。
至此便可以在方法或類上使用註解去切換數據源。
如果不想理解原理,只想快速的使用此功能測只需要把項目中的dataSource包考在項目中,然後在啓動類上加上@Import({DynamicDataSourceRegister.class})即可使用
包名如下圖
注意DataSourceName中的屬性值是yml配置中的從數據源名稱