Spring Boot從入門到精通(五)多數據源配置實現及源碼分析

轉載:素文宅博客

地址:https://blog.yoodb.com/yoodb/article/detail/1566

多數據源配置在項目軟件中是比較常見的開發需求,Spring和Spring Boot中對此都有相應的解決方案可供大家參考。在Spring Boot中,如MyBatis、JdbcTemplate以及Jpa都可以配置多數據源。

本文在前一篇“Spring Boot從入門到精通(四)連接MySQL數據庫(附源碼)”文章中項目源碼的基礎上,來實現Spring Boot集成MyBatis和使用JdbcTemplate兩種方式配置多數據源。

Spring Boot集成MyBatis和使用JdbcTemplate配置公共文件

1、配置數據源application.properties文件
假定有兩個數據源來配置實現,分別對應的名字是oneDataSource和twoDataSource。
在application.properties文件中配置數據源信息如下:

spring.datasource.one.url=jdbc:mysql://123.57.47.154:3306/springboot1
spring.datasource.one.username=root
spring.datasource.one.password=wangyoodb
spring.datasource.one.driverClassName=com.mysql.cj.jdbc.Driver

spring.datasource.two.url=jdbc:mysql://123.57.47.154:3306/springboot2
spring.datasource.two.username=root
spring.datasource.two.password=wangyoodb
spring.datasource.two.driverClassName=com.mysql.cj.jdbc.Driver

2、數據源配置java類文件
通過上一步操作,利用關鍵詞one和two對數據源進行區分,只是簡單增加這些信息是無法被Spring Boot自動加載的,需要增加代碼來實現加載DataSource,下面小編手動配置DataSourceConfig,用來提供DataSource Bean。
數據源一:新增命名DataSourceOneConfig的java類文件,具體代碼如下:

package com.yoodb.study.demo03.datasource;

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.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
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 javax.sql.DataSource;

