java 多数据源_GitHub - yz-java/multiple-data-sources: spring多数据源管理

Spring多数据源管理实现原理

[TOC]

应用场景:

大部分单一架构项目连接一台数据库服务器,但随着业务的增加数据库数据量不断飙升,数据库达到性能瓶颈,大部分技术人员都会对数据库主从配置;既然读写分离那就需要连接两个不同的数据库,这时候Spring多数据源管理类AbstractRoutingDataSource就要派上用场了(排除使用数据库集群管理工具统一管理的应用场景)

源码分析:

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {

private Map targetDataSources;

private Object defaultTargetDataSource;

private boolean lenientFallback = true;

private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();

private Map resolvedDataSources;

private DataSource resolvedDefaultDataSource;

...

}

通过源码可以看出该类是一个抽象类,定义了6个属性。

targetDataSources:是一个map类型该属性正是用来维护项目中多个数据源

defaultTargetDataSource:通过属性名很直观的可以理解它的作用(默认数据源)

lenientFallback:默认为true,无需改动

dataSourceLookup:查找数据源接口的名称

resolvedDataSources:如果该字段没有赋值,就是targetDataSources

resolvedDefaultDataSource:改变后的数据源

public interface DataSourceLookup {

/**

* Retrieve the DataSource identified by the given name.

* @param dataSourceName the name of the DataSource

* @return the DataSource (never {@code null})

* @throws DataSourceLookupFailureException if the lookup failed

*/

DataSource getDataSource(String dataSourceName) throws DataSourceLookupFailureException;

}

该类是一个interface并且只有一个方法getDataSource,通过方法的参数名称应该清楚传入一个字符类型的数据源名称获取DataSource

深入理解:

使用数据源的目的就是要获取Connection,接下来就从AbstractRoutingDataSource的getConnection方法一探究竟。

@Override

public Connection getConnection() throws SQLException {

return determineTargetDataSource().getConnection();

}

@Override

public Connection getConnection(String username, String password) throws SQLException {

return determineTargetDataSource().getConnection(username, password);

}

直接进入determineTargetDataSource方法

/**

* Retrieve the current target DataSource. Determines the

* {@link #determineCurrentLookupKey() current lookup key}, performs

* a lookup in the {@link #setTargetDataSources targetDataSources} map,

* falls back to the specified

* {@link #setDefaultTargetDataSource default target DataSource} if necessary.

* @see #determineCurrentLookupKey()

*/

protected DataSource determineTargetDataSource() {

Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");

//该方法是一个抽象方法,返回要从resolvedDataSources查找key,该方法还会实现检查线程绑定事务上下文。

Object lookupKey = determineCurrentLookupKey();

//从resolvedDataSources中取出数据源并返回

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;

}

###代码实现

实现AbstractRoutingDataSource重写determineCurrentLookupKey

/**

* @author yangzhao

* Created by 17/2/7.

*/

public class DynamicDataSource extends AbstractRoutingDataSource {

@Override

protected Object determineCurrentLookupKey() {

String dataSourceName = DataSourceContextHolder.getDataSourceName();

return dataSourceName;

}

}

定义DataSourceContextHolder

/**

* 该类内部维护了{@link ThreadLocal}

* @author yangzhao

* Created by 17/2/7.

*/

public class DataSourceContextHolder {

private static final ThreadLocal contextHolder = new ThreadLocal();

/**

* @Description: 设置数据源类型

* @param dataSourceName 数据源名称

* @return void

* @throws

*/

public static void setDataSourceName(String dataSourceName) {contextHolder.set(dataSourceName);}

/**

* @Description: 获取数据源名称

* @param

* @return String

* @throws

*/

public static String getDataSourceName() {

return contextHolder.get();

}

/**

* @Description: 清除数据源名称

* @param

* @return void

* @throws

*/

public static void clearDataSource() {

contextHolder.remove();

}

}

通过ThreadLocal类使每个线程获取独立的数据源,防止并发访问时获取错误的数据源

基于SpringAop实现数据源动态切换

注解类DataSource

/**

* 数据源

* Created by yangzhao on 17/2/7.

*/

