日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

前言

數(shù)據(jù)源,實(shí)際就是數(shù)據(jù)庫(kù)連接池,負(fù)責(zé)管理數(shù)據(jù)庫(kù)連接,在Springboot中,數(shù)據(jù)源通常以一個(gè)bean的形式存在于IOC容器中,也就是我們可以通過(guò)依賴注入的方式拿到數(shù)據(jù)源,然后再?gòu)臄?shù)據(jù)源中獲取數(shù)據(jù)庫(kù)連接。

那么什么是多數(shù)據(jù)源呢,其實(shí)就是IOC容器中有多個(gè)數(shù)據(jù)源的bean,這些數(shù)據(jù)源可以是不同的數(shù)據(jù)源類型,也可以連接不同的數(shù)據(jù)庫(kù)。

本文將對(duì)多數(shù)據(jù)如何加載,如何結(jié)合MyBatis使用進(jìn)行說(shuō)明,知識(shí)點(diǎn)腦圖如下所示。

 

正文

一. 數(shù)據(jù)源概念和常見(jiàn)數(shù)據(jù)源介紹

數(shù)據(jù)源,其實(shí)就是數(shù)據(jù)庫(kù)連接池,負(fù)責(zé)數(shù)據(jù)庫(kù)連接的管理和借出。目前使用較多也是性能較優(yōu)的有如下幾款數(shù)據(jù)源。

  1. TomcatJdbcTomcatJdbcApache提供的一種數(shù)據(jù)庫(kù)連接池解決方案,各方面都還行,各方面也都不突出;
  2. DruidDruid是阿里開源的數(shù)據(jù)庫(kù)連接池,是阿里監(jiān)控系統(tǒng)Dragoon的副產(chǎn)品,提供了強(qiáng)大的可監(jiān)控性和基于Filter-Chain的可擴(kuò)展性;
  3. HikariCPHikariCP是基于BoneCP進(jìn)行了大量改進(jìn)和優(yōu)化的數(shù)據(jù)庫(kù)連接池,是Springboot 2.x版本默認(rèn)的數(shù)據(jù)庫(kù)連接池,也是速度最快的數(shù)據(jù)庫(kù)連接池。

二. Springboot加載數(shù)據(jù)源原理分析

首先搭建一個(gè)極簡(jiǎn)的示例工程,POM文件引入依賴如下所示。

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

<dependency>
    <groupId>MySQL</groupId>
    <artifactId>mysql-connector-JAVA</artifactId>
</dependency>
復(fù)制代碼

編寫一個(gè)Springboot的啟動(dòng)類,如下所示。

@SpringBootApplication
public class Application {

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

}
復(fù)制代碼

再編寫一個(gè)從數(shù)據(jù)源拿連接的DAO類,如下所示。

@Repository
public class MyDao implements InitializingBean {

    @Autowired
    private DataSource dataSource;

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

}
復(fù)制代碼

application.yml文件中加入數(shù)據(jù)源的參數(shù)配置。

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
復(fù)制代碼

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

整體的工程目錄如下。

 

負(fù)責(zé)完成數(shù)據(jù)源加載的類叫做
DataSourceAutoConfiguration,由spring-boot-autoconfigure包提供,
DataSourceAutoConfiguration的加載是基于Springboot的自動(dòng)裝配機(jī)制,不過(guò)這里說(shuō)明一下,由于本篇文章是基于Springboot2.7.6版本,所以沒(méi)有辦法在spring-boot-autoconfigure包的spring.factories文件中找到
DataSourceAutoConfiguration,在Springboot2.7.x版本中,是通過(guò)加載META-INF/spring/xxx.xxx.xxx.imports文件來(lái)實(shí)現(xiàn)自動(dòng)裝配的,但這不是本文重點(diǎn),故先在這里略做說(shuō)明。

下面先看一下
DataSourceAutoConfiguration的部分代碼實(shí)現(xiàn)。

@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 {

    }

    ......

}
復(fù)制代碼

上述展示出來(lái)的代碼,做了兩件和加載數(shù)據(jù)源有關(guān)的事情。

  1. 將數(shù)據(jù)源的配置類DataSourceProperties注冊(cè)到了容器中;
  2. DataSourceConfiguration的靜態(tài)內(nèi)部類Hikari注冊(cè)到了容器中。

先看一下DataSourceProperties的實(shí)現(xiàn),如下所示。

@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;
	
	......
	
}
復(fù)制代碼

DataSourceProperties中加載了配置在application.yml文件中的spring.datasource.xxx等配置,像我們配置的typedriver-class-nameurlusernamepassword都會(huì)加載在DataSourceProperties中。