@Configuration
@MapperScan(basePackages = "com.yoodb.study.demo03.mapper.one", sqlSessionFactoryRef = "oneSqlSessionFactory")
public class DataSourceOneConfig {
    @Bean(name = "oneDataSource")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.one")
    public DataSource getDateSourceOne() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "oneSqlSessionFactory")
    @Primary
    public SqlSessionFactory oneSqlSessionFactory(@Qualifier("oneDataSource") DataSource datasource)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(datasource);
        bean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/one/*.xml"));
        return bean.getObject();
    }
    //
    @Bean("oneSqlSessionTemplate")
    @Primary
    public SqlSessionTemplate onesqlsessiontemplate(
            @Qualifier("oneSqlSessionFactory") SqlSessionFactory sessionfactory) {
        return new SqlSessionTemplate(sessionfactory);
    }
}

數據源二:新增命名DataSourceTwoConfig的java類文件,具體代碼如下:

package com.yoodb.study.demo03.datasource;

import com.zaxxer.hikari.HikariDataSource;
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.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;

@Configuration
@MapperScan(basePackages = "com.yoodb.study.demo03.mapper.two", sqlSessionFactoryRef = "twoSqlSessionFactory")
public class DataSourceTwoConfig {
    @Bean(name = "twoDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.two")
    public DataSource getDateSourceTwo(DataSourceProperties properties) {
        return DataSourceBuilder.create(properties.getClassLoader())
                .type(HikariDataSource.class)
                .driverClassName(properties.determineDriverClassName())
                .url(properties.determineUrl())
                .username(properties.determineUsername())
                .password(properties.determinePassword())
                .build();
    }
    @Bean(name = "twoSqlSessionFactory")
    public SqlSessionFactory twoSqlSessionFactory(@Qualifier("twoDataSource") DataSource datasource)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(datasource);
        bean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/two/*.xml"));
        return bean.getObject();
    }
    @Bean("twoSqlSessionTemplate")
    public SqlSessionTemplate twosqlsessiontemplate(
            @Qualifier("twoSqlSessionFactory") SqlSessionFactory sessionfactory) {
        return new SqlSessionTemplate(sessionfactory);
    }
}

在上述代碼中使用的註解含義可以參考之前寫過“Spring Boot從入門到精通(三)常用註解含義及用法分析總結”的文章(見微信公衆號“Java精選”),具體使用註解含義如下:

@Configuration

用於定義配置類,可替換xml配置文件,被註解的類內部包含有一個或多個被@Bean註解的方法,這些方法將會被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext類進行掃描,並用於構建bean定義,初始化Spring容器。

@MapperScan

指定要掃描mapper類包的路徑,配置mybatis的接口存放位置。

@Bean

用於告訴方法產生一個Bean對象,然後這個Bean對象交給Spring管理。

@ConfigurationProperties

是Spring Boot提供的類型安全的屬性綁定,以第一個Bean爲例,@ConfigurationProperties(prefix = “spring.datasource.one”)表示使用spring.datasource.one前綴的數據庫配置去創建一個DataSource。

@Primary

可以理解爲默認優先選擇,不可以同時設置多個,必須增加此註解用於區分主數據庫(默認數據庫)。

@Qualifier

來達到注入某個特指bean的作用,表示查找Spring容器中指定名字的對象。

注意:

bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(“***”)) mapper的xml形式文件位置必須要配置,不然項目會報no statement錯誤信息。

3、實體類文件

新增實體類文件,兩個數據源公用一個實體類,具體代碼如下:

package com.yoodb.study.demo03.bean;

public class BootUser {
    private String id;
    private String name;
    private String detail;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getDetail() {
        return detail;
    }

    public void setDetail(String detail) {
        this.detail = detail;
    }
}

集成Mybatis

1、新增mapper xml文件
數據源一:在src/main/resources/mapper/one(不存在文件加新建)創建BootUserOneMapper.xml文件,具體配置信息如下:

<?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.yoodb.study.demo03.mapper.one.BootUserOneMapper" >
    <resultMap id="BaseResultMap" type="com.yoodb.study.demo03.bean.BootUser" >
        <id column="id" property="id" jdbcType="VARCHAR" />
        <result column="user_name" property="name" jdbcType="VARCHAR" />
        <result column="detail" property="detail" jdbcType="VARCHAR" />
    </resultMap>

    <select id="selectAll" resultMap="BaseResultMap">
    select
         id, user_name, detail
    from boot_user order by detail asc
    </select>
</mapper>

數據源二:在src/main/resources/mapper/two(不存在文件加新建)創建BootUserTwoMapper.xml文件,具體配置信息如下:

<?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.yoodb.study.demo03.mapper.two.BootUserTwoMapper" >
    <resultMap id="BaseResultMap" type="com.yoodb.study.demo03.bean.BootUser" >
        <id column="id" property="id" jdbcType="VARCHAR" />
        <result column="user_name" property="name" jdbcType="VARCHAR" />
        <result column="detail" property="detail" jdbcType="VARCHAR" />
    </resultMap>

    <select id="selectAll" resultMap="BaseResultMap">
    select
         id, user_name, detail
    from boot_user order by detail asc
    </select>
</mapper>

2、新增mapper接口類文件

數據源一:mapper接口類文件,具體代碼如下:

package com.yoodb.study.demo03.mapper.one;
import com.yoodb.study.demo03.bean.BootUser;
import java.util.List;
public interface BootUserOneMapper {
  List<BootUser> selectAll();
}

數據源二:mapper接口類文件,具體代碼如下:

package com.yoodb.study.demo03.mapper.two;
import com.yoodb.study.demo03.bean.BootUser;
import java.util.List;
public interface BootUserTwoMapper {
  List<BootUser> selectAll();
}

3、創建service類文件
新增文件名BootUserService類文件,具體代碼如下:

package com.yoodb.study.demo03.service;

import com.yoodb.study.demo03.bean.BootUser;
import com.yoodb.study.demo03.mapper.one.BootUserOneMapper;
import com.yoodb.study.demo03.mapper.two.BootUserTwoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class BootUserService {
    
    @Autowired
    private BootUserOneMapper onemapper;

    @Autowired
    private BootUserTwoMapper twomapper;

    public List<BootUser> getUsers(){
        List<BootUser> listone = onemapper.selectAll();
        List<BootUser> listtwo = twomapper.selectAll();
        listone.addAll(listtwo);
        return listone;
    }
}

4、創建controller類文件
新增文件名BootUserController類文件,具體代碼如下:

package com.yoodb.study.demo03;

import java.util.List;

import com.yoodb.study.demo03.bean.BootUser;
import com.yoodb.study.demo03.service.BootUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/myt")
public class BootUserController {
    @Autowired
    private BootUserService service;

    @RequestMapping("/getUsers")
    public List<BootUser> getUsers() {
        List<BootUser> list = service.getUsers();
        return list;
    }

}

mapper的接口、xml文件及實體文件、service層、controller層創建完成後,目錄如圖:

5、項目啓動

以上操作完成後,項目啓動過程中控制檯報錯,錯誤信息如下:

### Error querying database. Cause: org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "BOOT_USER" not found; SQL statement:
select
id, user_name, detail
from boot_user order by detail asc [42102-200]
### The error may exist in file [F:\project\study\springboot-study-demo03\target\classes\mapper\one\BootUserOneMapper.xml]
### The error may involve com.yoodb.study.demo03.mapper.one.BootUserOneMapper.selectAll
### The error occurred while executing a query
### SQL: select id, user_name, detail from boot_user order by detail asc
### Cause: org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "BOOT_USER" not found; SQL statement:

問題分析:目前Spring Boot最新版本不會將application.properties文件加載到classes目錄下,導致無法讀取到application.properties文件信息,從而在項目訪問的時候控制檯報上述錯誤信息。
解決方法:就是在pom.xml文件中增加如下配置信息:

<resources>
    <resource>
        <!-- 指定resources插件處理哪個目錄下的資源文件 -->
        <directory>src/main/resources</directory>
        <includes>
            <include>**/**</include>
        </includes>
    </resource>
