返回顶部
首页 > 资讯 > 后端开发 > Python >一文搞懂MyBatis多数据源Starter实现
  • 958
分享到

一文搞懂MyBatis多数据源Starter实现

MyBatis多数据源StarterMyBatis多数据源多数据源Starter 2023-05-16 15:05:24 958人浏览 泡泡鱼

Python 官方文档:入门教程 => 点击学习

摘要

目录前言正文一. 实现思路二. 自动装配实现三. 配置加载四. 数据源初始化五. mybatis初始化六. SpringBoot数据源原生自动装配抑制总结前言 本文将实现一个MyBa

前言

本文将实现一个MyBatisSpringbootStarter包,引用这个Starter包后,仅需要提供少量配置信息,就能够完成MyBatis多数据源的初始化和使用,相较于MyBatis官方的Starter包,扩展了多数据源的使用场景。

本文的所有源码可以从如下仓库下载。

multidatasource GitHub

Springboot版本:2.7.6

正文

一. 实现思路

要实现Starter包,肯定需要借助Springboot的自动装配机制,所以我们首先需要提供自动装配的配置类。

然后我们需要加载多个数据源的配置并且生成对应的数据源,同时还需要可以根据用户配置的type创建不同的数据源,例如可以支持创建HikariCPDruidTomcatJdbc的数据源。

创建出来的数据源需要根据用户的配置,设置给不同的SqlSessionFactory,然后不同的SqlSessionFactory设置给不同的MapperScannerConfigurer,最终实现的效果就是一部分映射接口使用一个数据源,另一部分映射接口使用另一个数据源。

最后,还需要提供一种手段,抑制Springboot原生的数据源加载,这个功能我们可以通过ApplicationContextInitializer这个扩展点来完成。

整体的一个思维导图如下所示。

二. 自动装配实现

由于适配的是Springboot2.7.x版本,所以需要在resources\META-INF\spring目录下创建org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,且内容如下所示。

com.lee.multidatasource.autoconfig.LeeMultiPersistenceAutoConfiguration

上述的LeeMultiPersistenceAutoConfiguration就是完成自动装配的配置类,实现如下。

@AutoConfiguration
@Import({LeeMultiPersistenceConfiguration.class, DataSourceBeanPostProcessor.class,})
public class LeeMultiPersistenceAutoConfiguration {}

通过LeeMultiPersistenceAutoConfiguration导入了两个bean,一个是LeeMultiPersistenceConfiguration,用于加载配置以及创建数据源和MyBatisbean,另一个是DataSourceBeanPostProcessor,用于对LeeMultiPersistenceConfiguration创建的bean做一些后置处理。

上述就是自动装配的实现,主要就是将LeeMultiPersistenceAutoConfiguration注册到容器中,而LeeMultiPersistenceAutoConfiguration也是整个Starter包实现的关键。

三. 配置加载

约定数据源和MyBatis的配置需要遵循如下规则。

lee:
  persistence:
    dataSourceName1:
      datasource:
        type: ...
        max-lifetime: ...
        keep-alive-time: ...
        driver-class-name: ...
        url: ...
        username: ...
        passWord: ...
        pool-name: ...
      mybatis:
        configLocation: ...
        basePackage: ...
    dataSourceName2:
      datasource:
        max-lifetime: ...
        keep-alive-time: ...
        driver-class-name: ...
        url: ...
        username: ...
        password: ...
        pool-name: ...
      mybatis:
        configLocation: ...
        basePackage: ...

lee.persistence的下一级的配置,是数据源的名字,可以由用户自定义,这个名字最终会作为数据源的beanIOC容器中的名字。

lee.persistence.dataSourceName的下一级的配置,固定是两个配置项,其一是lee.persistence.dataSourceName.datasource,用于设置数据源相关的配置,其二是lee.persistence.dataSourceName.mybatis,用于设置MyBatis相关的配置。

由于数据源的名字和个数都可以由用户自定义,那么很难基于@ConfigurationProperties注解来一步到位的完成上述数据源配置的加载,我们需要基于Environment来自行处理。

下面来看一下用于处理数据源配置的LeeMultiPersistenceConfiguration的类图,如下所示。

