动态

详情 返回 返回

✨MyBatis✨整合Springboot多數據源實現 - 动态 详情

在這裏插入圖片描述

大家好,我是半夏之沫 😁😁 一名金融科技領域的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系統研發😊😊
我希望將自己工作和學習中的經驗以最樸實最嚴謹的方式分享給大家,共同進步👉💓👈
👉👉👉👉👉👉👉👉💓寫作不易,期待大家的關注和點贊💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓關注微信公眾號【技術探界】 💓👈👈👈👈👈👈👈👈

在這裏插入圖片描述

user avatar gkymfrg1 头像 cloud11y 头像 bizseerbishikeji 头像
点赞 3 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.