亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.430618.com 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

前言

數據源,實際就是數據庫連接池,負責管理數據庫連接,在Springboot中,數據源通常以一個bean的形式存在于IOC容器中,也就是我們可以通過依賴注入的方式拿到數據源,然后再從數據源中獲取數據庫連接。

那么什么是多數據源呢,其實就是IOC容器中有多個數據源的bean,這些數據源可以是不同的數據源類型,也可以連接不同的數據庫。

本文將對多數據如何加載,如何結合MyBatis使用進行說明,知識點腦圖如下所示。

 

正文

一. 數據源概念和常見數據源介紹

數據源,其實就是數據庫連接池,負責數據庫連接的管理和借出。目前使用較多也是性能較優的有如下幾款數據源。

  1. TomcatJdbcTomcatJdbcApache提供的一種數據庫連接池解決方案,各方面都還行,各方面也都不突出;
  2. DruidDruid是阿里開源的數據庫連接池,是阿里監控系統Dragoon的副產品,提供了強大的可監控性和基于Filter-Chain的可擴展性;
  3. HikariCPHikariCP是基于BoneCP進行了大量改進和優化的數據庫連接池,是Springboot 2.x版本默認的數據庫連接池,也是速度最快的數據庫連接池。

二. Springboot加載數據源原理分析

首先搭建一個極簡的示例工程,POM文件引入依賴如下所示。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<dependency>
    <groupId>MySQL</groupId>
    <artifactId>mysql-connector-JAVA</artifactId>
</dependency>
復制代碼

編寫一個Springboot的啟動類,如下所示。

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}
復制代碼

再編寫一個從數據源拿連接的DAO類,如下所示。

@Repository
public class MyDao implements InitializingBean {

    @Autowired
    private DataSource dataSource;

    @Override
    public void afterPropertiesSet() throws Exception {
        Connection connection = dataSource.getConnection();
        System.out.println("獲取到數據庫連接:" + connection);
    }

}
復制代碼

application.yml文件中加入數據源的參數配置。

spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      max-lifetime: 1600000
      keep-alive-time: 90000
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.101.8:3306/test?characterEncoding=utf-8&serverTimezone=UTC&useSSL=false
    username: root
    password: root
復制代碼

其中urlusernamepassword是必須配置的,其它的僅僅是為了演示。

整體的工程目錄如下。

 

負責完成數據源加載的類叫做
DataSourceAutoConfiguration,由spring-boot-autoconfigure包提供,
DataSourceAutoConfiguration的加載是基于Springboot的自動裝配機制,不過這里說明一下,由于本篇文章是基于Springboot2.7.6版本,所以沒有辦法在spring-boot-autoconfigure包的spring.factories文件中找到
DataSourceAutoConfiguration,在Springboot2.7.x版本中,是通過加載META-INF/spring/xxx.xxx.xxx.imports文件來實現自動裝配的,但這不是本文重點,故先在這里略做說明。

下面先看一下
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等配置,像我們配置的typedriver-class-nameurlusernamepassword都會加載在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();
}
復制代碼

DataSourceProperties
initializeDataSourceBuilder() 方法會返回一個DataSourceBuilder,具體實現如下。

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

也就是在創建DataSourceBuilder時,會一并設置typedriverClassNameurlusernamepassword等屬性,其中typedriverClassName不用設置也沒關系,Springboot會做自動判斷,只需要引用了相應的依賴即可。

那么至此,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();
    }

}
復制代碼

首先在配置類的ds1DataSource() 和ds2DataSource() 方法中創建出HikariDataSource,然后由于使用了@ConfigurationProperties注解,因此lee.datasource.ds1.xxx的配置內容會加載到nameds1HikariDataSource中,lee.datasource.ds2.xxx的配置內容會加載到nameds2HikariDataSource中,最后nameds1HikariDataSourcenameds2HikariDataSource都會作為bean注冊到容器中。

下面是一個簡單的基于JDBC的測試例子。

@Repository
public class MyDao implements InitializingBean {

    @Autowired
    @Qualifier("ds2")
    private DataSource dataSource;

    @Override
    public void afterPropertiesSet() throws Exception {
        Connection connection = dataSource.getConnection();
        Statement statement = connection.createStatement();
        statement.executeQuery("SELECT * FROM book");
        ResultSet resultSet = statement.getResultSet();
        while (resultSet.next()) {
            System.out.println(resultSet.getString("b_name"));
        }
        resultSet.close();
        statement.close();
        connection.close();
    }

}
復制代碼

四. MyBatis整合Springboot原理分析