LeeMultiPersistenceConfiguration首先实现了EnvironmentAware接口,从而可以拿到Environment对象,其次实现了ImportBeanDefinitionRegistrar接口,从而可以向Spring注册BeanDefinition,那么实际上LeeMultiPersistenceConfiguration做的事情就是加载配置以及向Spring注册数据源和MyBatis相关组件的BeanDefinition。下面看一下LeeMultiPersistenceConfiguration实现的registerBeanDefinitions() 方法。

@Override
public void reGISterBeanDefinitions(AnnotationMetadata importinGClaSSMetadata,
                                    BeanDefinitionRegistry registry) {
    // 加载并解析数据源配置
    MultiPersistenceProperties multiPersistenceProperties = parseMultiPersistenceProperties();
    List<String> persistenceNames = multiPersistenceProperties.getPersistenceNames();
    // 为每个数据源注册BeanDefinition
    for (String persistenceName : persistenceNames) {
        registerDatasource(registry, persistenceName, multiPersistenceProperties.getDataSourceProperties(persistenceName));
        registersqlSessionFactory(registry, persistenceName, multiPersistenceProperties.getMybatisProperties(persistenceName));
        registerMapperScannerConfigurer(registry, persistenceName, multiPersistenceProperties.getMybatisProperties(persistenceName));
    }
}

本节就先分析一下数据源配置的加载和解析,具体就是LeeMultiPersistenceConfigurationparseMultiPersistenceProperties() 方法,如下所示。

private MultiPersistenceProperties parseMultiPersistenceProperties() {
    MultiPersistenceProperties multiPersistenceProperties = new MultiPersistenceProperties();
    // 将数据源相关的配置加载为MultiPersistencePropertiesWrapper
    MultiPersistencePropertiesWrapper multiPersistencePropertiesWrapper = parseMultiPersistencePropertiesWrapper();
    List<String> persistenceNames = multiPersistencePropertiesWrapper.getPersistenceNames();
    // 遍历每一个数据源并拿到这个数据源下数据源相关配置和MyBatis相关配置
    for (String persistenceName : persistenceNames) {
        DataSourceProperties dataSourceProperties = multiPersistencePropertiesWrapper
                .getPersistenceDataSourceProperties(persistenceName);
        MybatisExtendProperties mybatisProperties = multiPersistencePropertiesWrapper
                .getPersistenceMybatisProperties(persistenceName);
        // 添加当前数据源的配置信息到MultiPersistenceProperties中
        multiPersistenceProperties.addPersistenceProperties(
                persistenceName, dataSourceProperties, mybatisProperties);
    }
    return multiPersistenceProperties;
}

private MultiPersistencePropertiesWrapper parseMultiPersistencePropertiesWrapper() {
    Map<String, Map<String, Map<String, String>>> persistenceProperties;

    Binder binder = Binder.get(environment);
    // PERSISTENCE_PREFIX为lee.persistence
    persistenceProperties = binder.bind(PERSISTENCE_PREFIX, Bindable.of(Map.class)).get();
    persistencePropertiesCache = persistenceProperties;

    return new MultiPersistencePropertiesWrapper(persistenceProperties);
}

上述方法解析数据源配置是在parseMultiPersistencePropertiesWrapper() 方法中,思路是先将我们的数据源配置基于Binder解析为Map的形式,因为lee.persistence这个配置前缀已经是确定的,所以在Binderbind() 方法中传入的配置名就是lee.persistence,那么最终得到的配置的Map实际内容如下所示。

// 数据源名就是上面示例的dataSourceName1
// 配置类型就是datasource或mybatis
Map[数据源名, Map[配置类型, Map[配置key, 配置value]]]

通常上述这种层级很多的Map,无论是可读性还是易用性都很差,所以我们可以使用一个包装类来包装一下这种Map,并进行适当充血,来方便对这种多层级Map的使用,这里的包装类就是MultiPersistencePropertiesWrapper,如下所示。

public class MultiPersistencePropertiesWrapper {

    private Map<String, Map<String, Map<String, String>>> multiPersistenceProperties;

    public MultiPersistencePropertiesWrapper(Map<String, Map<String, Map<String, String>>> multiPersistenceProperties) {
        this.multiPersistenceProperties = multiPersistenceProperties;
    }

    // 只对multiPersistenceProperties提供set方法
    public void setMultiPersistenceProperties(Map<String, Map<String, Map<String, String>>> multiPersistenceProperties) {
        this.multiPersistenceProperties = multiPersistenceProperties;
    }

    // 获取数据源的个数
    public int getPersistenceSize() {
        return multiPersistenceProperties.size();
    }

