Spring配置多數據源

環境背景

這裏以配置兩個mysql數據庫爲展現用例。持久層使用mybatis實現。兩個鏈接分別使用不一樣的鏈接池 druid 和 hikarijava

相關知識

這裏介紹了一些相關的知識點,清楚後能夠跳過mysql

mybatis和mybatis-spring-boot-starter的關係

在pom依賴上它們是兩個不一樣的依賴文件。web

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

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>x.x.x</version>
</dependency>

mybatis-spring-boot-starter相似一箇中間件,連接Springboot和mybatis,構建基於Springboot的mybatis應用程序。同時mybatis-spring-boot-starter做爲一個集成包是包含mybatis的。spring

mybatis-spring-boot-starter和spring-boot-starter-jdbc

mybatis-spring-boot-starter做爲一個集成包,若是在項目裏引入了該依賴那就不須要在顯示的依賴spring-boot-starter-jdbc,由於mybatis-spring-boot-starter是包含spring-boot-starter-jdbc的。sql

mybatis-spring-boot-starter的集成

<?xml version="1.0" encoding="UTF-8"?>
<!--

       Copyright 2015-2018 the original author or authors.

       Licensed under the Apache License, Version 2.0 (the "License");
       you may not use this file except in compliance with the License.
       You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

       Unless required by applicable law or agreed to in writing, software
       distributed under the License is distributed on an "AS IS" BASIS,
       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       See the License for the specific language governing permissions and
       limitations under the License.

-->
<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.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot</artifactId>
    <version>1.3.2</version>
  </parent>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <name>mybatis-spring-boot-starter</name>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
    </dependency>
  </dependencies>
</project>

做爲一個集成包,能夠看到包含了mybatis、mybatis-spring、spring-boot-starter-jdbc。數據庫

Spring自動裝配的相關注解

@Autowired

  • 默認是按照類型進行配置注入,默認狀況下,它要求依賴對象必須存在,若是容許爲null值,能夠設置它的 required 爲false
  • 若是想要按照名稱進行裝配的話,能夠添加一個@Qualifier註解解決

@Qualifire

  • 讓Spring能夠按照Bean名稱注入
/**
     * 獲得數據源
     * 定義一個名字爲barDataSource的數據源
     *
     * @return
     */
    @Bean("barDataSource")
    public DataSource barDataSource() {
        DataSourceProperties dataSourceProperties = barDataSourceProperties();
        log.info("bar datasource url:{}", dataSourceProperties.getUrl());
        log.info("{}", dataSourceProperties.getType().getName());
        return dataSourceProperties.initializeDataSourceBuilder().build();
    }

    /**
     * 定義一個名字爲 barSqlSessionFactory 的SqlSession工廠,實現的時候使用名字爲barDataSource的數據源去實現
     * @param dataSource
     * @return
     * @throws Exception
     */
    @Bean("barSqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("barDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION));
        return bean.getObject();
    }
  • 首先定義一個名字爲barDataSource的數據源
  • 在定義SqlSession工廠時,經過@Qualifier指定使用名字爲barDataSource數據源對象

@Primary

  • 當有多個相同類型的Bean時,優先使用@Primary註解的Bean

@Resource

  • Resource有兩個比較重要的屬性
    • name:當設置了name屬性時,Spring會將name的屬性解析爲bean的名稱,使用byName的自動注入策略
    • type:當設置了type屬性時,Spring會將type的屬性解析爲bean的類型,使用byType的自動注入策略
    • 若是既沒有設置name屬性,又沒有設置type屬性,Spring經過反射機制使用byName自動註冊策略
  • 使用Resource裝配時的裝配順序
    • 若是同時制定了name和type,那麼Spring將從容器中找到惟一匹配的bean進行裝配,若是找不到則拋出異常
    • 若是指定了name屬性,則從容器中查找名稱匹配的bean進行裝配,找不到則拋出異常
    • 若是指定了type屬性,則從容器中查找類型匹配的惟一的bean進行裝配,找不到或者找到多個都會拋出異常
    • 若是都不指定,則會自動按照byName方式裝配,若是沒有匹配,則回退一個原始類型進行匹配,若是匹配則自動裝配

@ConfigurationProperties和@PropertySource

@ConfigurationProperties和@PropertySource都是用來讀取Spring的配置文件的,有三種配置方式express

第一種
  • 首先在resource中建立一個資源文件(*.properties文件),好比book.properties,書籍的名稱、價錢、版本等信息
  • 建立對應的配置類文件,使用@PropertySource指定配置資源的名稱避免出現中文信息亂碼能夠設置編碼信息,使用@ConfigurationProperties指定屬性信息

image.pngimage.png

package com.lucky.spring.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

/**
 * Created by zhangdd on 2020/7/16
 */
@PropertySource(value = "book.properties",encoding = "utf-8")
@ConfigurationProperties(prefix = "book")
@Component
public class BookConfig {
    private String name;
    private String price;
    private String version;

    public String getName() {
        return name;
    }

