头图

在这里插入图片描述

大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈

前言

数据源,实际就是数据库连接池,负责管理数据库连接,在Springboot中,数据源通常以一个bean的形式存在于IOC容器中,也就是我们可以通过依赖注入的方式拿到数据源,然后再从数据源中获取数据库连接。

那么什么是多数据源呢,其实就是IOC容器中有多个数据源的bean,这些数据源可以是不同的数据源类型,也可以连接不同的数据库。

下图是Springboot中业务线程发起数据库请求时的一次示意图。

在这里插入图片描述

本文将对多数据源如何加载,如何结合MyBatis使用进行说明,知识点脑图如下所示。

在这里插入图片描述

本文示例代码如下。

learn-multidatasource
learn-multidatasource-aop

正文

一. 数据源概念

数据源,其实就是数据库连接池,负责数据库连接的全生命周期管理

目前使用较多并且性能较优的有如下几款数据源。

  1. TomcatJdbcTomcatJdbcApache提供的一种数据库连接池解决方案,各方面都还行,各方面也都不突出(均衡大师);
  2. DruidDruid是阿里开源的数据库连接池,是阿里监控系统Dragoon的副产品,提供了强大的可监控性和基于Filter-Chain的可扩展性(BUG 有点多);
  3. HikariCPHikariCP是基于BoneCP进行了大量改进和优化的数据库连接池,是Springboot 2.x版本默认的数据库连接池,也是速度最快的数据库连接池(快男)。

二. Springboot加载数据源原理分析

负责完成数据源加载的类叫做DataSourceAutoConfiguration,由spring-boot-autoconfigure包提供,DataSourceAutoConfiguration的加载是基于Springboot的自动装配机制。

下面先看一下DataSourceAutoConfiguration的部分代码实现。

@AutoConfiguration(before = SqlInitializationAutoConfiguration.class)
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import(DataSourcePoolMetadataProvidersConfiguration.class)
public class DataSourceAutoConfiguration {

    // 省略

    @Configuration(proxyBeanMethods = false)
    @Conditional(PooledDataSourceCondition.class)
    @ConditionalOnMissingBean({DataSource.class, XADataSource.class})
    @Import({DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
            DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
            DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class})
    protected static class PooledDataSourceConfiguration {

    }

    // 省略

}

上述展示出来的代码,做了两件和加载数据源有关的事情。

  1. 将数据源的配置类 DataSourceProperties 注册到了容器中
  2. DataSourceConfiguration 的静态内部类 Hikari 注册到了容器中

先看一下DataSourceProperties的实现,如下所示。

@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {

    private ClassLoader classLoader;

    private boolean generateUniqueName = true;

    private String name;

    private Class<? extends DataSource> type;

    private String driverClassName;

    private String url;

    private String username;

    private String password;
    
    // 省略
    
}

DataSourceProperties中加载了配置在application.yml文件中的spring.datasource.xxx等配置,例如typeurlusernamepassword等都会加载在DataSourceProperties中。

再看一下DataSourceConfiguration的静态内部类Hikari的实现,如下所示。

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HikariDataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource",
        matchIfMissing = true)
static class Hikari {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.hikari")
    HikariDataSource dataSource(DataSourceProperties properties) {
        HikariDataSource dataSource = createDataSource(properties, HikariDataSource.class);
        if (StringUtils.hasText(properties.getName())) {
            dataSource.setPoolName(properties.getName());
        }
        return dataSource;
    }

}

可知Hikari会向容器注册一个HikariCP的数据源HikariDataSource,同时HikariDataSource也是一个配置类,其会加载application.yml文件中的spring.datasource.hikari.xxx等和HikariCP相关的数据源配置,例如max-lifetimekeep-alive-time等都会加载在HikariDataSource中。

并且创建HikariDataSourcecreateDataSource() 方法的第一个参数是容器中的DataSourcePropertiesbean,所以在创建HikariDataSource时,肯定是需要使用到DataSourceProperties里面保存的相关配置的,下面看一下DataSourceConfigurationcreateDataSource() 方法的实现。