    // 获取所有数据源的名字
    public List<String> getPersistenceNames() {
        return new ArrayList<>(multiPersistenceProperties.keySet());
    }

    // 获取某个数据源对应的数据源的配置类DataSourceProperties
    public DataSourceProperties getPersistenceDataSourceProperties(String persistenceName) {
        DataSourceProperties dataSourceProperties = new DataSourceProperties();
        Map<String, Map<String, String>> persistenceProperties = multiPersistenceProperties.get(persistenceName);
        Map<String, String> persistenceDatasourceProperties = persistenceProperties.get(KEY_DATASOURCE);
        if (ObjectUtils.isNotEmpty(persistenceDatasourceProperties) || !persistenceDatasourceProperties.isEmpty()) {
            Binder binder = new Binder(new MapConfigurationPropertySource(persistenceDatasourceProperties));
            dataSourceProperties = binder.bind(StringUtils.EMPTY, Bindable.of(DataSourceProperties.class)).get();
        }
        return dataSourceProperties;
    }

    // 获取某个数据源对应的MyBatis的配置类MybatisExtendProperties
    public MybatisExtendProperties getPersistenceMybatisProperties(String persistenceName) {
        MybatisExtendProperties mybatisProperties = new MybatisExtendProperties();
        Map<String, Map<String, String>> persistenceProperties = multiPersistenceProperties.get(persistenceName);
        Map<String, String> persistenceMybatisProperties = persistenceProperties.get(KEY_MYBATIS);
        if (ObjectUtils.isNotEmpty(persistenceMybatisProperties) && !persistenceMybatisProperties.isEmpty()) {
            Binder binder = new Binder(new MapConfigurationPropertySource(persistenceMybatisProperties));
            mybatisProperties = binder.bind(StringUtils.EMPTY, Bindable.of(MybatisExtendProperties.class)).get();
        }
        return mybatisProperties;
    }

}

MultiPersistencePropertiesWrapper中仅提供了对数据源配置Mapset方法,然后提供了多个有具体含义的get方法,例如拿到数据源的个数,名字集合以及某个数据源的DataSourceProperties或者MybatisExtendProperties,当然,如何将某个数据源对应的配置的Map解析为DataSourcePropertiesMybatisExtendProperties,还是依靠的Binder,这里就不再赘述,可以自己看一下上述代码的具体实现。

这里再多提一下,DataSourceProperties这个是Springboot提供的数据源的配置类,而MybatisExtendProperties是我们自定义的一个继承于MybatisPropertiesMyBatis官方启动包提供)的配置类,主要是扩展了一个叫做basePackage的属性,用于配置映射接口路径,如下所示。

public class MybatisExtendProperties extends MybatisProperties {

    private String basePackage;

    public String getBasePackage() {
        return basePackage;
    }

    public void setBasePackage(String basePackage) {
        this.basePackage = basePackage;
    }

}

现在回到LeeMultiPersistenceConfigurationparseMultiPersistenceProperties() 方法,再贴出其实现如下。

private MultiPersistenceProperties parseMultiPersistenceProperties() {
    MultiPersistenceProperties multiPersistenceProperties = new MultiPersistenceProperties();
    // 将数据源相关的配置加载为MultiPersistencePropertiesWrapper
    MultiPersistencePropertiesWrapper multiPersistencePropertiesWrapper = parseMultiPersistencePropertiesWrapper();
    List<String> persistenceNames = multiPersistencePropertiesWrapper.getPersistenceNames();
    // 遍历每一个数据源并拿到这个数据源下数据源相关配置和MyBatis相关配置
    for (String persistenceName : persistenceNames) {
        DataSourceProperties dataSourceProperties = multiPersistencePropertiesWrapper
                .getPersistenceDataSourceProperties(persistenceName);
        MybatisExtendProperties mybatisProperties = multiPersistencePropertiesWrapper
                .getPersistenceMybatisProperties(persistenceName);
        // 添加当前数据源的配置信息到MultiPersistenceProperties中
        multiPersistenceProperties.addPersistenceProperties(
                persistenceName, dataSourceProperties, mybatisProperties);
    }
    return multiPersistenceProperties;
}

在完成所有数据源配置加载并且生成包装类后,我们做的事情就是遍历每一个数据源的名字,然后通过数据源名字从包装类中拿到对应的DataSourcePropertiesMybatisExtendProperties,最后添加到MultiPersistenceProperties中,而MultiPersistenceProperties就是我们最终希望得到的多数据源的配置类,如下所示。