在分析如何將多數據源應用于MyBatis前,需要了解一下MyBatis是如何整合到Springboot中的。在超詳細解釋MyBatis與Spring的集成原理一文中,有提到將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整合Springbootstarter
mybatis-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并注冊到容器中。MybatisAutoConfiguration使用了@AutoConfigureAfter注解來指定MybatisAutoConfiguration要在DataSourceAutoConfiguration執行完畢之后再執行,所以此時容器中已經有了Springboot加載的數據源;
  3. 基于SqlSessionFactory創建SqlSessionTemplate并注冊到容器中;
  4. 使用AutoConfiguredMapperScannerRegistrar向容器注冊MapperScannerConfigurerAutoConfiguredMapperScannerRegistrar實現了ImportBeanDefinitionRegistrar接口,因此可以向容器注冊bean

那么可以發現,其實MybatisAutoConfiguration干的事情和我們自己將MyBatis集成到Spring干的事情是一樣的:1. 獲取一個數據源并基于這個數據源創建SqlSessionFactorybean并注冊到容器中;2. 創建MapperScannerConfigurerbean并注冊到容器中。

五. MyBatis整合Springboot多數據源實現


mybatis-spring-boot-starter是單數據源的實現,本節將對MyBatis整合Springboot的多數據實現進行演示和說明。

首先需要引入相關依賴,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路徑下的映射接口使用的數據源為ds1
com.lee.learn.multidatasource.dao.mapper2路徑下的映射接口使用的數據源為ds2

完整的示例工程目錄結構如下所示。

 

BookMapperBookMapper.xml如下所示。

public interface BookMapper {

    List<Book> queryAllBooks();

}
復制代碼
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lee.learn.multidatasource.dao.mapper1.BookMapper">
    <resultMap id="bookResultMap" type="com.lee.learn.multidatasource.entity.Book">
        <id column="id" property="id"/>
        <result column="b_name" property="bookName"/>
        <result column="b_price" property="bookPrice"/>
        <result column="bs_id" property="bsId"/>
    </resultMap>

    <select id="queryAllBooks" resultMap="bookResultMap">
        SELECT * FROM book;
    </select>

</mapper>
復制代碼

StudentMapperStudentMapper.xml如下所示。

public interface StudentMapper {

    List<Student> queryAllStudents();

}
復制代碼
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lee.learn.multidatasource.dao.mapper2.StudentMapper">
    <resultMap id="studentResultMap" type="com.lee.learn.multidatasource.entity.Student">
        <id column="id" property="id"/>
        <result column="name" property="studentName"/>
        <result column="level" property="studentLevel"/>
        <result column="grades" property="studentGrades"/>
    </resultMap>

    <select id="queryAllStudents" resultMap="studentResultMap">
        SELECT * FROM stu;
    </select>

</mapper>
復制代碼

BookStudent如下所示。

public class Book {

    private int id;
    private String bookName;
    private float bookPrice;
    private int bsId;

    // 省略getter和setter

}

public class Student {

    private int id;
    private String studentName;
    private String studentLevel;
    private int studentGrades;

    // 省略getter和setter

}
復制代碼

BookServiceStudentService如下所示。

@Service
public class BookService {

    @Autowired
    private BookMapper bookMapper;

    public List<Book> queryAllBooks() {
        return bookMapper.queryAllBooks();
    }

}

@Service
public class StudentService {

    @Autowired
    private StudentMapper studentMapper;

    public List<Student> queryAllStudents() {
        return studentMapper.queryAllStudents();
    }

}
復制代碼

BookControllerStudentsController如下所示。

@RestController
public class BookController {

    @Autowired
    private BookService bookService;

    @GetMapping("/test/ds1")
    public List<Book> queryAllBooks() {
        return bookService.queryAllBooks();
    }

}

@RestController
public class StudentsController {

    @Autowired
    private StudentService studentService;

    @GetMapping("/test/ds2")
    public List<Student> queryAllStudents() {
        return studentService.queryAllStudents();
    }

}
復制代碼

那么測試時,啟動Springboot應用后,如果調用接口/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多數據源切換

在第五節中,MyBatis整合Springboot多數據源的實現思路是固定讓某些映射接口使用一個數據源,另一些映射接口使用另一個數據源。本節將提供另外一種思路,通過AOP的形式來指定要使用的數據源,也就是利用切面來實現多數據源的切換。

整體的實現思路如下。

  1. 配置并得到多個數據源;
  2. 使用一個路由數據源存放多個數據源;
  3. 將路由數據源配置給MyBatisSqlSessionFactory
  4. 實現切面來攔截對MyBatis映射接口的請求;
  5. 在切面邏輯中完成數據源切換。

