SpringBoot 的多資料來源配置

未讀程式碼發表於2020-12-30

最近在專案開發中,需要為一個使用 MySQL 資料庫的 SpringBoot 專案,新新增一個 PLSQL 資料庫資料來源,那麼就需要進行 SpringBoot 的多資料來源開發。程式碼很簡單,下面是實現的過程。

環境準備

實驗環境:

  • JDK 1.8
  • SpringBoot 2.4.1
  • Maven 3.6.3
  • MySQL 5.7

因為我本地只有 MySQL 資料庫,為了方便演示,我會在啟動一個本地 MySQL,在 MySQL 建立兩個資料庫,每個庫中均有一個表,以此進行演示。

資料準備

本地 MySQL 埠預設不做改動,埠號 3306。

建立資料庫 demo1,demo2。在 demo1 資料庫中建立表 book。

-- create table
create table Book
(
    id          int auto_increment
        primary key,
    author      varchar(64)  not null comment '作者資訊',
    name        varchar(64)  not null comment '書籍名稱',
    price       decimal      not null comment '價格',
    createTime  datetime     null comment '上架時間',
    description varchar(128) null comment '書籍描述'
);
-- insert data
INSERT INTO demo1.Book (id, author, name, price, createTime, description) VALUES (1, '金庸', '笑傲江湖', 13, '2020-12-19 15:26:51', '武俠小說');
INSERT INTO demo1.Book (id, author, name, price, createTime, description) VALUES (2, '羅貫中', '三國演義', 14, '2020-12-19 15:28:36', '歷史小說');

在 demo2 資料庫中建立表 user。

-- create table
create table User
(
    id       int auto_increment
        primary key,
    name     varchar(32) null comment '使用者名稱稱',
    birthday date        null comment '出生日期'
)
    comment '使用者資訊表';
-- insert data
INSERT INTO demo2.User (id, name, birthday) VALUES (1, '金庸', '1924-03-10');
INSERT INTO demo2.User (id, name, birthday) VALUES (2, '羅貫中', '1330-01-10');

資料準備完畢,表中都新增了兩條資料。

專案準備

這裡直接從 Spring 官方上初始化一個新增了 web、lombok、mybatis、mysql 依賴的 SpringBoot 專案。

訪問直接下載:https://start.spring.io/starter.zip?type=maven-project&language=java&bootVersion=2.4.1.RELEASE&baseDir=demo&groupId=com&artifactId=wdbyte&name=demo&description=Demo%20project%20for%20Spring%20Boot&packageName=com.wdbyte.demo&packaging=jar&javaVersion=1.8&dependencies=mybatis,lombok,web,mysql

如果你手上已經有了一個 SpringBoot 專案,既然你想改造成多資料來源,那麼你應該已經有了一個資料來源了,如果新增的資料來源資料庫和目前的一致,你可以直接使用你的專案進行改造測試。

多資料來源

SpringBoot 的多資料來源開發十分簡單,如果多個資料來源的資料庫相同,比如都是 MySQL,那麼依賴是不需要任何改動的,只需要進行多資料來源配置即可。

如果你新增的資料庫資料來源和目前的資料庫不同,記得引入新資料庫的驅動依賴,比如 MySQL 和 PGSQL。

<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<scope>runtime</scope>
</dependency>

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.2.7</version>
</dependency>

連線配置

既然有多個資料來源,因為資料庫使用者名稱密碼可能不相同,所以是需要配置多個資料來源資訊的,直接在 properties/yml 中配置即可。這裡要注意根據配置的屬性名進行區分,同時因為資料來源要有一個預設使用的資料來源,最好在名稱上有所區分(這裡使用 primary 作為主資料來源標識)。

########################## 主資料來源 ##################################
spring.datasource.primary.jdbc-url=jdbc:mysql://127.0.0.1:3306/demo1?characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.primary.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.primary.username=root
spring.datasource.primary.password=