</resources>

上述問題解決完後系統可以正常啓動,但是在通過瀏覽器訪問時還是會報錯。

訪問地址:

http://localhost:8080/myt/getUsers

錯誤信息如下:

### Error querying database. Cause: java.lang.IllegalArgumentException: dataSource or dataSourceClassName or jdbcUrl is required.
### The error may exist in file [F:\project\study\springboot-study-demo03\target\classes\mapper\one\BootUserOneMapper.xml]
### The error may involve com.yoodb.study.demo03.mapper.BootUserOneMapper.selectAll
### The error occurred while executing a query
### Cause: java.lang.IllegalArgumentException: dataSource or dataSourceClassName or jdbcUrl is required.] with root cause
java.lang.IllegalArgumentException: dataSource or dataSourceClassName or jdbcUrl is required.

問題分析:

1)在Spring Boot 1.* 版本中配置數據源是spring.datasource.url和spring.datasource.driverClassName;

2)在Spring Boot 2.* 版本中配置數據源是spring.datasource.jdbc-url和spring.datasource.driver-class-name;

3)Spring Boot項目中(本文是基於Spring Boot 2.3.0.M2 版本)由於無法讀取到application.properties文件中“driver-class-name”和“jdbc-url”兩個參數導致項目報錯。

解決方法:
將application.properties文件中spring.datasource..url和spring.datasource..driverClassName分別替換成spring.datasource..jdbc-url和spring.datasource..driver-class-name。

*Spring Boot源碼分析:

1)spring.datasource.*.url替換成spring.datasource.*.jdbcUrl也沒有任何問題。

源碼org.springframework.boot.jdbc包中DatabaseDriver.class文件反編譯後,發現url參數必須以“jdbc”開始,具體源碼如下:

public static DatabaseDriver fromJdbcUrl(String url) {
    if (StringUtils.hasLength(url)) {
        Assert.isTrue(url.startsWith("jdbc"), "URL must start with 'jdbc'");
        String urlWithoutPrefix = url.substring("jdbc".length()).toLowerCase(Locale.ENGLISH);
        DatabaseDriver[] var2 = values();
        int var3 = var2.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            DatabaseDriver driver = var2[var4];
            Iterator var6 = driver.getUrlPrefixes().iterator();

            while(var6.hasNext()) {
                String urlPrefix = (String)var6.next();
                String prefix = ":" + urlPrefix + ":";
                if (driver != UNKNOWN && urlWithoutPrefix.startsWith(prefix)) {
                    return driver;
                }
            }
        }
    }

    return UNKNOWN;
}

2)spring.datasource.*.driverClassName不替換也沒有影響。

源碼org.springframework.boot.autoconfigure.jdbc包中ConfigurationProperties.class文件反編譯後,發現屬性即爲driverClassName字段,具體源碼如下:

public String determineDriverClassName() {
    if (StringUtils.hasText(this.driverClassName)) {
        Assert.state(this.driverClassIsLoadable(), () -> {
            return "Cannot load driver class: " + this.driverClassName;
        });
        return this.driverClassName;
    } else {
    ...

修改application.properties文件配置,參考信息如下:

spring.datasource.one.jdbc-url=jdbc:mysql://123.57.47.154:3306/springboot1
spring.datasource.one.username=root
spring.datasource.one.password=wangyoodb
spring.datasource.one.driver-class-name=com.mysql.cj.jdbc.Driver

spring.datasource.two.jdbc-url=jdbc:mysql://123.57.47.154:3306/springboot2
spring.datasource.two.username=root
spring.datasource.two.password=wangyoodb
spring.datasource.two.driver-class-name=com.mysql.cj.jdbc.Driver

引入話題
之前有羣裏的網友問Spring Boot中jdbc-url和url有什麼區別?(題外話:關注微信公衆號“Java精選”,留言切換工會總好後臺發送消息可以進羣,羣內只允許探討技術,完全免費,幫助大家解決各位技術難題。)
相信現在通過上述的分析,大家應該很清楚了吧,兩者是因爲Spring Boot升級版本調整了源碼,所以“url”參數被重新命名了。
至此集成mybatis實現多數據源的方法已完成,項目啓動成功後可以正常訪問,通過瀏覽器訪問輸出如下信息:

[{"id":"1","name":"素文宅博客","detail":"歡迎關注“Java精選”微信公衆號,專注程序員推送一些Java開發知識,包括基礎知識、各大流行框架(Mybatis、Spring、Spring Boot等)、大數據技術(Storm、Hadoop、MapReduce、Spark等)、數據庫(Mysql、Oracle、NoSQL等)、算法與數據結構、面試專題、面試技巧經驗、職業規劃以及優質開源項目等。"},{"id":"2","name":"素文宅博客導航","detail":"歡迎關注“Java精選”微信公衆號,一部分由小編總結整理,另一部分來源於網絡上優質資源,希望對大家的學習和工作有所幫助。"}]

使用JdbcTemplate
1、新增controller類文件
新增文件名JdbcTemplateController類文件,具體代碼如下:

package com.yoodb.study.demo03;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import com.yoodb.study.demo03.bean.BootUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping("/jte")
public class JdbcTemplateController {
    @Autowired
    private JdbcTemplate oneSqlSessionTemplate;

    @Autowired
    private JdbcTemplate twoSqlSessionTemplate;

    @RequestMapping("/getOneUsers")
    public List<Map<String, Object>> getOneUsers(){
        String sql = "select * from boot_user";
        List<Map<String, Object>> list =  twoSqlSessionTemplate.queryForList(sql);
        for (Map<String, Object> map : list) {
            Set<Entry<String, Object>> entries = map.entrySet( );
            if(entries != null) {
                Iterator<Entry<String, Object>> iterator = entries.iterator( );
                while(iterator.hasNext( )) {
                    Entry<String, Object> entry =(Entry<String, Object>) iterator.next( );
                    Object key = entry.getKey( );
                    Object value = entry.getValue();
                    System.out.println(key+":"+value);
                }
            }
        }
        return list;
    }

    @RequestMapping("/getTwoUsers")
    public List<BootUser> getTwoUsers() {
        List<BootUser> list = twoSqlSessionTemplate.query("select id,user_name " +
                "name,detail from boot_user", new BeanPropertyRowMapper<>(BootUser.class));
        return list;
    }
    
}

多數據源配置文件、多數據源類文件、實體類文件、controller層創建完成後,目錄如圖:


上述操作完成後,因爲集成mybatis時部分代碼已存在(配置多數據源類文件),所以使用JdbcTemplate配置完成。

2、項目啓動

項目啓動後訪問多數據源一請求地址:
http://localhost:8080/jte/getOneUsers
通過瀏覽器訪問輸出如下信息:

[{"id":1,"user_name":"素文宅博客","password":"e10adc3949ba59abbe56e057f20f883e","role_name":"素文宅博客","detail":"歡迎關注“Java精選”微信公衆號,專注程序員推送一些Java開發知識,包括基礎知識、各大流行框架(Mybatis、Spring、Spring Boot等)、大數據技術(Storm、Hadoop、MapReduce、Spark等)、數據庫(Mysql、Oracle、NoSQL等)、算法與數據結構、面試專題、面試技巧經驗、職業規劃以及優質開源項目等。"}]

項目啓動後訪問多數據源二請求地址:
http://localhost:8080/jte/getTwoUsers
通過瀏覽器訪問輸出如下信息:

[{"id":"1","name":"素文宅博客","detail":"歡迎關注“Java精選”微信公衆號,專注程序員推送一些Java開發知識,包括基礎知識、各大流行框架(Mybatis、Spring、Spring Boot等)、大數據技術(Storm、Hadoop、MapReduce、Spark等)、數據庫(Mysql、Oracle、NoSQL等)、算法與數據結構、面試專題、面試技巧經驗、職業規劃以及優質開源項目等。"}]

Spring容器中JdbcTemplate提供了兩種注入方式,一種是使用@Resource註解,直接通過byName的方式注入進來,另外一種就是@Autowired註解加上@Qualifier註解,兩者聯合起來,實際上也是byName。

注意:

將JdbcTemplate注入成功後,oneSqlSessionTemplate和twoSqlSessionTemplate此時就代表操作不同的數據源,使用不同的JdbcTemplate操作不同的數據源,實現了多數據源配置。
Spring Boot從入門到精通項目源碼(多數據源配置springboot-study-demo03)地址:
https://github.com/yoodb/springboot
至此,Spring Boot集成MyBatis和使用JdbcTemplate兩種方式多數據源配置完成,下面大家有什麼問題歡迎留言評論。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章