protected static <T> T createDataSource(DataSourceProperties properties, Class<? extends DataSource> type) {
    return (T) properties.initializeDataSourceBuilder().type(type).build();
}

DataSourcePropertiesinitializeDataSourceBuilder() 方法会返回一个DataSourceBuilder,具体实现如下。

public DataSourceBuilder<?> initializeDataSourceBuilder() {
    return DataSourceBuilder.create(getClassLoader()).type(getType()).driverClassName(determineDriverClassName())
            .url(determineUrl()).username(determineUsername()).password(determinePassword());
}

说明在创建DataSourceBuilder时,会一并设置typeurlusernamepassword等属性。

知道你不爱看源码,如下是Springboot加载数据源原理小结。

  1. 数据源的 通用配置 会保存在 DataSourceProperties 。例如urlusernamepassword等配置都属于通用配置;
  2. HikariCP 的数据源是 HikariDataSourceHikariCP 相关的配置会保存在 HikariDataSource 。例如max-lifetimekeep-alive-time等都属于HiakriCP相关配置;
  3. 通过 DataSourceProperties 可以创建 DataSourceBuilder
  4. 通过 DataSourceBuilder 可以创建具体的数据源

三. Springboot加载多数据源实现

加载数据源可以分为如下三步。

  1. 读取数据源配置信息
  2. 创建数据源的 bean
  3. 将数据源 bean 注册到 IOC 容器中

因此我们可以自定义一个配置类,在配置类中读取若干个数据源的配置信息,然后基于这些配置信息创建出若干个数据源,最后将这些数据源全部注册到IOC容器中。

首先application.yml文件内容可以配置如下。

lee:
  datasource:
    ds1:
      max-lifetime: 1600000
      keep-alive-time: 90000
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://192.168.101.8:3306/test?characterEncoding=utf-8&serverTimezone=UTC&useSSL=false
      username: root
      password: root
      pool-name: testpool-1
    ds2:
      max-lifetime: 1600000
      keep-alive-time: 90000
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://192.168.101.8:3306/test?characterEncoding=utf-8&serverTimezone=UTC&useSSL=false
      username: root
      password: root
      pool-name: testpool-2

然后自定义的配置类如下所示。

@Configuration
public class MultiDataSourceConfig {

    @Bean(name = "ds1")
    @ConfigurationProperties(prefix = "lee.datasource.ds1")
    public DataSource ds1DataSource() {
        return new HikariDataSource();
    }

    @Bean(name = "ds2")
    @ConfigurationProperties(prefix = "lee.datasource.ds2")
    public DataSource ds2DataSource() {
        return new HikariDataSource();
    }

}

此时就注册了两个HikariDataSource到了IOC容器中。

四. MyBatis整合Springboot原理分析

通常如果要将MyBatis集成到Spring中,需要提供如下的配置类

@Configuration
@ComponentScan(value = "扫描包路径")
public class MybatisConfig {

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory() throws Exception{
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(pooledDataSource());
        sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("Mybatis配置文件名"));
        return sqlSessionFactoryBean;
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("映射接口包路径");
        return msc;
    }

    // 创建一个数据源
    private PooledDataSource pooledDataSource() {
        PooledDataSource dataSource = new PooledDataSource();
        dataSource.setUrl("数据库URL地址");
        dataSource.setUsername("数据库用户名");
        dataSource.setPassword("数据库密码");
        dataSource.setDriver("数据库连接驱动");
        return dataSource;
    }

}

也就是MyBatis如果要集成到Spring中,需要向容器中注册SqlSessionFactorybean,以及MapperScannerConfigurerbean

因此有理由相信MyBatis整合Springbootstartermybatis-spring-boot-starter应该就是在做上述的事情,下面来分析一下mybatis-spring-boot-starter的工作原理。

首先在POM中引入mybatis-spring-boot-starter的依赖,如下所示。

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.3</version>
</dependency>

mybatis-spring-boot-starter会引入mybatis-spring-boot-autoconfigure,看一下mybatis-spring-boot-autoconfigurespring.factories文件,如下所示。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