########################## 第二個資料來源 ###############################
spring.datasource.datasource2.jdbc-url=jdbc:mysql://127.0.0.1:3306/demo2?characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.datasource2.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.datasource2.username=root
spring.datasource.datasource2.password=

# mybatis
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.wdbyte.domain

注意,配置中的資料來源連線 url 末尾使用的是 jdbc-url.

因為使用了 Mybatis 框架,所以 Mybatis 框架的配置資訊也是少不了的,指定掃描目錄 mapper 下的mapper xml 配置檔案。

Mybatis 配置

如何編寫 Mybatis Mapper 或者如何使用工具生成 Mybatis Mapper 不是本文的重點,如果你不知道可以參考 Mybatis 官方文件或者我之前的文章。

連結一:使用 Mybatis(自動生成外掛) 訪問資料庫

連結二:使用 Mybatis 整合 pagehelper 分頁外掛和 mapper 外掛

下面我已經按照上面的兩個庫中的兩個表,Book 和 User 表分別編寫相應的 Mybatis 配置。

建立 BookMapper.xmlUserMapper.xml 放到配置檔案配置的路徑 mapper 目錄下。建立 UserMapper 和 BookMapper 介面操作類放在不同的目錄。這裡注意 Mapper 介面要按資料來源分開放在不同的目錄中。後續好使用不同的資料來源配置掃描不同的目錄,這樣就可以實現不同的 Mapper 使用不同的資料來源配置。

Mybatis 檔案配置

Service 層沒有變化,這裡 BookMapper 和 UserMapper 都有一個 selectAll() 方法用於查詢測試。

多資料來源配置

上面你應該看到了,到目前為止和 Mybatis 單資料來源寫法唯一的區別就是 Mapper 介面使用不同的目錄分開了,那麼這個不同點一定會在資料來源配置中體現。

主資料來源

開始配置兩個資料來源資訊,先配置主資料來源,配置掃描的 MapperScan 目錄為 com.wdbyte.mapper.primary

/**
 * 主資料來源配置
 *
 * @author niujinpeng
 * @website: https://www.wdbyte.com
 * @date 2020/12/19
 */
@Configuration
@MapperScan(basePackages = {"com.wdbyte.mapper.primary"}, sqlSessionFactoryRef = "sqlSessionFactory")
public class PrimaryDataSourceConfig {

    @Bean(name = "dataSource")
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    @Primary
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "sqlSessionFactory")
    @Primary
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
        return bean.getObject();
    }

    @Bean(name = "transactionManager")
    @Primary
    public DataSourceTransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "sqlSessionTemplate")
    @Primary
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

和單資料來源不同的是這裡把

  • dataSource
  • sqlSessionFactory
  • transactionManager
  • sqlSessionTemplate

都單獨進行了配置,簡單的 bean 建立,下面是用到的一些註解說明。

  • @ConfigurationProperties(prefix = "spring.datasource.primary"):使用spring.datasource.primary 開頭的配置。
  • @Primary :宣告這是一個主資料來源(預設資料來源),多資料來源配置時必不可少
  • @Qualifier:顯式選擇傳入的 Bean。

第二個資料來源

第二個資料來源和主資料來源唯一不同的只是 MapperScan 掃描路徑和建立的 Bean 名稱,同時沒有 @Primary 主資料來源的註解。

/**
 * 第二個資料來源配置
 * 
 * @author niujinpeng
 * @website: https://www.wdbyte.com
 * @date 2020/12/19
 */
@Configuration
@MapperScan(basePackages = {"com.wdbyte.mapper.datasource2"}, sqlSessionFactoryRef = "sqlSessionFactory2")
public class SecondDataSourceConfig {

    @Bean(name = "dataSource2")
    @ConfigurationProperties(prefix = "spring.datasource.datasource2")
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "sqlSessionFactory2")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource2") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
        return bean.getObject();
    }

    @Bean(name = "transactionManager2")
    public DataSourceTransactionManager transactionManager(@Qualifier("dataSource2") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "sqlSessionTemplate2")
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory2") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