再看一下DataSourceConfiguration的靜態(tài)內(nèi)部類Hikari的實(shí)現(xiàn),如下所示。

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

}
復(fù)制代碼

可知Hikari會(huì)向容器注冊(cè)一個(gè)HikariCP的數(shù)據(jù)源HikariDataSource,同時(shí)HikariDataSource也是一個(gè)配置類,其會(huì)加載application.yml文件中的
spring.datasource.hikari.xxx等和HikariCP相關(guān)的數(shù)據(jù)源配置,像我們配置的max-lifetimekeep-alive-time都會(huì)加載在HikariDataSource中。

然后還能發(fā)現(xiàn),創(chuàng)建HikariDataSourcecreateDataSource方法的第一個(gè)參數(shù)是容器中的DataSourcePropertiesbean,所以在創(chuàng)建HikariDataSource時(shí),肯定是需要使用到DataSourceProperties里面保存的相關(guān)配置的,下面看一下DataSourceConfigurationcreateDataSource() 方法的實(shí)現(xiàn)。

protected static <T> T createDataSource(DataSourceProperties properties, Class<? extends DataSource> type) {
    return (T) properties.initializeDataSourceBuilder().type(type).build();
}
復(fù)制代碼

DataSourceProperties
initializeDataSourceBuilder() 方法會(huì)返回一個(gè)DataSourceBuilder,具體實(shí)現(xiàn)如下。

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

也就是在創(chuàng)建DataSourceBuilder時(shí),會(huì)一并設(shè)置typedriverClassNameurlusernamepassword等屬性,其中typedriverClassName不用設(shè)置也沒(méi)關(guān)系,Springboot會(huì)做自動(dòng)判斷,只需要引用了相應(yīng)的依賴即可。

那么至此,Springboot加載數(shù)據(jù)源原理已經(jīng)分析完畢,小結(jié)如下。

  1. 數(shù)據(jù)源的通用配置會(huì)保存在DataSourceProperties中。例如urlusernamepassword等配置都屬于通用配置;
  2. HikariCP的數(shù)據(jù)源是HikariDataSourceHikariCP相關(guān)的配置會(huì)保存在HikariDataSource中。例如max-lifetimekeep-alive-time等都屬于HiakriCP相關(guān)配置;
  3. 通過(guò)DataSourceProperties可以創(chuàng)建DataSourceBuilder
  4. 通過(guò)DataSourceBuilder可以創(chuàng)建具體的數(shù)據(jù)源。

三. Springboot加載多數(shù)據(jù)源實(shí)現(xiàn)

現(xiàn)在已知,加載數(shù)據(jù)源可以分為如下三步。

  1. 讀取數(shù)據(jù)源配置信息;
  2. 創(chuàng)建數(shù)據(jù)源的bean
  3. 將數(shù)據(jù)源bean注冊(cè)到IOC容器中。

因此我們可以自定義一個(gè)配置類,在配置類中讀取若干個(gè)數(shù)據(jù)源的配置信息,然后基于這些配置信息創(chuàng)建出若干個(gè)數(shù)據(jù)源,最后將這些數(shù)據(jù)源全部注冊(cè)到IOC容器中。現(xiàn)在對(duì)加載多數(shù)據(jù)源進(jìn)行演示和說(shuō)明。

首先application.yml文件內(nèi)容如下所示。

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
復(fù)制代碼

自定義的配置類如下所示。

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

}
復(fù)制代碼

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

下面是一個(gè)簡(jiǎn)單的基于JDBC的測(cè)試?yán)印?/p>

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

}
復(fù)制代碼

四. MyBatis整合Springboot原理分析

在分析如何將多數(shù)據(jù)源應(yīng)用于MyBatis前,需要了解一下MyBatis是如何整合到Springboot中的。在超詳細(xì)解釋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;
    }

    // 創(chuàng)建一個(gè)數(shù)據(jù)源
    private PooledDataSource pooledDataSource() {
        PooledDataSource dataSource = new PooledDataSource();
        dataSource.setUrl("數(shù)據(jù)庫(kù)URL地址");
        dataSource.setUsername("數(shù)據(jù)庫(kù)用戶名");
        dataSource.setPassword("數(shù)據(jù)庫(kù)密碼");
        dataSource.setDriver("數(shù)據(jù)庫(kù)連接驅(qū)動(dòng)");
        return dataSource;
    }

}
復(fù)制代碼