所以负责自动装配MyBatis的类是MybatisAutoConfiguration,该类的部分代码如下所示。

@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean {

    // 省略

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        // 设置数据源
        factory.setDataSource(dataSource);
        
        // 省略
        
        return factory.getObject();
    }

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        ExecutorType executorType = this.properties.getExecutorType();
        if (executorType != null) {
            return new SqlSessionTemplate(sqlSessionFactory, executorType);
        } else {
            return new SqlSessionTemplate(sqlSessionFactory);
        }
    }

    public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {

        private BeanFactory beanFactory;

        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

            // 省略

            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
            
            // 省略
            
            registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
        }

        @Override
        public void setBeanFactory(BeanFactory beanFactory) {
            this.beanFactory = beanFactory;
        }

    }
    
    // 省略

}

归纳一下MybatisAutoConfiguration做的事情。

  1. MyBatis 相关的配置加载到 MybatisProperties 并注册到容器中。实际就是将application.yml文件中配置的mybatis.xxx相关的配置加载到MybatisProperties中;
  2. 基于 Springboot 加载的数据源创建 SqlSessionFactory 并注册到容器中
  3. 基于 SqlSessionFactory 创建 SqlSessionTemplate 并注册到容器中
  4. 使用 AutoConfiguredMapperScannerRegistrar 向容器注册 MapperScannerConfigurerAutoConfiguredMapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,因此可以向容器注册bean

所以MybatisAutoConfiguration和我们自己将MyBatis集成到Spring做的事情是一样的。

  1. 获取一个数据源并基于这个数据源创建 SqlSessionFactory bean 并注册到容器中;
  2. 创建 MapperScannerConfigurer bean 并注册到容器中

五. MyBatis整合Springboot多数据源实现

mybatis-spring-boot-starter单数据源的实现,本节将对MyBatis整合Springboot的多数据实现进行演示和说明,示例代码learn-multidatasource

首先需要引入相关依赖,POM文件如下所示。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-parent</artifactId>
        <version>2.7.6</version>
    </parent>

    <groupId>com.lee.learn.multidatasource</groupId>
    <artifactId>learn-multidatasource</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>

</project>

然后提供多数据源的配置,application.yml文件如下所示。

lee:
  datasource:
    ds1:
      max-lifetime: 1600000
      keep-alive-time: 90000
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://192.168.101.8:3306/test?characterEncoding=utf-8&serverTimezone=UTC&useSSL=false
      username: root
      password: root
      pool-name: testpool-1
    ds2:
      max-lifetime: 1600000
      keep-alive-time: 90000
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://192.168.101.8:3306/test?characterEncoding=utf-8&serverTimezone=UTC&useSSL=false
      username: root
      password: root
      pool-name: testpool-2

现在先看一下基于数据源ds1MyBatis的配置类,如下所示。

@Configuration
public class MybatisDs1Config {

    @Bean(name = "ds1")
    @ConfigurationProperties(prefix = "lee.datasource.ds1")
    public DataSource ds1DataSource() {
        // 加载lee.datasource.ds1.xxx的配置到HikariDataSource
        // 然后以ds1为名字将HikariDataSource注册到容器中
        return new HikariDataSource();
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory1(@Qualifier("ds1") DataSource dataSource) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        // 设置数据源
        sqlSessionFactoryBean.setDataSource(dataSource);
        // 设置MyBatis的配置文件
        sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
        return sqlSessionFactoryBean;
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer1(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        // 设置使用的SqlSessionFactory的名字
        msc.setSqlSessionFactoryBeanName("sqlSessionFactory1");
        // 设置映射接口的路径
        msc.setBasePackage("com.lee.learn.multidatasource.dao.mapper1");
        return msc;
    }

}

基于数据源ds2MyBatis的配置类,如下所示。

@Configuration
public class MybatisDs2Config {