注意:因為已經在兩個資料來源中分別配置了掃描的 Mapper 路徑,如果你之前在 SpringBoot 啟動類中也使用了 Mapper 掃描註解,需要刪掉

訪問測試

編寫兩個簡單的查詢 Controller 然後進行訪問測試。

// BookController
@RestController
public class BookController {

    @Autowired
    private BookService bookService;

    @GetMapping(value = "/books")
    public Response selectAll() throws Exception {
        List<Book> books = bookService.selectAll();
        return ResponseUtill.success(books);
    }
}

// UserController
@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @ResponseBody
    @GetMapping(value = "/users")
    public Response selectAll() {
        List<User> userList = userService.selectAll();
        return ResponseUtill.success(userList);
    }
}

訪問測試,我這裡直接 CURL 請求。

➜  ~ curl localhost:8080/books 
{
  "code": "0000",
  "message": "success",
  "data": [
    {
      "id": 1,
      "author": "金庸",
      "name": "笑傲江湖",
      "price": 13,
      "createtime": "2020-12-19T07:26:51.000+00:00",
      "description": "武俠小說"
    },
    {
      "id": 2,
      "author": "羅貫中",
      "name": "三國演義",
      "price": 14,
      "createtime": "2020-12-19T07:28:36.000+00:00",
      "description": "歷史小說"
    }
  ]
}
➜  ~ curl localhost:8080/users 
{
  "code": "0000",
  "message": "success",
  "data": [
    {
      "id": 1,
      "name": "金庸",
      "birthday": "1924-03-09T16:00:00.000+00:00"
    },
    {
      "id": 2,
      "name": "羅貫中",
      "birthday": "1330-01-09T16:00:00.000+00:00"
    }
  ]
}
➜  ~

至此,多資料來源配置完成,測試成功。

連線池

其實在多資料來源改造中,我們一般情況下都不會使用預設的 JDBC 連線方式,往往都需要引入連線池進行連線優化,不然你可能會經常遇到資料來源連線被斷開等報錯日誌。其實資料來源切換連線池資料來源也是十分簡單的,直接引入連線池依賴,然後把建立 dataSource 的部分換成連線池資料來源建立即可。

下面以阿里的 Druid 為例,先引入連線池資料來源依賴。

<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>druid</artifactId>
</dependency>

新增 Druid 的一些配置。

spring.datasource.datasource2.initialSize=3 # 根據自己情況設定
spring.datasource.datasource2.minIdle=3
spring.datasource.datasource2.maxActive=20

改寫 dataSource Bean 的建立程式碼部分。

@Value("${spring.datasource.datasource2.jdbc-url}")
private String url;
@Value("${spring.datasource.datasource2.driver-class-name}")
private String driverClassName;
@Value("${spring.datasource.datasource2.username}")
private String username;
@Value("${spring.datasource.datasource2.password}")
private String password;
@Value("${spring.datasource.datasource2.initialSize}")
private int initialSize;
@Value("${spring.datasource.datasource2.minIdle}")
private int minIdle;
@Value("${spring.datasource.datasource2.maxActive}")
private int maxActive;

@Bean(name = "dataSource2")
public DataSource dataSource() {
    DruidDataSource dataSource = new DruidDataSource();
    dataSource.setUrl(url);
    dataSource.setDriverClassName(driverClassName);
    dataSource.setUsername(username);
    dataSource.setPassword(password);
    dataSource.setInitialSize(initialSize);
    dataSource.setMinIdle(minIdle);
    dataSource.setMaxActive(maxActive);
    return dataSource;
}

這裡只是簡單的提一下使用連線池的重要性,Druid 的詳細用法還請參考官方文件。

文中程式碼已經上傳到 Github: https://github.com/niumoo/springboot

最後的話

文章有幫助可以點個「」或「分享」,都是支援!
文章每週持續更新,可以關注「 未讀程式碼 」公眾號或者我的部落格

公眾號

相關文章