美文网首页
dynamic-datasource 如何在多数据源中选择目标数

dynamic-datasource 如何在多数据源中选择目标数

作者: ithankzc | 来源:发表于2022-03-30 10:22 被阅读0次

    项目依赖如下 jar 包

    compile("com.baomidou:dynamic-datasource-spring-boot-starter:3.5.0")
    

    本文概述

    • 基于 dynamic-datasource-spring-boot-starter 实现了多数据源中目标数据源的选择
    • 读写等主要逻辑放在抽象类实现类加注解方式减少代码量及提升可维护度

    具体实现

    定义一个 抽象类

    @Service
    abstract class AccountCoreService() {
     //  这里定义方法,方法调用 mapper 查数据
    }
    

    定义一个分表类

    @DS("sharding")
    @Service
    class ShardingAccountCoreService() : AccountCoreService()
    

    定义一个旧库旧表类

    @DS("single")
    @Primary
    @Service
    class SingleAccountCoreService() : AccountCoreService()
    

    为什么 SingleAccountCoreService, ShardingAccountCoreService 类要加这些注解

    dynamic-datasource-spring-boot-starter 织入了AOP, 对有加 @DS 的类或者方法进行取值,确定数据源。

    织入关键代码

    DynamicDataSourceAnnotationAdvisor.java

        @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
        @Bean
        @ConditionalOnProperty(prefix = DynamicDataSourceProperties.PREFIX + ".aop", name = "enabled", havingValue = "true", matchIfMissing = true)
        public Advisor dynamicDatasourceAnnotationAdvisor(DsProcessor dsProcessor) {
            DynamicDatasourceAopProperties aopProperties = properties.getAop();
            DynamicDataSourceAnnotationInterceptor interceptor = new DynamicDataSourceAnnotationInterceptor(aopProperties.getAllowedPublicOnly(), dsProcessor);
            DynamicDataSourceAnnotationAdvisor advisor = new DynamicDataSourceAnnotationAdvisor(interceptor, DS.class);
            advisor.setOrder(aopProperties.getOrder());
            return advisor;
        }
    

    Spring boot 拦截器触发的关键代码

    CglibAopProxy.java

        private static class CglibMethodInvocation extends ReflectiveMethodInvocation {
    
            private final MethodProxy methodProxy;
    
            private final boolean publicMethod;
    
            public CglibMethodInvocation(Object proxy, Object target, Method method, Object[] arguments,
                    Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers, MethodProxy methodProxy) {
    
                super(proxy, target, method, arguments, targetClass, interceptorsAndDynamicMethodMatchers);
                this.methodProxy = methodProxy;
                this.publicMethod = Modifier.isPublic(method.getModifiers());
            }
    
            /**
             * Gives a marginal performance improvement versus using reflection to
             * invoke the target when invoking public methods.
             */
            @Override
            protected Object invokeJoinpoint() throws Throwable {
                if (this.publicMethod) {
                    return this.methodProxy.invoke(this.target, this.arguments);
                }
                else {
                    return super.invokeJoinpoint();
                }
            }
        }
    

    网上上关于spring 拦截器处理过程的分析代码,可以帮助理解

    https://blog.csdn.net/qq_39794062/article/details/117337505
    https://www.cnblogs.com/zhuxudong/p/10327833.html

    如何确定数据源

    1. 可以看这个文件 DynamicDataSourceAnnotationInterceptor, 实现了 MethodInterceptor, 在 invoke 这个方式实现了数据源的选择

    DynamicDataSourceAnnotationInterceptor.java

    /*
     * Copyright © 2018 organization baomidou
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    package com.baomidou.dynamic.datasource.aop;
    
    import com.baomidou.dynamic.datasource.processor.DsProcessor;
    import com.baomidou.dynamic.datasource.support.DataSourceClassResolver;
    import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
    import org.aopalliance.intercept.MethodInterceptor;
    import org.aopalliance.intercept.MethodInvocation;
    
    /**
     * Core Interceptor of Dynamic Datasource
     *
     * @author TaoYu
     * @since 1.2.0
     */
    public class DynamicDataSourceAnnotationInterceptor implements MethodInterceptor {
    
        /**
         * The identification of SPEL.
         */
        private static final String DYNAMIC_PREFIX = "#";
    
        private final DataSourceClassResolver dataSourceClassResolver;
        private final DsProcessor dsProcessor;
    
        public DynamicDataSourceAnnotationInterceptor(Boolean allowedPublicOnly, DsProcessor dsProcessor) {
            dataSourceClassResolver = new DataSourceClassResolver(allowedPublicOnly);
            this.dsProcessor = dsProcessor;
        }
    
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            // 这里是确定当前数据源的关键
            String dsKey = determineDatasourceKey(invocation);
            DynamicDataSourceContextHolder.push(dsKey);
            try {
                return invocation.proceed();
            } finally {
                DynamicDataSourceContextHolder.poll();
            }
        }
    
        private String determineDatasourceKey(MethodInvocation invocation) {
            String key = dataSourceClassResolver.findKey(invocation.getMethod(), invocation.getThis());
            return key.startsWith(DYNAMIC_PREFIX) ? dsProcessor.determineDatasource(invocation, key) : key;
        }
    }
    
    1. 关键代码2 :不断查找注解,确定数据源
        /**
         * 查找注解的顺序
         * 1. 当前方法
         * 2. 桥接方法
         * 3. 当前类开始一直找到Object
         * 4. 支持mybatis-plus, mybatis-spring
         *
         * @param method       方法
         * @param targetObject 目标对象
         * @return ds
         */
        public String findKey(Method method, Object targetObject) {
            if (method.getDeclaringClass() == Object.class) {
                return "";
            }
            Object cacheKey = new MethodClassKey(method, targetObject.getClass());
            String ds = this.dsCache.get(cacheKey);
            if (ds == null) {
                ds = computeDatasource(method, targetObject);
                if (ds == null) {
                    ds = "";
                }
                this.dsCache.put(cacheKey, ds);
            }
            return ds;
       }
    
        private String computeDatasource(Method method, Object targetObject) {
            if (allowedPublicOnly && !Modifier.isPublic(method.getModifiers())) {
                return null;
            }
            //1. 从当前方法接口中获取
            String dsAttr = findDataSourceAttribute(method);
            if (dsAttr != null) {
                return dsAttr;
            }
            Class<?> targetClass = targetObject.getClass();
            Class<?> userClass = ClassUtils.getUserClass(targetClass);
            // JDK代理时,  获取实现类的方法声明.  method: 接口的方法, specificMethod: 实现类方法
            Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass);
    
            specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
            //2. 从桥接方法查找
            dsAttr = findDataSourceAttribute(specificMethod);
            if (dsAttr != null) {
                return dsAttr;
            }
            // 从当前方法声明的类查找
            dsAttr = findDataSourceAttribute(userClass);
            if (dsAttr != null && ClassUtils.isUserLevelMethod(method)) {
                return dsAttr;
            }
            //since 3.4.1 从接口查找,只取第一个找到的
            for (Class<?> interfaceClazz : ClassUtils.getAllInterfacesForClassAsSet(userClass)) {
                dsAttr = findDataSourceAttribute(interfaceClazz);
                if (dsAttr != null) {
                    return dsAttr;
                }
            }
            // 如果存在桥接方法
            if (specificMethod != method) {
                // 从桥接方法查找
                dsAttr = findDataSourceAttribute(method);
                if (dsAttr != null) {
                    return dsAttr;
                }
                // 从桥接方法声明的类查找
                dsAttr = findDataSourceAttribute(method.getDeclaringClass());
                if (dsAttr != null && ClassUtils.isUserLevelMethod(method)) {
                    return dsAttr;
                }
            }
            return getDefaultDataSourceAttr(targetObject);
        }
        
    
    1. findKey 多数据源查询数据源的优化


      image.png

    总结

    通过读源码,让自己了解组件运行的过程,更放心的用这个组件,也方便后续排查问题

    相关文章

      网友评论

          本文标题:dynamic-datasource 如何在多数据源中选择目标数

          本文链接:https://www.haomeiwen.com/subject/rknrdrtx.html