一 简介
在以前的博客中,我介绍了怎么使用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配置中的从数据源名称