    public String getPrice() {
        return price;
    }

    public String getVersion() {
        return version;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setPrice(String price) {
        this.price = price;
    }

    public void setVersion(String version) {
        this.version = version;
    }
}

測試用例apache

@RestController
public class BookController {

    @Autowired
    BookConfig bookConfig;

    @GetMapping("/api/queryBook")
    public String queryBook() {
        StringBuilder builder = new StringBuilder();
        StringBuilder bookInfo = builder.append(bookConfig.getName())
                .append(",")
                .append(bookConfig.getPrice())
                .append(",")
                .append(bookConfig.getVersion());
        return bookInfo.toString();
    }
}
第二種
  • 首先在resources下建立config文件夾,在config文件夾下建立資源文件(*.properties文件),好比person.properties,配置人員的信息
  • 建立對應的配置類文件,使用@PropertySource指定配置資源的名稱(區別點在於這裏要指定資源文件的路徑信息)避免出現中文信息亂碼能夠設置編碼信息,使用@ConfigurationProperties指定屬性信息

image.pngimage.png

person.name=張三
person.age=18
    
package com.lucky.spring.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

/**
 * Created by zhangdd on 2020/7/16
 */
@Component
@PropertySource(value = "classpath:config/person.properties",encoding = "utf-8")
@ConfigurationProperties(prefix = "person")
public class PersonConfig {
    private String name;
    private String age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
}

測試用例api

public class PersonController {

    @Autowired
    PersonConfig personConfig;

    @GetMapping("/api/queryPersonInfo")
    public String queryPersonInfo() {
        StringBuilder builder = new StringBuilder();
        builder.append(personConfig.getName())
                .append(",")
                .append(personConfig.getAge());
        return builder.toString();
    }
}
第三種
  • 直接將配置信息寫在application.properties中,
  • 建立對應的配置類文件,當寫在此文件中時,不須要指明資源文件路徑只須要使用@ConfigurationProperties指明前綴便可

image.png
image.png

car:
  price: 12345678
  name: 奧迪
 
 package com.lucky.spring.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * Created by zhangdd on 2020/7/16
 */
@Component
@ConfigurationProperties(prefix = "car")
public class CarConfig {

    private String price;
    private String name;

    public String getPrice() {
        return price;
    }

    public void setPrice(String price) {
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

測試用例session

package com.lucky.spring.controller;

import com.lucky.spring.config.CarConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Created by zhangdd on 2020/7/16
 */
@RestController
public class CarController {

    @Autowired
    CarConfig carConfig;

    @GetMapping("/api/queryCarInfo")
    public String queryCarInfo() {
        StringBuilder builder = new StringBuilder();
        builder.append(carConfig.getName())
                .append(",")
                .append(carConfig.getPrice());
        return builder.toString();
    }
}

多數據源配置使用

相關依賴配置

基於環境背景信息,mysql數據庫,mybatis持久層,三方數據源druid。添加配置信息以下:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.lucky</groupId>
    <artifactId>04spring-multi-datasource</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
    </properties>

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

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

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

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>

            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.2</version>
                <dependencies>
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>8.0.17</version>
                        <scope>runtime</scope>
                    </dependency>
                </dependencies>
                <configuration>
                    <configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile>
                    <verbose>true</verbose>
                    <overwrite>true</overwrite>
                </configuration>
            </plugin>
        </plugins>

    </build>
    
</project>
  • 添加對應配置信息
    • mybatis-spring-boot-starter
    • mysql-connector-java
    • druid-spring-boot-starter

鏈接信息配置

既然是多數據源的配置操做,那就是配置數據源信息了。配置方式有兩種:

  • 在application.yml文件中配置
  • 在業務代碼裏配置

這裏以配置文件中爲例說明。

datasource:
  foo:
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://localhost:3306/readinglist?characterEncoding=utf8&useSSL=false
    username: root
    password: 12345678
  bar:
    type: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://localhost:3306/shiro?characterEncoding=utf8&useSSL=false
    username: root
    password: 12345678
  • 定義了兩個數據源信息foo、bar
  • 不一樣的數據源配置本身的數據庫信息和Datasource信息

使用配置信息

已經對信息進行了配置,下面就是對配置信息的使用了。

foo數據源的配置

package com.lucky.spring.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

/**
 * Created by zhangdd on 2020/7/13
 */
@Configuration
@Slf4j
@MapperScan(basePackages = "com.lucky.spring.dao.foo", sqlSessionFactoryRef = "fooSqlSessionFactory")
public class FooDataSourceConfig {


    static final String MAPPER_LOCATION = "classpath:mapper/foo/*.xml";
    //=========foo 數據源相關配置 start==================================================


    @Bean
    @Primary// 該註解是指當有多個相同的bean時,優先使用用@Primary註解的bean
    @ConfigurationProperties("datasource.foo")
    public DataSourceProperties fooDataSourceProperties() {
        return new DataSourceProperties();
    }

    /**
     * 返回 foo 對應的數據源
     *
     * @return
     */
    @Bean("fooDataSource")
    @Primary
    public DataSource fooDataSource() {
        DataSourceProperties dataSourceProperties = fooDataSourceProperties();
        log.info("foo datasource url:{}", dataSourceProperties.getUrl());
        log.info("{}", dataSourceProperties.getType().getName());
        return dataSourceProperties.initializeDataSourceBuilder().build();
    }


    /**
     * 返回foo 對應數據庫的會話工廠
     *
     * @param ds
     * @return
     */
    @Bean(name = "fooSqlSessionFactory")
    @Primary
    public SqlSessionFactory sqlSessionFactory(@Qualifier("fooDataSource") DataSource ds) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(ds);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION));
        return bean.getObject();
    }