也就是MyBatis集成到Spring,需要向容器中注冊(cè)SqlSessionFactorybean,以及MapperScannerConfigurerbean。那么有理由相信,MyBatis整合Springbootstarter
mybatis-spring-boot-starter應(yīng)該也是在做這個(gè)事情,下面來(lái)分析一下
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>
復(fù)制代碼


mybatis-spring-boot-starter會(huì)引入
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
復(fù)制代碼

所以負(fù)責(zé)自動(dòng)裝配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();
        // 設(shè)置數(shù)據(jù)源
        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;
        }

    }
    
    ......

}
復(fù)制代碼

歸納一下MybatisAutoConfiguration做的事情如下所示。

  1. MyBatis相關(guān)的配置加載到MybatisProperties并注冊(cè)到容器中。實(shí)際就是將application.yml文件中配置的mybatis.xxx相關(guān)的配置加載到MybatisProperties中;
  2. 基于Springboot加載的數(shù)據(jù)源創(chuàng)建SqlSessionFactory并注冊(cè)到容器中。MybatisAutoConfiguration使用了@AutoConfigureAfter注解來(lái)指定MybatisAutoConfiguration要在DataSourceAutoConfiguration執(zhí)行完畢之后再執(zhí)行,所以此時(shí)容器中已經(jīng)有了Springboot加載的數(shù)據(jù)源;
  3. 基于SqlSessionFactory創(chuàng)建SqlSessionTemplate并注冊(cè)到容器中;
  4. 使用AutoConfiguredMapperScannerRegistrar向容器注冊(cè)MapperScannerConfigurerAutoConfiguredMapperScannerRegistrar實(shí)現(xiàn)了ImportBeanDefinitionRegistrar接口,因此可以向容器注冊(cè)bean

那么可以發(fā)現(xiàn),其實(shí)MybatisAutoConfiguration干的事情和我們自己將MyBatis集成到Spring干的事情是一樣的:1. 獲取一個(gè)數(shù)據(jù)源并基于這個(gè)數(shù)據(jù)源創(chuàng)建SqlSessionFactorybean并注冊(cè)到容器中;2. 創(chuàng)建MapperScannerConfigurerbean并注冊(cè)到容器中。

五. MyBatis整合Springboot多數(shù)據(jù)源實(shí)現(xiàn)


mybatis-spring-boot-starter是單數(shù)據(jù)源的實(shí)現(xiàn),本節(jié)將對(duì)MyBatis整合Springboot的多數(shù)據(jù)實(shí)現(xiàn)進(jìn)行演示和說(shuō)明。

首先需要引入相關(guān)依賴,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>
復(fù)制代碼

然后提供多數(shù)據(jù)源的配置,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
復(fù)制代碼

現(xiàn)在先看一下基于數(shù)據(jù)源ds1MyBatis的配置類,如下所示。

@Configuration
public class MybatisDs1Config {

    @Bean(name = "ds1")
    @ConfigurationProperties(prefix = "lee.datasource.ds1")
    public DataSource ds1DataSource() {
        // 加載lee.datasource.ds1.xxx的配置到HikariDataSource
        // 然后以ds1為名字將HikariDataSource注冊(cè)到容器中
        return new HikariDataSource();
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory1(@Qualifier("ds1") DataSource dataSource) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        // 設(shè)置數(shù)據(jù)源
        sqlSessionFactoryBean.setDataSource(dataSource);
        // 設(shè)置MyBatis的配置文件
        sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
        return sqlSessionFactoryBean;
    }

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

}
復(fù)制代碼

同理,基于數(shù)據(jù)源ds2MyBatis的配置類,如下所示。

@Configuration
public class MybatisDs2Config {

    @Bean(name = "ds2")
    @ConfigurationProperties(prefix = "lee.datasource.ds2")
    public DataSource ds2DataSource() {
        // 加載lee.datasource.ds2.xxx的配置到HikariDataSource
        // 然后以ds2為名字將HikariDataSource注冊(cè)到容器中
        return new HikariDataSource();
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory2(@Qualifier("ds2") DataSource dataSource) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        // 設(shè)置數(shù)據(jù)源
        sqlSessionFactoryBean.setDataSource(dataSource);
        // 設(shè)置MyBatis的配置文件
        sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
        return sqlSessionFactoryBean;
    }

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

}
復(fù)制代碼

基于上述兩個(gè)配置類,那么最終
com.lee.learn.multidatasource.dao.mapper1路徑下的映射接口使用的數(shù)據(jù)源為ds1
com.lee.learn.multidatasource.dao.mapper2路徑下的映射接口使用的數(shù)據(jù)源為ds2

完整的示例工程目錄結(jié)構(gòu)如下所示。

 