@Target({ElementType.METHOD, ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface DataSource {

String value() default "defaultSource";

}

增强类(DataSouceAdvisor)

/**

* 增强类

* 实现MethodInterceptor接口,通过反射动态解析方法是否标注@DataSource {@link DataSource}注解。

* 如果已标注@DataSource注解取值,set到{@link DataSourceContextHolder}

* @author yangzhao

* create by 17/10/20

*/

@Component("dataSourceAdvisor")

public class DataSouceAdvisor implements MethodInterceptor {

@Override

public Object invoke(MethodInvocation methodInvocation) throws Throwable {

Method method = methodInvocation.getMethod();

Object aThis = methodInvocation.getThis();

//设置默认数据库

DataSourceContextHolder.setDataSourceName("defaultSource");

DataSource dataSource = aThis.getClass().getAnnotation(DataSource.class);

if (dataSource!=null){

DataSourceContextHolder.setDataSourceName(dataSource.value());

}

dataSource = method.getAnnotation(DataSource.class);

if (dataSource!=null){

DataSourceContextHolder.setDataSourceName(dataSource.value());

}

Object proceed = null;

try {

proceed = methodInvocation.proceed();

}catch (Exception e){

throw e;

}finally {

DataSourceContextHolder.clearDataSource();

}

return proceed;

}

}

核心管理类(DataSourceManager真正实现切换)

/**

* 数据源切换管理类

*

* @author yangzhao

* Created by 17/2/7.

*/

@Component

public class DataSourceManager implements BeanFactoryPostProcessor {

private final Logger logger = LogManager.getLogger(DataSourceManager.class);

/**

* 扫描包

* 一般项目都是以com开头所以这里默认为com

*/

private String pacakgePath = "com";

@Override

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

//getconfigs

List configs = getconfigs().stream().collect(Collectors.toList());

//打印所有生成的expression配置信息

configs.forEach(s -> logger.info(s));

//设置aop信息

setAopInfo(configs,beanFactory);

}

/**

* 设置注册bean动态AOP信息

* @param configs

* @param beanFactory

*/

private void setAopInfo(List configs, ConfigurableListableBeanFactory beanFactory) {

if (beanFactory instanceof BeanDefinitionRegistry){

BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) beanFactory;

for (String config :configs) {

//增强器

RootBeanDefinition advisor = new RootBeanDefinition(DefaultBeanFactoryPointcutAdvisor.class);

advisor.getPropertyValues().addPropertyValue("adviceBeanName",new RuntimeBeanReference("dataSourceAdvisor").getBeanName());

//切点类

RootBeanDefinition pointCut = new RootBeanDefinition(AspectJExpressionPointcut.class);

pointCut.setScope(BeanDefinition.SCOPE_PROTOTYPE);

pointCut.setSynthetic(true);

pointCut.getPropertyValues().addPropertyValue("expression",config);

advisor.getPropertyValues().addPropertyValue("pointcut",pointCut);

//注册到spring容器

String beanName = BeanDefinitionReaderUtils.generateBeanName(advisor, beanDefinitionRegistry,false);

beanDefinitionRegistry.registerBeanDefinition(beanName,advisor);

}

}

}

public Set getconfigs() {

Set configs = new HashSet<>();

Reflections reflections = new Reflections(new ConfigurationBuilder().addUrls(ClasspathHelper.forPackage(pacakgePath)));

//获取所有标记@DataSource的类

Set> typesAnnotatedWith = reflections.getTypesAnnotatedWith(DataSource.class);

Iterator> iterator = typesAnnotatedWith.iterator();

while (iterator.hasNext()){

Class> next = iterator.next();

//获取该类所有方法

Method[] declaredMethods = next.getDeclaredMethods();

for (Method method:declaredMethods){

String classAndMethod = method.getDeclaringClass().getCanonicalName()+"."+method.getName();

//生成expression配置

String expression = "execution (* "+classAndMethod+"(..))";

configs.add(expression);

}

}

reflections = new Reflections(new ConfigurationBuilder().setUrls(ClasspathHelper.forPackage(pacakgePath)).setScanners(new MethodAnnotationsScanner()));

//获取所有类中标记@DataSource的方法

Set methodsAnnotatedWith = reflections.getMethodsAnnotatedWith(DataSource.class);

Iterator it = methodsAnnotatedWith.iterator();

while (it.hasNext()){

Method method = it.next();

String classAndMethod = method.getDeclaringClass().getCanonicalName()+"."+method.getName();

//生成expression配置

String expression = "execution (* "+classAndMethod+"(..))";

configs.add(expression);

}

return configs;

}

}

以上属于原创文章,转载请注明作者@怪咖

QQ:208275451

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值