    /**
     * foo 對應的數據庫會話模版
     *
     * @param sessionFactory
     * @return
     */
    @Bean("fooSqlSessionTemplate")
    @Primary
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("fooSqlSessionFactory") SqlSessionFactory sessionFactory) {
        log.info("sessionFactory:{}", sessionFactory.toString());
        return new SqlSessionTemplate(sessionFactory);
    }

    /**
     * 返回 foo 對應的數據庫事務
     *
     * @param fooDatasource
     * @return
     */
    @Bean
    @Primary
    public DataSourceTransactionManager fooTxManager(@Qualifier("fooDataSource") DataSource fooDatasource) {
        return new DataSourceTransactionManager(fooDatasource);
    }

    //=========foo 數據源相關配置 end==================================================


}
  • 配置類文件使用@MapperScan指定mybatis的接口定義包
  • 首先使用@ConfigurationProperties("datasource.foo")來裝載yml文件例的配置信息
  • 建立DataSource,這裏調用第一步獲取配置信息。若是不在yml文件例進行配置,也能夠所有在這裏使用Java代碼實現
  • 建立SqlSessionFactory
  • 建立SqlSessionTemplate
  • 建立DataSourceTransactionManager

建議將須要的都建立好,這樣方便作一些優化,好比超時時間、最大鏈接數、事務的處理方式

bar數據源的配置

bar數據源的配置和foo的模式是同樣的,代碼以下:

package com.lucky.spring.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.annotation.Resource;
import javax.sql.DataSource;

/**
 * Created by zhangdd on 2020/7/13
 */
@Configuration
@Slf4j
@MapperScan(basePackages = "com.lucky.spring.dao.bar", sqlSessionFactoryRef = "barSqlSessionFactory")
public class BarDataSourceConfig {

    static final String MAPPER_LOCATION = "classpath:mapper/bar/*.xml";

    //=========bar 數據源相關配置 start==================================================

    @Bean
    @ConfigurationProperties("datasource.bar")
    public DataSourceProperties barDataSourceProperties() {
        return new DataSourceProperties();
    }

    /**
     * 獲得數據源
     *
     * @return
     */
    @Bean("barDataSource")
    public DataSource barDataSource() {
        DataSourceProperties dataSourceProperties = barDataSourceProperties();
        log.info("bar datasource url:{}", dataSourceProperties.getUrl());
        log.info("{}", dataSourceProperties.getType().getName());
        return dataSourceProperties.initializeDataSourceBuilder().build();
    }

    /**
     *
     * @param dataSource
     * @return
     * @throws Exception
     */
    @Bean("barSqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("barDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION));
        return bean.getObject();
    }


    /**
     * bar 對應的數據庫會話模版
     *
     * @param sessionFactory
     * @return
     */
    @Bean("barSqlSessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("barSqlSessionFactory") SqlSessionFactory sessionFactory) {
        return new SqlSessionTemplate(sessionFactory);
    }


    @Bean
    @Resource
    public DataSourceTransactionManager barTxManager(@Qualifier("barDataSource") DataSource barDatasource) {
        return new DataSourceTransactionManager(barDatasource);
    }

    //=========bar 數據源相關配置 end==================================================

}

測試場景

兩個數據源foo和bar鏈接的數據庫分別是readinglist和shiro,分別以readinglist數據庫例的foo表和tpermission表爲例。
image.png

@RestController
public class MultiDataController {

    @Autowired
    TPermissionMapper permissionMapper;

    @Autowired
    FooMapper fooMapper;

    @GetMapping("/api/getData")
    public MultiDataVo getData() {
        List<TPermission> tPermissions = permissionMapper.queryList();
        List<Foo> foos = fooMapper.queryList();
        MultiDataVo dataVo = new MultiDataVo();
        dataVo.setFoos(foos);
        dataVo.setPermissions(tPermissions);
        return dataVo;
    }
}
package com.lucky.spring.vo;

import com.lucky.spring.model.bar.TPermission;
import com.lucky.spring.model.foo.Foo;
import lombok.Data;

import java.util.List;

/**
 * Created by zhangdd on 2020/7/14
 */
@Data
public class MultiDataVo {
    private List<TPermission> permissions;
    private List<Foo> foos;

}
相關文章
相關標籤/搜索