Принцип реализации управления несколькими источниками данных Spring

база данных

Принцип реализации управления несколькими источниками данных Spring

[TOC]

Сценарии применения:

Большинство проектов с одной архитектурой подключаются к серверу базы данных, но с ростом бизнеса объем данных базы данных продолжает расти, и производительность базы данных становится узким местом. чтение-запись разделены, необходимо подключить две разные базы данных, на этот раз Spring класс управления несколькими источниками данныхAbstractRoutingDataSourceПригодится (исключая прикладной сценарий единого управления средствами управления кластером БД)

Анализ исходного кода:

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {

	private Map<Object, Object> targetDataSources;

	private Object defaultTargetDataSource;

	private boolean lenientFallback = true;

	private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();

	private Map<Object, DataSource> resolvedDataSources;

	private DataSource resolvedDefaultDataSource;
	...
}

Из исходного кода видно, что этот класс является абстрактным классом, определяющим 6 атрибутов.targetDataSources: тип карты. Это свойство используется для поддержки нескольких источников данных в проекте. defaultTargetDataSource: интуитивно понятно понять его роль по имени свойства (источник данных по умолчанию). lenientFallback: значение по умолчанию верно, менять не нужно dataSourceLookup: поиск имени интерфейса источника данных разрешенные источники данных: если поле не назначено, оно равно targetDataSources. разрешенный дефаултдатасаурце: измененный источник данных

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;

}

Этот класс является интерфейсом и имеет только один метод, getDataSource.Имя параметра метода должно быть четким, и имя источника данных символьного типа должно быть передано для получения DataSource.

Глубокое понимание:

Целью использования источника данных является получение соединения Далее рассмотрим метод getConnection класса AbstractRoutingDataSource.

@Override
	public Connection getConnection() throws SQLException {
		return determineTargetDataSource().getConnection();
	}

	@Override
	public Connection getConnection(String username, String password) throws SQLException {
		return determineTargetDataSource().getConnection(username, password);
	}

Перейдите непосредственно к методу defineTargetDataSource.

/**
	 * 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, чтобы переопределить DefinityCurrentLookupKey.

/**
 * @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<String> contextHolder = new ThreadLocal<String>();
    /**
     * @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<String> 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<String> 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<String> getconfigs() {
        Set<String> configs = new HashSet<>();
        Reflections reflections = new Reflections(new ConfigurationBuilder().addUrls(ClasspathHelper.forPackage(pacakgePath)));
        //获取所有标记@DataSource的类
        Set<Class<?>> typesAnnotatedWith = reflections.getTypesAnnotatedWith(DataSource.class);
        Iterator<Class<?>> 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<Method> methodsAnnotatedWith = reflections.getMethodsAnnotatedWith(DataSource.class);
        Iterator<Method> 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;
    }
}

адрес проекта:Каталог GitHub.com/always-java/…

Вышеупомянутые статьи являются оригинальными статьями, при перепечатке просьба указывать автора@чудак QQ:208275451