public class MultiPersistenceProperties {

    private final Map<String, PersistenceProperties> persistencePropertiesMap = new HashMap<>(HASH_MAP_INITIAL_SIZE);
    
    // 将DataSourceProperties和MybatisExtendProperties封装为PersistenceProperties
    public void addPersistenceProperties(String persistenceName,
                                         DataSourceProperties dataSourceProperties,
                                         MybatisExtendProperties mybatisProperties) {
        PersistenceProperties persistenceProperties = new PersistenceProperties(dataSourceProperties, mybatisProperties);
        persistencePropertiesMap.put(persistenceName, persistenceProperties);
    }

    public List<String> getPersistenceNames() {
        return new ArrayList<>(persistencePropertiesMap.keySet());
    }

    public PersistenceProperties getPersistenceProperties(String persistenceName) {
        return persistencePropertiesMap.get(persistenceName);
    }

    public DataSourceProperties getDataSourceProperties(String persistenceName) {
        PersistenceProperties persistenceProperties = persistencePropertiesMap.get(persistenceName);
        if (ObjectUtils.isNotEmpty(persistenceProperties)) {
            return persistenceProperties.getDataSourceProperties();
        }
        throw new RuntimeException();
    }

    public MybatisExtendProperties getMybatisProperties(String persistenceName) {
        PersistenceProperties persistenceProperties = persistencePropertiesMap.get(persistenceName);
        if (ObjectUtils.isNotEmpty(persistenceProperties)) {
            return persistenceProperties.getMybatisProperties();
        }
        throw new RuntimeException();
    }

    public static class PersistenceProperties {
        private DataSourceProperties dataSourceProperties;
        private MybatisExtendProperties mybatisProperties;

        public PersistenceProperties(DataSourceProperties dataSourceProperties,
                                     MybatisExtendProperties mybatisProperties) {
            this.dataSourceProperties = dataSourceProperties;
            this.mybatisProperties = mybatisProperties;
        }

        public DataSourceProperties getDataSourceProperties() {
            return dataSourceProperties;
        }

        public void setDataSourceProperties(DataSourceProperties dataSourceProperties) {
            this.dataSourceProperties = dataSourceProperties;
        }

        public MybatisExtendProperties getMybatisProperties() {
            return mybatisProperties;
        }

        public void setMybatisProperties(MybatisExtendProperties mybatisProperties) {
            this.mybatisProperties = mybatisProperties;
        }
    }

}

我们在MultiPersistenceProperties中也进行了适当充血,首先将DataSourcePropertiesMybatisExtendProperties封装为了PersistenceProperties,然后将数据源名字作为key,数据源对应的PersistenceProperties作为value,存储到persistencePropertiesMap这个Map中,最后提供了若干get方法来实现对数据源对应的PersistenceProperties的访问。

那么至此,我们就完成了本节一开始定义的多数据源配置的加载,最终加载完毕后得到的多数据源的配置类就是MultiPersistenceProperties,并且数据源个数,数据源名字和数据源类型完全可以自定义。

四. 数据源初始化

在第三节中已经拿到了多数据源的配置信息,并且被我们解析为了一个易用性很强的配置类MultiPersistenceProperties,那么本节将介绍如何完成数据源的初始化,也就是如何创建数据源的bean并注册到Spring容器中。

首先回到LeeMultiPersistenceConfiguration实现的registerBeanDefinitions() 方法,如下所示。

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                    BeanDefinitionRegistry registry) {
    // 拿到多数据源配置类MultiPersistenceProperties
    MultiPersistenceProperties multiPersistenceProperties = parseMultiPersistenceProperties();
    List<String> persistenceNames = multiPersistenceProperties.getPersistenceNames();
    for (String persistenceName : persistenceNames) {
        // 注册每个数据源对应的数据源bean到spring容器中
        registerDatasource(registry, persistenceName, multiPersistenceProperties.getDataSourceProperties(persistenceName));
        registerSqlSessionFactory(registry, persistenceName, multiPersistenceProperties.getMybatisProperties(persistenceName));
        registerMapperScannerConfigurer(registry, persistenceName, multiPersistenceProperties.getMybatisProperties(persistenceName));
    }
}

跟进registerDatasource() 方法,如下所示。