BookMapperBookMapper.xml如下所示。

public interface BookMapper {

    List<Book> queryAllBooks();

}
復(fù)制代碼
<?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>
復(fù)制代碼

StudentMapperStudentMapper.xml如下所示。

public interface StudentMapper {

    List<Student> queryAllStudents();

}
復(fù)制代碼
<?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>
復(fù)制代碼

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

}
復(fù)制代碼

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

}
復(fù)制代碼

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

}
復(fù)制代碼

那么測(cè)試時(shí),啟動(dòng)Springboot應(yīng)用后,如果調(diào)用接口/test/ds1,會(huì)有如下的打印字樣。

testpool-1 - Starting...
testpool-1 - Start completed.
復(fù)制代碼

說(shuō)明查詢book表時(shí)的連接是從ds1數(shù)據(jù)源中獲取的,同理調(diào)用接口/test/ds2,會(huì)有如下打印字樣。

testpool-2 - Starting...
testpool-2 - Start completed.
復(fù)制代碼

說(shuō)明查詢stu表時(shí)的連接是從ds2數(shù)據(jù)源中獲取的。

至此,MyBatis完成了整合Springboot的多數(shù)據(jù)源實(shí)現(xiàn)。

六. MyBatis整合Springboot多數(shù)據(jù)源切換

在第五節(jié)中,MyBatis整合Springboot多數(shù)據(jù)源的實(shí)現(xiàn)思路是固定讓某些映射接口使用一個(gè)數(shù)據(jù)源,另一些映射接口使用另一個(gè)數(shù)據(jù)源。本節(jié)將提供另外一種思路,通過(guò)AOP的形式來(lái)指定要使用的數(shù)據(jù)源,也就是利用切面來(lái)實(shí)現(xiàn)多數(shù)據(jù)源的切換。

整體的實(shí)現(xiàn)思路如下。

  1. 配置并得到多個(gè)數(shù)據(jù)源;
  2. 使用一個(gè)路由數(shù)據(jù)源存放多個(gè)數(shù)據(jù)源;
  3. 將路由數(shù)據(jù)源配置給MyBatisSqlSessionFactory
  4. 實(shí)現(xiàn)切面來(lái)攔截對(duì)MyBatis映射接口的請(qǐng)求;
  5. 在切面邏輯中完成數(shù)據(jù)源切換。

那么現(xiàn)在按照上述思路,來(lái)具體實(shí)現(xiàn)一下。

數(shù)據(jù)源的配置類如下所示。

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

}
復(fù)制代碼

名字為ds1ds2的數(shù)據(jù)源沒(méi)什么好說(shuō)的,具體關(guān)注一下名字為mds的數(shù)據(jù)源,也就是所謂的路由數(shù)據(jù)源,其實(shí)現(xiàn)如下所示。

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

}
復(fù)制代碼

我們自定義了一個(gè)路由數(shù)據(jù)源叫做MultiDataSource,其實(shí)現(xiàn)了AbstractRoutingDataSource類,而AbstractRoutingDataSource類正是Springboot提供的用于做數(shù)據(jù)源切換的一個(gè)抽象類,其內(nèi)部有一個(gè)Map類型的字段叫做targetDataSources,里面存放的就是需要做切換的數(shù)據(jù)源,key是數(shù)據(jù)源的名字,value是數(shù)據(jù)源。當(dāng)要從路由數(shù)據(jù)源獲取Connection時(shí),會(huì)調(diào)用到AbstractRoutingDataSource提供的getConnection() 方法,看一下其實(shí)現(xiàn)。

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

protected DataSource determineTargetDataSource() {
   Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
   // 得到實(shí)際要使用的數(shù)據(jù)源的key
   Object lookupKey = determineCurrentLookupKey();
   // 根據(jù)key從resolvedDataSources中拿到實(shí)際要使用的數(shù)據(jù)源
   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;
}
復(fù)制代碼

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

那么現(xiàn)在可以知道,每次從路由數(shù)據(jù)源獲取實(shí)際要使用的數(shù)據(jù)源時(shí),關(guān)鍵的就在于如何通過(guò)determineCurrentLookupKey() 拿到數(shù)據(jù)源的key,而determineCurrentLookupKey() 是一個(gè)抽象方法,所以在我們自定義的路由數(shù)據(jù)源中對(duì)其進(jìn)行了重寫,也就是從一個(gè)ThreadLocal中拿到數(shù)據(jù)源的key,有拿就有放,那么ThreadLocal是在哪里設(shè)置的數(shù)據(jù)源的key的呢,那當(dāng)然就是在切面中啦。下面一起看一下。

