2.x微服务踩坑之多数据源
目录
- 一、1.x版本配置
- 二、2.x版本配置
- 三、1.x 与 2.x 踩坑
【注】 踩坑是根据公司需求对原有代码进行升级,这里只对单纯的技术配置代码进行展示不涉及公司业务。公司采用的是properties这里就使用properties进行配置
一、1.x数据源配置
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>1.5.9.RELEASE</version>
</parent>
application.properties文件
# 主数据源
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url = jdbc:mysql://127.0.0.1:3305/lgg_zone
spring.datasource.username=root
spring.datasource.password=root
# 备用数据源1
custom.datasource.names = lgg-one, lgg-two
custom.datasource.lgg-one.type=com.alibaba.druid.pool.DruidDataSource
custom.datasource.lgg-one.driverClassName = com.mysql.jdbc.Drive
custom.datasource.lgg-one.url = jdbc:mysql://127.0.0.1:3306/lgg_one
custom.datasource.lgg-one.username=root
custom.datasource.lgg-one.password=root
# 备用数据源2
custom.datasource.lgg-two.type=com.alibaba.druid.pool.DruidDataSource
custom.datasource.lgg-two.driverClassName = com.mysql.jdbc.Drive
custom.datasource.lgg-two.url = jdbc:mysql://127.0.0.1:3307/lgg_two
custom.datasource.lgg-two.username=root
custom.datasource.lgg-two.password=root
package com.lxn.eureka.datasource1_x;
import java.lang.annotation.*;
/**
* @author: ligangan
* @Description 定义切换数据源的注解
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String name();
}
package com.lxn.eureka.datasource1_x;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* @author: ligangan
* @Date: 2021/3/12
* @Time: 12:58
* 动态数据源
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
package com.lxn.eureka.datasource1_x;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* @author: ligangan
* @Date: 2021/3/12
* @Time: 12:59
* 切换数据源Advice
*/
@Aspect
@Order(-1) // 保证该AOP在@Transactional 之前执行
@Component
public class DynamicDataSourceAspect {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
@Before("@annotation(ds)")
public void changeDataSource(JoinPoint joinPoint, TargetDataSource ds) throws Throwable{
String dsId = ds.name();
if(!DynamicDataSourceContextHolder.containsDataSource(dsId)){
logger.error("数据源[{}]不存在,使用默认数据源>{}",ds.name(), joinPoint.getSignature());
}else{
logger.debug("Use DataSource: {} > {}",ds.name(), joinPoint.getSignature());
DynamicDataSourceContextHolder.setDataSourceType(ds.name());
}
}
@After("@annotation(ds)")
public void restoreDataSource(JoinPoint joinPoint, TargetDataSource ds){
logger.debug("Use DataSource: {} > {}",ds.name(), joinPoint.getSignature());
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
package com.lxn.eureka.datasource1_x;
import java.util.ArrayList;
import java.util.List;
/**
* @author: ligangan
* @Date: 2021/3/12
* @Time: 12:59
* 判断指定DataSource 当前是否存在
*/
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static List<String> dataSourceIds = new ArrayList<>();
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static String getDataSourceType(){
return contextHolder.get();
}
public static void clearDataSourceType () {
contextHolder.remove();
}
public static boolean containsDataSource(String dataSourceId) {
return dataSourceIds.contains(dataSourceId);
}
}
package com.lxn.eureka.datasource1_x;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* @author: ligangan
* @Date: 2021/3/12
* @Time: 12:59
*/
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private static final Logger logger = LoggerFactory.getLogger(com.lxn.eureka.datasource1_x.DynamicDataSourceRegister.class);
private ConversionService conversionService = new DefaultConversionService();
private PropertyValues dataSourcePropertyValues;
/**
* 如果配置文件中未指定数据源类型,使用该默认值
*/
private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.DataSource";
/**
* 数据源
*/
private DataSource defaultDataSource;
private Map<String,DataSource> customDataSources = new HashMap<>();
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
Map<Object,Object> targetDataSources = new HashMap<Object,Object>();
// 将主数据源添加到更多数据源中
targetDataSources.put("dataSource",defaultDataSource);
DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
// 添加更多数据源
targetDataSources.putAll(customDataSources);
for (String key : customDataSources.keySet()) {
DynamicDataSourceContextHolder.dataSourceIds.add(key);
}
// 创建DynamicDataSource
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DynamicDataSource.class);
beanDefinition.setSynthetic(true);
MutablePropertyValues mvp = beanDefinition.getPropertyValues();
mvp.addPropertyValue("defaultTargetDataSource",defaultDataSource);
mvp.addPropertyValue("targetDataSources",targetDataSources);
beanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition);
logger.info("Dynamic DataSource Registry");
}
/**
* 创建DataSource
*/
@SuppressWarnings("unchecked")
public DataSource buildDataSource(Map<String,Object> dsMap) {
try {
Object type = dsMap.get("type");
if(type == null) {
type = DATASOURCE_TYPE_DEFAULT; // 默认DataSource
}
Class<? extends DataSource> dataSourceType;
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) {
logger.error("buildDataSource error", e);
}
return null;
}
/**
* 加载多数据源
* @param environment
*/
@Override
public void setEnvironment(Environment env) {
initDefaultDataSource(env);
initCustomDataSource(env);
}
/**
* 初始化主数据源
* @param env
*/
private void initDefaultDataSource(Environment env) {
// 读取主数据源
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env,"spring.datasource.");
HashMap<String, Object> dsMap = new HashMap<>();
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);
}
/**
* 初始化更多数据源
* @param env
*/
private void initCustomDataSource(Environment env) {
// 读取配置文件获取更多数据源,也可以通过defaultDataSource读取数据库获取更多数据源
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env,"custom.datasource.");
String dsPrefixs = propertyResolver.getProperty("names");
for (String dsPrefix : dsPrefixs.split(",")) {
Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix+ ".");
DataSource ds = buildDataSource(dsMap);
customDataSources.put(dsPrefix,ds);
dataBinder(ds,env);
}
}
private void dataBinder(DataSource dataSource, Environment env) {
RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
dataBinder.setConversionService(conversionService);
dataBinder.setIgnoreNestedProperties(false);
dataBinder.setIgnoreInvalidFields(false);
dataBinder.setIgnoreUnknownFields(true);
if(dataSourcePropertyValues == null){
Map<String, Object> rpr = new RelaxedPropertyResolver(env, "spring.datasource").getSubProperties(".");
HashMap<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);
}
}
二、2.x数据源配置
[注]由于2.x中springboot 官方删除了org.springframework.boot.bind 这个包所以原有配置不再适用 新增Binder类取代
properties 文件中这些短杠不要用下划线
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>2.3.7.RELEASE</version>
</parent>
package com.lxn.eureka.datasource2_x_properties;
import java.lang.annotation.*;
/**
* @author: ligangan
* @Description 定义切换数据源的注解
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String name();
}
package com.lxn.eureka.datasource2_x_properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.boot.jdbc.DataSourceBuilder;
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;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* @author: ligangan
* @Date: 2021/3/12
* @Time: 12:59
*/
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class);
/**
* 如果配置文件中未指定数据源类型,使用该默认值
*/
private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.DataSource";
/**
* 数据源
*/
private DataSource defaultDataSource;
/**
* 自定义数据源
*/
private Map<String,DataSource> customDataSources = new HashMap<>();
/**
* 公共数据源配置
*/
private Map<String,Object> commonConfig;
private Binder binder;
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
Map<Object,Object> targetDataSources = new HashMap<Object,Object>();
// 将主数据源添加到更多数据源中
targetDataSources.put("dataSource",defaultDataSource);
DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
// 添加更多数据源
targetDataSources.putAll(customDataSources);
// 创建DynamicDataSource
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DynamicDataSource.class);
beanDefinition.setSynthetic(true);
MutablePropertyValues mvp = beanDefinition.getPropertyValues();
mvp.addPropertyValue("defaultTargetDataSource",defaultDataSource);
mvp.addPropertyValue("targetDataSources",targetDataSources);
beanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition);
}
/**
* 创建DataSource
*/
@SuppressWarnings("unchecked")
public DataSource buildDataSource(Map<String,Object> dsMap) {
try {
Object type = dsMap.get("type");
if(type == null) {
type = DATASOURCE_TYPE_DEFAULT; // 默认DataSource
}
Class<? extends DataSource> dataSourceType;
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) {
logger.error("buildDataSource error", e);
}
return null;
}
/**
* 加载多数据源
* @param env
*/
@Override
public void setEnvironment(Environment env) {
// 新增环境传入
binder = Binder.get(env);
// 默认数据源配置
initDefaultDataSource(env);
// 其它数据源配置
initCustomDataSource(env);
}
/**
* 初始化主数据源
* @param env
*/
private void initDefaultDataSource(Environment env) {
commonConfig = binder.bind("spring.datasource", Map.class).get();
// 生成DataSource, 用于注册
defaultDataSource = buildDataSource(commonConfig);
// 给DataSource赋值其它公共配置
dataBinder(defaultDataSource,commonConfig);
}
/**
* 初始化更多数据源
* @param env
*/
private void initCustomDataSource(Environment env) {
Map otherConfig = binder.bind("custom.datasource", Map.class).get();
String dsPrefixs = otherConfig.get("names").toString();
for (String dsPrefix : dsPrefixs.split(",")) {
Map<String, Object> dsMap = binder.bind("custom.datasource."+dsPrefix,Map.class).get();
HashMap<String, Object> mergeMap = new HashMap<>();
mergeMap.putAll(commonConfig);
mergeMap.putAll(dsMap);
DataSource ds = buildDataSource(mergeMap);
customDataSources.put(dsPrefix,ds);
dataBinder(ds, mergeMap);
DynamicDataSourceContextHolder.dataSourceIds.add(dsPrefix);
}
}
private void dataBinder(DataSource dataSource, Map<String,Object> config){
MapConfigurationPropertySource source = new MapConfigurationPropertySource(config);
Binder binder = new Binder(source);
binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(dataSource));
}
}
package com.lxn.eureka.datasource2_x_properties;
import java.util.ArrayList;
import java.util.List;
/**
* @author: ligangan
* @Date: 2021/3/12
* @Time: 12:59
* 判断指定DataSource 当前是否存在
*/
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static List<String> dataSourceIds = new ArrayList<>();
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static String getDataSourceType(){
return contextHolder.get();
}
public static void clearDataSourceType () {
contextHolder.remove();
}
public static boolean containsDataSource(String dataSourceId) {
return dataSourceIds.contains(dataSourceId);
}
}
package com.lxn.eureka.datasource2_x_properties;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* @author: ligangan
* @Date: 2021/3/12
* @Time: 12:59
* 切换数据源Advice
*/
@Aspect
@Order(-1) // 保证该AOP在@Transactional 之前执行
@Component
public class DynamicDataSourceAspect {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
@Before("@annotation(ds)")
public void changeDataSource(JoinPoint joinPoint, TargetDataSource ds) throws Throwable{
String dsId = ds.name();
if(!DynamicDataSourceContextHolder.containsDataSource(dsId)){
logger.error("数据源[{}]不存在,使用默认数据源>{}",ds.name(), joinPoint.getSignature());
}else{
logger.debug("Use DataSource: {} > {}",ds.name(), joinPoint.getSignature());
DynamicDataSourceContextHolder.setDataSourceType(ds.name());
}
}
@After("@annotation(ds)")
public void restoreDataSource(JoinPoint joinPoint, TargetDataSource ds){
logger.debug("Use DataSource: {} > {}",ds.name(), joinPoint.getSignature());
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
package com.lxn.eureka.datasource2_x_properties;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* @author: ligangan
* @Date: 2021/3/12
* @Time: 12:58
* 动态数据源
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
三、1.x 与 2.x 踩坑
- 2.x中springboot 官方删除了org.springframework.boot.bind 这个包所以原有配置不再适用 新增Binder类取代
- properties文件中不支持
_
替换为-
- 使用注解的方式都是@TargetDataSource (name="")