private void registerDatasource(BeanDefinitionRegistry registry,
                                String persistenceName,
                                DataSourceProperties dataSourceProperties) {
    // 拿到具体数据源对应的BeanDefinitionBuilder
    // 如果没有配置数据源类型则默认是HikariCP
    BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(
            ObjectUtils.isNotEmpty(dataSourceProperties.getType()) ? dataSourceProperties.getType() : DEFAULT_DATASOURCE_CLASS);

    // 创建数据源的BeanDefinition并完成注册
    registry.registerBeanDefinition(persistenceName, beanDefinitionBuilder.getBeanDefinition());
}

这里的注册bean实际就是注册BeanDefinition,依赖BeanDefinitionBuilder来创建对应数据源的BeanDefinition,注意到这里好像仅仅只是将数据源的BeanDefinition创建出来然后就注册到BeanDefinitionRegistry中了,并没有进行一些数据源的属性相关的设置,那么数据源的属性是怎么被设置的呢,还记得在第二节中我们通过LeeMultiPersistenceAutoConfiguration导入了一个叫做DataSourceBeanPostProcessorbean后置处理器吗,数据源的属性的设置就是在这个后置处理器中完成的,下面一起看一下。

public class DataSourceBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 不同的数据源类型走不同的逻辑
        if (bean instanceof HikariDataSource) {
            assembleHikariDataSource((HikariDataSource) bean, beanName);
        } else if (bean instanceof DruidDataSource) {
            assembleDruidDatasource((DruidDataSource) bean, beanName);
        } else if (bean instanceof DataSource) {
            assembleTomcatJdbcDatasource((DataSource) bean, beanName);
        }
        return bean;
    }

    private void assembleHikariDataSource(HikariDataSource dataSource, String persistenceName) {
        Map<String, String> persistenceDatasourceProperties = LeeMultiPersistenceConfiguration
                .getPersistenceDatasourceProperties(persistenceName);
        dataSource.setJdbcUrl(persistenceDatasourceProperties.get(KEY_URL));
        Binder binder = new Binder(new MapConfigurationPropertySource(persistenceDatasourceProperties));
        binder.bind(StringUtils.EMPTY, Bindable.ofInstance(dataSource));
    }

    private void assembleDruidDatasource(DruidDataSource dataSource, String persistenceName) {
        Map<String, String> persistenceDatasourceProperties = LeeMultiPersistenceConfiguration
                .getPersistenceDatasourceProperties(persistenceName);
        Binder binder = new Binder(new MapConfigurationPropertySource(persistenceDatasourceProperties));
        binder.bind(StringUtils.EMPTY, Bindable.ofInstance(dataSource));
    }

    private void assembleTomcatJdbcDatasource(DataSource dataSource, String persistenceName) {
        Map<String, String> persistenceDatasourceProperties = LeeMultiPersistenceConfiguration
                .getPersistenceDatasourceProperties(persistenceName);
        Binder binder = new Binder(new MapConfigurationPropertySource(persistenceDatasourceProperties));
        binder.bind(StringUtils.EMPTY, Bindable.ofInstance(dataSource));
    }

}

// 在加载并解析数据源配置的时候对配置信息做了缓存
public class LeeMultiPersistenceConfiguration implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    ......

    private static Map<String, Map<String, Map<String, String>>> persistencePropertiesCache;

    public static Map<String, String> getPersistenceDatasourceProperties(String persistenceName) {
        Map<String, Map<String, String>> persistenceProperties = persistencePropertiesCache.get(persistenceName);
        return persistenceProperties.get(KEY_DATASOURCE);
    }

}

DataSourceBeanPostProcessor中,仅针对类型为HikariDataSourceDruidDataSourceDataSourcebean生效,然后针对这些bean基于Binder完成属性设置。因为在LeeMultiPersistenceConfiguration中加载数据源的配置时已经对数据源配置信息做了缓存,所以现在可以直接通过LeeMultiPersistenceConfiguration拿到某个数据源对应的配置信息。

那么至此,完整的数据源bean就注册好了。

五. MyBatis初始化