首先定義一個(gè)切面,如下所示。

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

}
復(fù)制代碼

切點(diǎn)是自定義的注解@DeterminDataSource修飾的方法,這個(gè)注解可以通過(guò)name屬性來(lái)指定實(shí)際要使用的數(shù)據(jù)源的key,然后定義了一個(gè)環(huán)繞通知,做的事情就是在目標(biāo)方法執(zhí)行前將DeterminDataSource注解指定的key放到MultiDataSourceThreadLocal中,然后執(zhí)行目標(biāo)方法,最后在目標(biāo)方法執(zhí)行完畢后,將數(shù)據(jù)源的keyMultiDataSourceThreadLocal中再移除。

現(xiàn)在已經(jīng)有路由數(shù)據(jù)源了,也有為路由數(shù)據(jù)源設(shè)置實(shí)際使用數(shù)據(jù)源key的切面了,最后一件事情就是將路由數(shù)據(jù)源給到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;
    }

}
復(fù)制代碼

完整的示例工程目錄結(jié)構(gòu)如下。

 

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

最后在BookServiceStudentService的方法中添加上@DeterminDataSource注解,來(lái)實(shí)現(xiàn)數(shù)據(jù)源切換的演示。

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

}
復(fù)制代碼

同樣,啟動(dòng)Springboot應(yīng)用后,如果調(diào)用接口/test/ds1,會(huì)有如下的打印字樣。

testpool-1 - Starting...
testpool-1 - Start completed.
復(fù)制代碼

說(shuō)明查詢book表時(shí)的連接是從ds1數(shù)據(jù)源中獲取的,同理調(diào)用接口/test/ds2,會(huì)有如下打印字樣。

testpool-2 - Starting...
testpool-2 - Start completed.
復(fù)制代碼

至此,MyBatis完成了整合Springboot的多數(shù)據(jù)源切換。

總結(jié)

本文的整體知識(shí)點(diǎn)如下所示。

 

首先數(shù)據(jù)源其實(shí)就是數(shù)據(jù)庫(kù)連接池,負(fù)責(zé)連接的管理和借出,目前主流的有TomcatJdbcDruidHikariCP

然后Springboot官方的加載數(shù)據(jù)源實(shí)現(xiàn),實(shí)際就是基于自動(dòng)裝配機(jī)制,通過(guò)
DataSourceAutoConfiguration來(lái)加載數(shù)據(jù)源相關(guān)的配置并將數(shù)據(jù)源創(chuàng)建出來(lái)再注冊(cè)到容器中。

所以模仿Springboot官方的加載數(shù)據(jù)源實(shí)現(xiàn),我們可以自己加載多個(gè)數(shù)據(jù)源的配置,然后創(chuàng)建出不同的數(shù)據(jù)源的bean,再全部注冊(cè)到容器中,這樣我們就實(shí)現(xiàn)了加載多數(shù)據(jù)源。

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

最后,還可以借助AbstractRoutingDataSource來(lái)實(shí)現(xiàn)數(shù)據(jù)源的切換,也就是提前將創(chuàng)建好的數(shù)據(jù)源放入路由數(shù)據(jù)源中,并且一個(gè)數(shù)據(jù)源對(duì)應(yīng)一個(gè)key,然后獲取數(shù)據(jù)源時(shí)通過(guò)key來(lái)獲取,key的設(shè)置通過(guò)一個(gè)切面來(lái)實(shí)現(xiàn),這樣的方式可以在更小的粒度來(lái)切換數(shù)據(jù)源。

現(xiàn)在最后思考一下,本文的多數(shù)據(jù)源的相關(guān)實(shí)現(xiàn),最大的問(wèn)題是什么。

我認(rèn)為有兩點(diǎn)。

  1. 本文的多數(shù)據(jù)源的實(shí)現(xiàn),都是我們自己提供了配置類來(lái)做整合,如果新起一個(gè)項(xiàng)目,又要重新提供一套配置類;
  2. 數(shù)據(jù)源的個(gè)數(shù),名字都是在整合的時(shí)候確定好了,如果加數(shù)據(jù)源,或者改名字,就得改代碼,改配置類。

所以本文的數(shù)據(jù)源的實(shí)現(xiàn)方式不夠優(yōu)雅,最好是能夠有一個(gè)starter包來(lái)完成多數(shù)據(jù)源加載這個(gè)事情,讓我們僅通過(guò)少量配置就能實(shí)現(xiàn)多數(shù)據(jù)源的動(dòng)態(tài)加載和使用。


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

分享到:
標(biāo)簽:MyBatis
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定