    @Bean(name = "ds2")
    @ConfigurationProperties(prefix = "lee.datasource.ds2")
    public DataSource ds2DataSource() {
        // 加载lee.datasource.ds2.xxx的配置到HikariDataSource
        // 然后以ds2为名字将HikariDataSource注册到容器中
        return new HikariDataSource();
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory2(@Qualifier("ds2") DataSource dataSource) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        // 设置数据源
        sqlSessionFactoryBean.setDataSource(dataSource);
        // 设置MyBatis的配置文件
        sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
        return sqlSessionFactoryBean;
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer2(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        // 设置使用的SqlSessionFactory的名字
        msc.setSqlSessionFactoryBeanName("sqlSessionFactory2");
        // 设置映射接口的路径
        msc.setBasePackage("com.lee.learn.multidatasource.dao.mapper2");
        return msc;
    }

}

基于上述两个配置类,那么最终com.lee.learn.multidatasource.dao.mapper1路径下的映射接口使用的数据源为ds1com.lee.learn.multidatasource.dao.mapper2路径下的映射接口使用的数据源为ds2

运行示例工程,调用接口/test/ds1,会有如下的打印字样。

testpool-1 - Starting...
testpool-1 - Start completed.

说明查询book表时的连接是从ds1数据源中获取的,同理调用接口/test/ds2,会有如下打印字样。

testpool-2 - Starting...
testpool-2 - Start completed.

说明查询stu表时的连接是从ds2数据源中获取的。

六. MyBatis整合Springboot多数据源切换

第五节中,MyBatis整合Springboot多数据源的实现思路是固定让某些映射接口使用一个数据源另一些映射接口使用另一个数据源

本节将提供另外一种思路,通过AOP的形式来指定要使用的数据源,也就是利用切面来实现多数据源的切换,整体的实现思路如下。

  1. 配置并得到多个数据源
  2. 使用一个路由数据源存放多个数据源
  3. 将路由数据源配置给 MyBatis SqlSessionFactory
  4. 实现切面来拦截对 MyBatis 映射接口的请求
  5. 在切面逻辑中完成数据源切换

那么现在按照上述思路,来具体实现一下(示例工程learn-multidatasource-aop)。

数据源的配置类如下所示。

@Configuration
public class DataSourceConfig {

    @Bean(name = "ds1")
    @ConfigurationProperties(prefix = "lee.datasource.ds1")
    public DataSource ds1DataSource() {
        return new HikariDataSource();
    }

    @Bean(name = "ds2")
    @ConfigurationProperties(prefix = "lee.datasource.ds2")
    public DataSource ds2DataSource() {
        return new HikariDataSource();
    }

    @Bean(name = "mds")
    public DataSource multiDataSource(@Qualifier("ds1") DataSource ds1DataSource,
                                      @Qualifier("ds2") DataSource ds2DataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("ds1", ds1DataSource);
        targetDataSources.put("ds2", ds2DataSource);

        MultiDataSource multiDataSource = new MultiDataSource();
        multiDataSource.setTargetDataSources(targetDataSources);
        multiDataSource.setDefaultTargetDataSource(ds1DataSource);

        return multiDataSource;
    }

}

名字为mds的数据源就是所谓的路由数据源,其实现如下所示。

public class MultiDataSource extends AbstractRoutingDataSource {

    private static final ThreadLocal<String> DATA_SOURCE_NAME = new ThreadLocal<>();

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

    public static void removeDataSourceName() {
        DATA_SOURCE_NAME.remove();
    }

    @Override
    public Object determineCurrentLookupKey() {
        return DATA_SOURCE_NAME.get();
    }

}

我们自定义了一个路由数据源叫做MultiDataSource,其实现了AbstractRoutingDataSource类,而AbstractRoutingDataSource类正是Springboot提供的用于做数据源切换的一个抽象类,其内部有一个Map类型的字段叫做targetDataSources,里面存放的就是需要做切换的数据源,key是数据源的名字,value是数据源。

当要从路由数据源获取Connection时,会调用到AbstractRoutingDataSource提供的getConnection() 方法,其实现如下。

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

protected DataSource determineTargetDataSource() {
    Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
    // 得到实际要使用的数据源的key
    Object lookupKey = determineCurrentLookupKey();
    // 根据key从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;
}

在从路由数据源获取实际使用的数据源时,会经历如下步骤。