同样先回到LeeMultiPersistenceConfiguration实现的registerBeanDefinitions()  方法,如下所示。

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                    BeanDefinitionRegistry registry) {
    // 拿到多数据源配置类MultiPersistenceProperties
    MultiPersistenceProperties multiPersistenceProperties = parseMultiPersistenceProperties();
    List<String> persistenceNames = multiPersistenceProperties.getPersistenceNames();
    for (String persistenceName : persistenceNames) {
        registerDatasource(registry, persistenceName, multiPersistenceProperties.getDataSourceProperties(persistenceName));
        // 注册每个数据源对应的SqlSessionFactory到Spring容器中
        registerSqlSessionFactory(registry, persistenceName, multiPersistenceProperties.getMybatisProperties(persistenceName));
        // 注册每个数据源对应的MapperScannerConfigurer到Spring容器中
        registerMapperScannerConfigurer(registry, persistenceName, multiPersistenceProperties.getMybatisProperties(persistenceName));
    }
}

现在先看一下registerSqlSessionFactory() 方法,如下所示。

private void registerSqlSessionFactory(BeanDefinitionRegistry registry,
                                       String persistenceName,
                                       MybatisExtendProperties mybatisProperties) {
    BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
            .genericBeanDefinition(SqlSessionFactoryBean.class);
    // 为SqlSessionFactory添加数据源
    // 主要就是指定数据源的名字
    beanDefinitionBuilder.addPropertyReference(DATA_SOURCE, persistenceName);
    // 设置SqlSessionFactory的配置文件路径
    beanDefinitionBuilder.addPropertyValue(CONFIG_LOCATION, mybatisProperties.getConfigLocation());
    registry.registerBeanDefinition(BeanNameUtil.getSqlSessionFactoryName(persistenceName),
            beanDefinitionBuilder.getBeanDefinition());
}

上述方法也是通过BeanDefinitionBuilder来完成SqlSessionFactory对应的BeanDefinition的创建,属性设置和注册。有两点需要注意。

  • 实际注册的是SqlSessionFactoryBeanBeanDefinitionSqlSessionFactoryBean提供了更多丰富的配置来完成SqlSessionFactory的创建,例如可以设置引用的数据源名称以及MyBatis的配置文件路径等;
  • 注册的SqlSessionFactory的名字格式是固定的且为dataSourceName + SqlSessionFactory。这样是为了方便MapperScannerConfigurer引用。

现在继续看registerMapperScannerConfigurer() 方法,如下所示。

private void registerMapperScannerConfigurer(BeanDefinitionRegistry registry,
                                             String persistenceName,
                                             MybatisExtendProperties mybatisProperties) {
    BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
            .genericBeanDefinition(MapperScannerConfigurer.class);
    // 设置SqlSessionFactory
    beanDefinitionBuilder.addPropertyValue(SQL_SESSION_FACTORY_BEANNAME, BeanNameUtil.getSqlSessionFactoryName(persistenceName));
    // 设置映射接口的包路径
    beanDefinitionBuilder.addPropertyValue(BASE_PACKAGE, mybatisProperties.getBasePackage());
    registry.registerBeanDefinition(BeanNameUtil.getMapperScannerConfigurerName(persistenceName),
            beanDefinitionBuilder.getBeanDefinition());
}

其实和注册SqlSessionFactory是一样的方式,唯一需要注意的就是在上述方法中为数据源对应的MapperScannerConfigurer设置了SqlSessionFactory以及映射接口的路径。

至此,MyBatis的初始化就做完了,其实就是向Spring容器注册每个数据源对应的SqlSessionFactorybean以及MapperScannerConfigurerbean

六. Springboot数据源原生自动装配抑制

由于我们自己定义了数据源的相关配置格式,那么相应的用户就不需要再去提供类似于spring.datasource这样的配置,所以我们需要抑制Springboot的数据源的原生自动装配的执行,依赖的扩展点是ApplicationContextInitializer

如果熟悉Springboot的自动装配,那么肯定对AutoConfigurationImportSelector不陌生,这个类的getAutoConfigurationEntry() 方法会拿到所有自动装配的配置类的全限定名,如下所示。

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    // 获取@EnableAutoConfiguration注解的元数据属性
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 将需要自动装配的组件的配置类的全限定名获取出来
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    // 去除重复的组件
    configurations = removeDuplicates(configurations);
    // 去除被排除的组件
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    // 去除依赖项不满足的组件
    configurations = getConfigurationClassFilter().filter(configurations);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    // 返回剩余的需要自动装配的组件的配置类的全限定名
    return new AutoConfigurationEntry(configurations, exclusions);
}

其中getExclusions() 方法会拿到需要排除的自动装配组件的全限定名,如下所示。

protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    Set<String> excluded = new LinkedHashSet<>();
    excluded.addAll(asList(attributes, "exclude"));
    excluded.addAll(asList(attributes, "excludeName"));
    excluded.addAll(getExcludeAutoConfigurationsProperty());
    return excluded;
}

protected List<String> getExcludeAutoConfigurationsProperty() {
    Environment environment = getEnvironment();
    if (environment == null) {
        return Collections.emptyList();
    }
    if (environment instanceof ConfigurableEnvironment) {
        Binder binder = Binder.get(environment);
        // PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE为spring.autoconfigure.exclude
        return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class).map(Arrays::asList)
                .orElse(Collections.emptyList());
    }
    String[] excludes = environment.getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);
    return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList();
}

在获取需要排除的自动装配的组件的全限定名时,实际就是去Environment中通过spring.autoconfigure.exclude拿到需要排除的组件的全限定名,那么现在找到切入点了,只要在getExclusions() 方法执行之前向Environment添加spring.autoconfigure.exclude的配置,那么就能够排除指定自动装配类的执行,那么最合适的扩展点其实就是ApplicationContextInitializer,理由如下。

  • ApplicationContextInitializer的加载在初始化SpringApplication时就已经完成;
  • ApplicationContextInitializer的执行是在prepareContext() 即准备容器的时候,这个时候Environment已经加载完毕,并且getExclusions() 方法也还没执行。

所以现在我们在spring.factories文件中加入如下内容。

org.springframework.context.ApplicationContextInitializer=\
  com.lee.multidatasource.initializer.ExcludeInitializer

然后ExcludeInitializer实现如下。

public class ExcludeInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    private static final String EXCLUDE_PROPERTY_SOURCE_NAME = "EXCLUDE_PROPERTY_SOURCE_NAME";

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        ConfigurableEnvironment environment = applicationContext.getEnvironment();

        Properties properties = new Properties();
        properties.setProperty("spring.autoconfigure.exclude",
                "org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration");

        environment.getPropertySources().addLast(new PropertiesPropertySource(
                EXCLUDE_PROPERTY_SOURCE_NAME, properties));
    }

}

至此,就完成了Springboot数据源原生自动装配的抑制。

总结

本文对MyBatis多数据源Starter的实现进行了说明,思维导图如下所示。

以上就是一文搞懂MyBatis多数据源Starter实现的详细内容,更多关于MyBatis多数据源Starte的资料请关注编程网其它相关文章!

--结束END--

本文标题: 一文搞懂MyBatis多数据源Starter实现

