前语

数据源,实践便是数据库衔接池,担任办理数据库衔接,在Springboot中,数据源通常以一个bean的形式存在于IOC容器中,也便是咱们能够经过依靠注入的方法拿到数据源,然后再从数据源中获取数据库衔接。

那么什么是大都据源呢,其实便是IOC容器中有多个数据源的bean,这些数据源能够是不同的数据源类型,也能够衔接不同的数据库。

本文将对大都据怎样加载,怎样结合MyBatis运用进行阐明,知识点脑图如下所示。

MyBatis整合Springboot多数据源实现

正文

一. 数据源概念和常见数据源介绍

数据源,其实便是数据库衔接池,担任数据库衔接的办理和借出。现在运用较多也是性能较优的有如下几款数据源。

  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是必须装备的,其它的只是是为了演示。

全体的工程目录如下。

MyBatis整合Springboot多数据源实现

担任完结数据源加载的类叫做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();
}

DataSourcePropertiesinitializeDataSourceBuilder() 办法会返回一个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整合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并注册到容器中。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途径下的映射接口运用的数据源为ds1com.lee.learn.multidatasource.dao.mapper2途径下的映射接口运用的数据源为ds2

完整的示例工程目录结构如下所示。

MyBatis整合Springboot多数据源实现

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

其实呢从路由数据源拿到实践运用的数据源时,便是首要经过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;
    }
}

完整的示例工程目录结构如下。

MyBatis整合Springboot多数据源实现

除了上面的代码以外,其他代码和第五节中一样,这儿不再重复给出。

终究在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的大都据源切换。

总结

本文的全体知识点如下所示。

MyBatis整合Springboot多数据源实现

首要数据源其实便是数据库衔接池,担任衔接的办理和借出,现在干流的有TomcatJdbcDruidHikariCP

然后Springboot官方的加载数据源完结,实践便是依据主动装配机制,经过DataSourceAutoConfiguration来加载数据源相关的装备并将数据源创立出来再注册到容器中。

所以仿照Springboot官方的加载数据源完结,咱们能够自己加载多个数据源的装备,然后创立出不同的数据源的bean,再全部注册到容器中,这样咱们就完结了加载大都据源。

加载完大都据源后该怎样运用呢。首要能够经过数据源的的姓名,也便是bean的姓名来依靠注入数据源,然后直接从数据源拿到Connection,这样的方法能用,可是必定没人会这样用。所以结合之前MyBatis整合Spring的知识,咱们能够将不同的数据源设置给不同的SqlSessionFactory,然后再将不同的SqlSessionFactory设置给不同的MapperScannerConfigurer,这样就完结了某一些映射接口运用一个数据源,另一些映射接口运用另一个数据源的效果。

终究,还能够凭借AbstractRoutingDataSource来完结数据源的切换,也便是提早将创立好的数据源放入路由数据源中,并且一个数据源对应一个key,然后获取数据源时经过key来获取,key的设置经过一个切面来完结,这样的方法能够在更小的粒度来切换数据源。

现在终究思考一下,本文的大都据源的相关完结,最大的问题是什么。

我认为有两点。

  1. 本文的大都据源的完结,都是咱们自己供给了装备类来做整合,假如新起一个项目,又要重新供给一套装备类;
  2. 数据源的个数,姓名都是在整合的时分确认好了,假如加数据源,或许改姓名,就得改代码,改装备类。

所以本文的数据源的完结方法不够高雅,最好是能够有一个starter包来完结大都据源加载这个工作,让咱们仅经过少量装备就能完结大都据源的动态加载和运用。

那么鄙人一篇文章中,将对Springboot加载大都据源的starter包的完结进行详细剖析和阐明。

假如觉得本篇文章对你有协助,求求你点个赞,加个保藏终究再点个重视吧。创作不易,感谢支撑!


本文正在参与「金石方案」