  1. 通过 determineCurrentLookupKey() 方法拿 key
  2. 根据 key resolvedDataSources 中拿到实际使用的数据源

现在可知每次从路由数据源获取实际要使用的数据源时,关键就在于如何通过determineCurrentLookupKey() 拿到数据源的key,而determineCurrentLookupKey() 是一个抽象方法,所以在我们自定义的路由数据源中对其进行了重写,也就是从一个ThreadLocal中拿到数据源的key,有拿就有放,那么ThreadLocal是在哪里设置的数据源的key的呢,那当然就是在切面中啦。下面一起看一下。

首先定义一个切面,如下所示。

@Aspect
@Component
public class DeterminDataSourceAspect {

    @Pointcut("@annotation(com.lee.learn.multidatasource.aspect.DeterminDataSource)")
    private void determinDataSourcePointcount() {}

    @Around("determinDataSourcePointcount()")
    public Object determinDataSource(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
        DeterminDataSource determinDataSource = methodSignature.getMethod()
                .getAnnotation(DeterminDataSource.class);
        MultiDataSource.setDataSourceName(determinDataSource.name());

        try {
            return proceedingJoinPoint.proceed();
        } finally {
            MultiDataSource.removeDataSourceName();
        }
    }

}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DeterminDataSource {

    String name() default "ds1";

}

切点是自定义的注解@DeterminDataSource修饰的方法,这个注解可以通过name属性来指定实际要使用的数据源的key,同时定义了一个环绕通知,做的事情就是在目标方法执行前将DeterminDataSource注解指定的key放到MultiDataSourceThreadLocal中,在目标方法执行完毕后,将数据源的keyMultiDataSourceThreadLocal中移除。

现在已经有路由数据源了,最后一件事情就是将路由数据源给到MyBatisSessionFactory,配置类MybatisConfig如下所示。

@Configuration
public class MybatisConfig {

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(@Qualifier("mds") DataSource dataSource) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
        return sqlSessionFactoryBean;
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer1(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setSqlSessionFactoryBeanName("sqlSessionFactory");
        msc.setBasePackage("com.lee.learn.multidatasource.dao");
        return msc;
    }

}

启动示例工程后,如果调用接口/test/ds1,会有如下的打印字样。

testpool-1 - Starting...
testpool-1 - Start completed.

说明查询book表时的连接是从ds1数据源中获取的,同理调用接口/test/ds2,会有如下打印字样。

testpool-2 - Starting...
testpool-2 - Start completed.

总结

首先数据源其实就是数据库连接池,负责连接的全生命周期管理,目前主流的有TomcatJdbcDruidHikariCP

然后Springboot官方的加载数据源实现,实际就是基于自动装配机制,通过DataSourceAutoConfiguration来加载数据源相关的配置并将数据源创建出来再注册到容器中。

所以模仿Springboot官方的加载数据源实现,我们可以自己加载多个数据源的配置,然后创建出不同的数据源的bean,再全部注册到容器中,这样我们就实现了加载多数据源

加载完多数据源后我们可以按照如下步骤来使用。

  1. 通过数据源的的名字也就是 bean 的名字来依赖注入数据源
  2. 将不同的数据源设置给不同的 SqlSessionFactory
  3. 将不同的 SqlSessionFactory 设置给不同的 MapperScannerConfigurer
  4. 最终实现了某一些映射接口使用一个数据源另一些映射接口使用另一个数据源的效果

此外还可以借助AbstractRoutingDataSource来实现数据源的切换,也就是提前将创建好的数据源放入路由数据源中,并且一个数据源对应一个key,然后获取数据源时通过key来获取,key的设置通过一个切面来实现,这样的方式可以在更小的粒度来切换数据源。


大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈

在这里插入图片描述


半夏之沫
68 声望33 粉丝