本文链接: https://lsjlt.com/news/210169.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 一文搞懂MyBatis多数据源Starter实现
    目录前言正文一. 实现思路二. 自动装配实现三. 配置加载四. 数据源初始化五. MyBatis初始化六. Springboot数据源原生自动装配抑制总结前言 本文将实现一个MyBa...
    99+
    2023-05-16
    MyBatis多数据源Starter MyBatis多数据源 多数据源Starter
  • 一文搞懂C#实现读写文本文件中的数据
    【1】首先我们定义一段假数据,这里以一个string为例字   static void Main(string[] args) { string data = "我的数据要开始...
    99+
    2024-04-02
  • springboot+mybatis实现多数据源
    1. 前言 最近做项目碰到了一个需要连4个不同数据库的需求,其中db1、db2表结构都不相同;另外两个数据库same_db_private、same_db_public表结构完全相同,一个对内一个对外...
    99+
    2023-09-11
    mybatis spring boot java
  • 一文搞懂MySQL元数据锁(MDL)
    目录一、什么是metadata lock二、MDL和行锁有什么区别三、MDL为什么会造成系统崩溃四、MDL的生命周期有多长五、如何快速找到阻塞源头六、本文开始的案例最终如何解决小结某日,路上收到用户咨询,为了清除空间,想...
    99+
    2024-04-02
  • 一文搞懂Redis中String数据类型
    概述: 字符串类型是Redis中最为基础的数据存储类型,它在Redis中是二进制安全的,这便意味着该类型可以接受任何格式的数据,如JPEG图像数据或Json对象描述信息等。在Redi...
    99+
    2024-04-02
  • 一文搞懂Python中Pandas数据合并
    目录1.concat()主要参数示例2.merge()参数示例3.append()参数示例4.join()示例数据合并是数据处理过程中的必经环节,pandas作为数据分析的...
    99+
    2024-04-02
  • 使用dynamic datasource springboot starter实现多数据源及源码分析
    目录简介实操基本使用集成druid连接池service嵌套为什么切换数据源不生效或事务不生效?源码分析整体结构自动配置怎么实现的如何集成众多连接池的DS注解如何被拦截处理的多数据源动...
    99+
    2024-04-02
  • Mybatis操作多数据源的实现
    目录1. 注入多数据源2. 动态数据源(1) 创建并注入动态数据源(2) Mybatis配置类(3) 使用注解简化数据源切换3. 结语现在有一个Mysql数据源和一个Postgres...
    99+
    2023-05-20
    Mybatis操作多数据源 Mybatis 多数据源
  • 一文搞懂JavaScript中bind,apply,call的实现
    目录bind、call和apply的用法bindcall&apply实现bind实现call和apply总结bind、call和apply都是Function原型链上面的方法...
    99+
    2024-04-02
  • 一文搞懂Java中对象池的实现
    目录1. 什么是对象池2. 为什么需要对象池3. 对象池的实现4. 开源的对象池工具5. JedisPool 对象池实现分析6. 对象池总结最近在分析一个应用中的某个接口的耗时情况时...
    99+
    2024-04-02
  • springboot+mybatis实现mysql和oracle多数据源
    1.aop+注解方式 在实际项目中很多时候会涉及到多个数据库的访问,或者数据库读写分离的形式。 下面通过使用 Aspect+注解来实现mysql+oracle的多数据源配置(注意:事务一致性未提供) 首先要去oracle官网下载ojdbc的...
    99+
    2023-09-05
    spring boot mybatis mysql oracle 后端
  • 一文搞懂如何实现Go 超时控制
    为什么需要超时控制? 请求时间过长,用户侧可能已经离开本页面了,服务端还在消耗资源处理,得到的结果没有意义 过长时间的服务端处理会占用过多资源,导致并发能力下降,甚至...
    99+
    2022-06-07
    GO
  • 一文搞懂JavaMD5算法的原理及实现
    目录MD5加密简介MD5加密原理MD5加密常用方法MD5加密简介 哈希算法又称散列算法,是将任何数据转换成固定长度的算法的统称。 从本质上讲,MD5也是一种哈希算法,其输出...
    99+
    2024-04-02
  • Mybatis操作多数据源实现的方法
    今天小编给大家分享的是Mybatis操作多数据源实现的方法,相信很多人都不太了解,为了让大家更加了解,所以给大家总结了以下内容,一起往下看吧。一定会有所收获的哦。现在有一个Mysql数据源和一个Postgresql数据源,使用Mybatis...
    99+
    2023-07-06
  • 一文搞懂JavaScript如何实现图片懒加载
    目录实现思路准备知识data-*getBoundingClientRect()throttlewindow.innerHeight完整代码js部分CSS部分运行结果总结图片懒加载,往...
    99+
    2024-04-02
  • SpringBoot+Mybatis plus实现多数据源整合的实践
    SpringBoot 版本为1.5.10.RELEASE,Mybatis plus 版本为2.1.8。 第一步:填写配置信息: spring: aop: proxy-...
    99+
    2024-04-02
  • springboot中mybatis多数据源动态切换实现
    目录多数据源配置引入 动态数据源路由实现 动态数据源切换使用 案例源码 在开发中,动态数据源配置还是用的比较多的,比如在多数据源使用方面,又或者是在多个DB之间切换方面。这里给出一个...
    99+
    2024-04-02
  • 一文快速搞懂MySQL InnoDB事务ACID实现原理
    【51CTO.com原创稿件】说到数据库事务,想到的就是要么都做修改,要么都不做,或者是 ACID 的概念。其实事务的本质就是锁、并发和重做日志的结合体。 这一篇主要讲一下 InnoDB 中的事务到底是如何...
    99+
    2024-04-02
  • 一文搞懂MySQL XA如何实现分布式事务
    目录前言XA 协议如何通过MySQL XA实现分布式事务前言 MySQL支持单机事务的良好表现毋庸置疑,那么在分布式系统中,涉及多个节点,MySQL又是如何实现分布式事务的呢?比如开...
    99+
    2024-04-02
  • springboot配置多数据源的一款框架(dynamic-datasource-spring-boot-starter)
    目录前言框架简介基本使用 框架说明与 springboot 的整合数据准备引入依赖springboot 配置文件启动类实体类service 层controller 层测试前...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作