那么現在按照上述思路,來具體實現一下。

數據源的配置類如下所示。

@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;
    }

}
復制代碼

名字為ds1ds2的數據源沒什么好說的,具體關注一下名字為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 determ.NETargetDataSource().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;
}
復制代碼

其實呢從路由數據源拿到實際使用的數據源時,就是首先通過determineCurrentLookupKey() 方法拿key,然后再根據keyresolvedDataSources這個Map中拿到實際使用的數據源。看到這里可能又有疑問了,在DataSourceConfig中創建路由數據源的bean時,明明只設置了AbstractRoutingDataSource#targetDataSources的值,并沒有設置AbstractRoutingDataSource#resolvedDataSources,那為什么resolvedDataSources中會有實際要使用的數據源呢,關于這個問題,可以看一下AbstractRoutingDataSourceafterPropertiesSet() 方法,這里不再贅述。

那么現在可以知道,每次從路由數據源獲取實際要使用的數據源時,關鍵的就在于如何通過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中再移除。

現在已經有路由數據源了,也有為路由數據源設置實際使用數據源key的切面了,最后一件事情就是將路由數據源給到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;
    }

}
復制代碼

完整的示例工程目錄結構如下。

 

除了上面的代碼以外,其余代碼和第五節中一樣,這里不再重復給出。

最后在BookServiceStudentService的方法中添加上@DeterminDataSource注解,來實現數據源切換的演示。

@Service
public class BookService {

    @Autowired
    private BookMapper bookMapper;

    @DeterminDataSource(name = "ds1")
    public List<Book> queryAllBooks() {
        return bookMapper.queryAllBooks();
    }

}

@Service
public class StudentService {

    @Autowired
    private StudentMapper studentMapper;

    @DeterminDataSource(name = "ds2")
    public List<Student> queryAllStudents() {
        return studentMapper.queryAllStudents();
    }

}
復制代碼

同樣,啟動Springboot應用后,如果調用接口/test/ds1,會有如下的打印字樣。

testpool-1 - Starting...
testpool-1 - Start completed.
復制代碼

說明查詢book表時的連接是從ds1數據源中獲取的,同理調用接口/test/ds2,會有如下打印字樣。

testpool-2 - Starting...
testpool-2 - Start completed.
復制代碼

至此,MyBatis完成了整合Springboot的多數據源切換。

總結

本文的整體知識點如下所示。

 

首先數據源其實就是數據庫連接池,負責連接的管理和借出,目前主流的有TomcatJdbcDruidHikariCP

然后Springboot官方的加載數據源實現,實際就是基于自動裝配機制,通過
DataSourceAutoConfiguration來加載數據源相關的配置并將數據源創建出來再注冊到容器中。

所以模仿Springboot官方的加載數據源實現,我們可以自己加載多個數據源的配置,然后創建出不同的數據源的bean,再全部注冊到容器中,這樣我們就實現了加載多數據源。

加載完多數據源后該怎么使用呢。首先可以通過數據源的的名字,也就是bean的名字來依賴注入數據源,然后直接從數據源拿到Connection,這樣的方式能用,但是肯定沒人會這樣用。所以結合之前MyBatis整合Spring的知識,我們可以將不同的數據源設置給不同的SqlSessionFactory,然后再將不同的SqlSessionFactory設置給不同的MapperScannerConfigurer,這樣就實現了某一些映射接口使用一個數據源,另一些映射接口使用另一個數據源的效果。

最后,還可以借助AbstractRoutingDataSource來實現數據源的切換,也就是提前將創建好的數據源放入路由數據源中,并且一個數據源對應一個key,然后獲取數據源時通過key來獲取,key的設置通過一個切面來實現,這樣的方式可以在更小的粒度來切換數據源。

現在最后思考一下,本文的多數據源的相關實現,最大的問題是什么。

我認為有兩點。

  1. 本文的多數據源的實現,都是我們自己提供了配置類來做整合,如果新起一個項目,又要重新提供一套配置類;
  2. 數據源的個數,名字都是在整合的時候確定好了,如果加數據源,或者改名字,就得改代碼,改配置類。

所以本文的數據源的實現方式不夠優雅,最好是能夠有一個starter包來完成多數據源加載這個事情,讓我們僅通過少量配置就能實現多數據源的動態加載和使用。


作者:半夏之沫
鏈接:
https://juejin.cn/post/7220797267715522615

分享到:
標簽:MyBatis
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定