自动化切换数据源

本文转自

基于springboot+mybatis+druid注解模式动态切换数据源,完全自动化配置模式

先看完原文,发现最重要的是AbstractRoutingDataSource,这是spring-boot-starter-jdbc里面的东西,所以使用的使用需要引用

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

先看下源码吧
org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource

属性

	// 用来存放数据源的(多数据源以map的形式)
	private Map<Object, Object> targetDataSources;

	// 默认的数据源
	private Object defaultTargetDataSource;

	private boolean lenientFallback = true;

	private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();

	// 从targetDataSources中解析出数据源对象
	private Map<Object, DataSource> resolvedDataSources;

	// 解析出来的默认的数据源对象
	private DataSource resolvedDefaultDataSource;

方法

因为实现了InitializingBean接口,所以在这个对象初始化的时候会调用afterPropertiesSet()方法,解析数据源的逻辑就在这个方法中。

	public void afterPropertiesSet() {
		// 一定要有目标数据源
		if (this.targetDataSources == null) {
			throw new IllegalArgumentException("Property 'targetDataSources' is required");
		}
		// 实例化对象
		this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());
		// 遍历对象数据源,从里面解析key,value,放入resolvedDataSources
		this.targetDataSources.forEach((key, value) -> {
			Object lookupKey = resolveSpecifiedLookupKey(key);
			DataSource dataSource = resolveSpecifiedDataSource(value);
			this.resolvedDataSources.put(lookupKey, dataSource);
		});
		// 接续默认数据源
		if (this.defaultTargetDataSource != null) {
			this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
		}
	}

解析数据源的时候,有两个方法resolveSpecifiedLookupKey,resolveSpecifiedDataSource

	protected Object resolveSpecifiedLookupKey(Object lookupKey) {
		return lookupKey;
	}

解析数据源对象,如果已经是DataSource类型了,直接返回,不是的话,从dataSourceLookup解析,这个逻辑不太了解,只要我们放目标数据源的时候,value是DataSource类型,解析的时候,直接就返回了。

	protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
		if (dataSource instanceof DataSource) {
			return (DataSource) dataSource;
		}
		else if (dataSource instanceof String) {
			return this.dataSourceLookup.getDataSource((String) dataSource);
		}
		else {
			throw new IllegalArgumentException(
					"Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
		}
	}

再看一个重要大的方法,从多数据源中找出目标数据源org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#determineTargetDataSource

	protected DataSource determineTargetDataSource() {
		Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
		// 得到key
		Object lookupKey = determineCurrentLookupKey();
		// 从map中取出目标数据源
		DataSource dataSource = this.resolvedDataSources.get(lookupKey);
		// 如果没有,lenientFallback 还是true,就设置默认的数据源
		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;
	}

determineCurrentLookupKey是一个抽象方法,需要子类实现的。

protected abstract Object determineCurrentLookupKey();

到此,源码分析完成了。
到底怎么实现多数据切换呢?可以参考思路:有了多数据源,在determineTargetDataSource的时候,只要设定好选哪个key,就可以了,而选key的方法,就是determineCurrentLookupKey,所以在这个方法中只要返回设置的key就可以。
原文代码和代码链接已经给了,我做了一些小的修改。原文代码好像是作为第三方提供给别人使用的是,所以配置了spring.factooreis文件。我是基于开发者,利用aop实现。

创建一个切面,作用是找到标注注解的方法

package com.ztryou.aop;

import com.ztryou.annotation.TargetDataSource;
import com.ztryou.config.DataSourceContextHolder;
import com.ztryou.config.DataSourceProperties;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
public class DataSourceAop {
    @Autowired
    private DataSourceProperties dataSourceProperties;

    @Pointcut("@annotation(com.ztryou.annotation.TargetDataSource)")
    public void pointcut() {
    }


    @Around(value = "pointcut()")
    public Object changeDataSource(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        TargetDataSource targetDataSource = method.getAnnotation(TargetDataSource.class);
        String dataSource = targetDataSource.value();
        if (!dataSourceProperties.getConfig().containsKey(dataSource)) {
            throw new NullPointerException(String.format("数据源配置【%s】不存在", dataSource));
        }
        try {
            DataSourceContextHolder.setDataSource(dataSource);
            Object result = joinPoint.proceed();
            return result;

        } catch (Throwable ex) {
            throw ex;
        } finally {
            DataSourceContextHolder.clearDataSource();

        }


    }

}

路由数据源的实现类,这里是简化了原作者的代码。实现bean生命周期的接口,spring会自动调用,不用我们自己显示调用,只要处理返回key值的逻辑即可。

public class DynamicMultipleDataSources extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
}

数据源配置类,这里需要说明下,spring数据源配置的顺序,默认是DataSourceAutoConfiguration自动配置,如果引入了Druid,会使用Druid的。因为它的配置在DataSourceAutoConfiguration之前,如果Druid配置之前,已经有DataSource 对象了,就不再使用Druid的DruidDataSourceWrapper,而我们自己配置的类是优先加载的。如果像原作者那样自动配置的话,一定要注意顺序。

@Configuration
@ConditionalOnClass({DruidDataSource.class})
@AutoConfigureBefore({DataSourceAutoConfiguration.class})
@EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})
@Import({DruidSpringAopConfiguration.class, DruidStatViewServletConfiguration.class, DruidWebStatFilterConfiguration.class, DruidFilterConfiguration.class})
public class DruidDataSourceAutoConfigure {
    private static final Logger LOGGER = LoggerFactory.getLogger(DruidDataSourceAutoConfigure.class);

    public DruidDataSourceAutoConfigure() {
    }

    @Bean(
        initMethod = "init"
    )
    @ConditionalOnMissingBean
    public DataSource dataSource() {
        LOGGER.info("Init DruidDataSource");
        return new DruidDataSourceWrapper();
    }
}
@Configuration
public class DynamicMultipleDataSourcesConfiguration {


    @Bean
    public DataSource dynamicMultipleDataSources(DataSourceProperties dataSourceProperties) {
        Map<String, DruidDataSource> configs = dataSourceProperties.getConfig();
        if (configs.isEmpty()) {
            throw new RuntimeException("数据库配置不存在");
        }
        if (!configs.containsKey(dataSourceProperties.getDefaultConfig())) {
            throw new RuntimeException("默认数据库必须配置");
        }
        Map<Object, Object> targetDataSources = new HashMap<>(configs.size());
        configs.keySet().forEach(key -> targetDataSources.put(key, configs.get(key)));
        DynamicMultipleDataSources dynamicMultipleDataSources = new DynamicMultipleDataSources();
        dynamicMultipleDataSources.setTargetDataSources(targetDataSources);
        dynamicMultipleDataSources.setDefaultTargetDataSource(dataSourceProperties.getDefaultDataSource());
        return dynamicMultipleDataSources;

    }
}

没有展示出的代码直接参考原作者即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值