一. 引言
當前項目遇到需要連接多個數據庫的場景,此時需要引入多數據源了.
還有一些諸如以下的場景:
- 與第三方對接時,有些合作方并不會為了你的某些需求而給你開發(fā)一個功能,他們可以提供給你一個可以訪問數據源的只讀賬號,你需要獲取什么數據由你自己進行邏輯處理,這時候就避免不了需要進行多數據源了
- 業(yè)務數據達到了一個量級,使用單一數據庫存儲達到了一個瓶頸,需要進行分庫分表等操作進行數據管理,在操作數據時,不可避免的涉及到多數據源問題
網上搜索發(fā)現有不少的示例都是錯誤的,于是自己打算寫一篇,也方便以后自己需要用到的時候拿來參考.
如果你只想要看代碼請直接拉到最后看完整代碼哦~
如果你用的是Mybatis-Plus請查看官方文檔↓↓↓↓
MP多數據源配置
至于MyCat、Sharding-JDBC之類的中間件我們今天不談,只分享多數據源配置方案.
二. 實踐
HikariCP項目倉庫
注意:
Springboot 2.0開始開始默認引入了HikariCP依賴,所以我們不需要單獨引入!
HikariDataSource是 HikariCP 開放給用戶使用連接池的主要操作類。所以,我們創(chuàng)建一個 HikariCP 的連接池,其實就是構造一個HikariDataSource.
1. 首先我們來看一下正常情況下我們配置的單數據源的配置
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/db_xxx?useSSL=false&autoReconnect=true&characterEncoding=utf8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
# 指定為HikariDataSource
type: com.zaxxer.hikari.HikariDataSource
# hikari連接池配置
hikari:
#連接池名
pool-name: HikariCP
#最小空閑連接數
minimum-idle: 5
# 空閑連接存活最大時間,默認10分鐘
idle-timeout: 600000
# 連接池最大連接數,默認是10
maximum-pool-size: 10
# 此屬性控制從池返回的連接的默認自動提交行為,默認值:true
auto-commit: true
# 此屬性控制池中連接的最長生命周期,值0表示無限生命周期,默認30分鐘
max-lifetime: 1800000
# 數據庫連接超時時間,默認30秒
connection-timeout: 30000
# 連接測試query
connection-test-query: SELECT 1
2. 看看多數據源的配置示例(下面都以這個配置為準)
spring:
datasource:
# 數據源-1
primary:
url: jdbc:mysql://127.0.0.1:3306/db_market?useSSL=false&autoReconnect=true&characterEncoding=utf8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
# 指定為HikariDataSource
type: com.zaxxer.hikari.HikariDataSource
# hikari連接池配置 對應 HikariConfig 配置屬性類
hikari:
pool-name: HikariCP-Primary
#最小空閑連接數
minimum-idle: 5
# 空閑連接存活最大時間,默認10分鐘
idle-timeout: 600000
# 連接池最大連接數,默認是10
maximum-pool-size: 10
# 此屬性控制從池返回的連接的默認自動提交行為,默認值:true
auto-commit: true
# 此屬性控制池中連接的最長生命周期,值0表示無限生命周期,默認30分鐘
max-lifetime: 1800000
# 數據庫連接超時時間,默認30秒
connection-timeout: 30000
# 連接測試query
connection-test-query: SELECT 1
# 數據源-2
secondary:
url: jdbc:mysql://192.168.58.212:3306/db_market?useSSL=false&autoReconnect=true&characterEncoding=utf8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 12345678
# 指定為HikariDataSource
type: com.zaxxer.hikari.HikariDataSource
# hikari連接池配置
hikari:
pool-name: HikariCP-Secondary
minimum-idle: 5
idle-timeout: 600000
maximum-pool-size: 10
auto-commit: true
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECT 1
3. 接下來看看網上出現的不少這樣子的例子(這里認為是錯誤示例)
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "usersDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
對于多數據源的配置,這里由于 hikari 這個屬性對應的值還在下一層,所以使用上面這種配置是不生效的. 因為:
- 這里使用了Hikari,所以這里創(chuàng)建的DataSource這個Bean其實是HikariDataSource.
- @ConfigurationProperties(prefix = “spring.datasource.primary”)會將primary下的屬性賦值給HikariDataSource這個Bean中的對應的屬性(其實就是給HikariConfig這個賦值,更具體的信息大家可以自行搜索DataSource的初始化流程. 下面源碼圖中可看到HikariDataSource繼承了HikariConfig).
- 但是,hikari這個屬性因為是spring.datasource.primary的第二層屬性,并不能正確設置進去(后面我們會驗證這個問題,注意看我下圖中框出來LOGGER打印那里).
4. 下面是我完整的多數據源配置類(最終版本會分成兩個進行配置)
import com.zaxxer.hikari.HikariDataSource;
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.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
/**
* @ClassName: HikariDataSourceConfiguration
* @Author: lequal
* @Date: 2022/12/22
* @Description: 多數據源配置
*/
@Configuration
public class HikariDataSourceConfiguration {
@Primary
@Bean("primaryDataSourceProperties")
@ConfigurationProperties("spring.datasource.primary")
public DataSourceProperties primaryDataSourceProperties() {
return new DataSourceProperties();
}
@Primary
@Bean("primaryDataSource")
@Qualifier(value = "primaryDataSource")
// 留意下面這行
@ConfigurationProperties(prefix = "spring.datasource.primary.hikari")
public HikariDataSource primaryDataSource() {
return primaryDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
@Bean("secondaryDataSourceProperties")
@ConfigurationProperties("spring.datasource.secondary")
public DataSourceProperties secondaryDataSourceProperties() {
return new DataSourceProperties();
}
@Bean("secondaryDataSource")
@Qualifier(value = "secondaryDataSource")
// 留意下面這行
@ConfigurationProperties(prefix = "spring.datasource.secondary.hikari")
public HikariDataSource secondaryDataSource() {
return secondaryDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
@Bean(name = "primaryJdbcTemplate")
public JdbcTemplate primaryJdbcTemplate(@Qualifier("primaryDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean(name = "secondaryJdbcTemplate")
public JdbcTemplate secondaryJdbcTemplate(@Qualifier("secondaryDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
參考資料(實踐證明有事找官方文檔是挺標準的)↓↓↓↓↓
Spring官方文檔多數據配置示例
注意上面我在兩個
@ConfigurationProperties(prefix = “spring.datasource.primary.hikari”)
@ConfigurationProperties(prefix = “spring.datasource.secondary.hikari”)
第一、第二數據源配置都做了標記
驗證我們上面說的配置失效的問題,步驟:
1、當我們注釋掉這兩行,此時的配置就相當于是上面提到的錯誤示例的樣子了
2、多數據源配置以上面代碼為準,我分別設置兩個數據源的連接池名稱是HikariCP-Primary、HikariCP-Secondary,再結合最上面貼圖紅框中HikariDataSource構造方法知道Bean初始化時會打印數據池名稱
LOGGER.info(“{} - Starting…”, configuration.getPoolName());
3、啟動項目,查看控制臺
此時,你會發(fā)現,數據源初始化出來的并不是我們需要的,這足以說明我們配置的spring.datasource.primary.hikari這一層屬性沒有被正確地設置到HikariConfig中(雖然能跑起來,可以正確連接到對應的數據源,但是其它的配置并未生效),而是HikariConfig在初始化時自動給每個數據源加上了名字,可以見以下源碼(HikariConfig.class)
假如,這時候我們把
第一、第二數據源配置上面的@ConfigurationProperties(prefix = “spring.datasource.primary.hikari”)注解打開,再啟動項目,則可以清晰地看到我們自己設置的屬性被應用到了HikariConfig中.
這時候就可以正常地讀入hikari連接池的配置了.
有不理解的可以自己看看DataSource的初始化流程即可.
5. 接下來我們做一下測試
import cn.hutool.json.JSONUtil;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
/**
* @ClassName: MultiDataSourceTest
* @Author: lequal
* @Date: 2022/12/22
* @Description:
*/
@SpringBootTest
public class MultiDataSourceTest {
@Resource
private JdbcTemplate primaryJdbcTemplate;
@Resource
private JdbcTemplate secondaryJdbcTemplate;
@Resource(name = "primaryDataSource")
private DataSource primaryDataSource;
@Resource(name = "secondaryDataSource")
private DataSource secondaryDataSource;
@Test
public void testPrimaryDataSourceConnect() {
String sql = "SELECT * FROM `apk_category`";
List<Map<String, Object>> result = primaryJdbcTemplate.queryForList(sql);
System.out.println("primary data source :\t"+ JSONUtil.toJsonStr(result));
}
@Test
public void testSecondaryDataSourceConnect() {
String sql = "SELECT * FROM `apk_category`";
List<Map<String, Object>> result = secondaryJdbcTemplate.queryForList(sql);
System.out.println("secondary data source :\t"+ JSONUtil.toJsonStr(result));
}
@Test
void testGetConnection() {
try (Connection connection = primaryDataSource.getConnection()) {
System.out.println("獲取到的primaryDataSource連接對象" + connection);
} catch (SQLException e) {
e.printStackTrace();
}
try (Connection connection = secondaryDataSource.getConnection()) {
System.out.println("獲取到的secondaryDataSource連接對象" + connection);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
三.完整代碼
- PrimaryDataSourceConfiguration.class
import com.zaxxer.hikari.HikariDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
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.core.JdbcTemplate;
import javax.sql.DataSource;
/**
* @ClassName: PrimaryDataSourceConfiguration
* @Author: lequal
* @Date: 2022/12/22
* @Description: 主要數據源配置
*/
@Configuration
@MapperScan(basePackages = "com.market.mapper", sqlSessionFactoryRef = "primarySqlSessionFactory")
public class PrimaryDataSourceConfiguration {
/**
* 指定mapper xml文件路徑
*/
public static final String MAPPER_LOCATION = "classpath:mapper/*.xml";
/**
* @Author: lequal
* @Description 獲取一級的屬性
* @Date 2022/12/22 16:48
* @return org.springframework.boot.autoconfigure.jdbc.DataSourceProperties
*/
@Primary
@ConfigurationProperties("spring.datasource.primary")
public DataSourceProperties primaryDataSourceProperties() {
return new DataSourceProperties();
}
/**
* @Author: lequal
* @Description 獲取下一級的屬性(hikari)并創(chuàng)建數據源
* @Date 2022/12/22 16:48
* @return com.zaxxer.hikari.HikariDataSource
*/
@Primary
@Bean("primaryDataSource")
@Qualifier(value = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary.hikari")
public HikariDataSource primaryDataSource() {
return primaryDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
/**
* @Author: lequal
* @Description 配置事務管理器
* @Date 2022/12/22 16:48
* @param dataSource
* @return org.springframework.transaction.PlatformTransactionManager
*/
@Bean(name = "primaryTransactionManager")
@Primary
public PlatformTransactionManager primaryTransactionManager(@Qualifier("primaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
/**
* @Author: lequal
* @Description 自定義SQLSession工廠
* @Date 2022/12/22 16:50
* @param dataSource
* @return org.apache.ibatis.session.SqlSessionFactory
*/
@Primary
@Bean(name = "primarySqlSessionFactory")
public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
final SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(PrimaryDataSourceConfiguration.MAPPER_LOCATION));
return sessionFactoryBean.getObject();
}
/**
* @Author: lequal
* @Description 創(chuàng)建JDBC模板
* @Date 2022/12/22 16:48
* @param dataSource
* @return org.springframework.jdbc.core.JdbcTemplate
*/
@Bean(name = "primaryJdbcTemplate")
public JdbcTemplate primaryJdbcTemplate(@Qualifier("primaryDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
- SecondaryDataSourceConfiguration.class
import com.zaxxer.hikari.HikariDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
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.core.JdbcTemplate;
import javax.sql.DataSource;
/**
* @ClassName: HikariDataSourceConfiguration
* @Author: lequal
* @Date: 2022/12/22
* @Description: 副數據源配置
*/
@Configuration
@MapperScan(basePackages = "com.market.mapper2", sqlSessionFactoryRef = "secondarySqlSessionFactory")
public class SecondaryDataSourceConfiguration {
/**
* 指定mapper xml文件路徑
*/
public static final String MAPPER_LOCATION = "classpath:mapper2/*.xml";
/**
* @Author: lequal
* @Description 獲取一級的屬性
* @Date 2022/12/22 16:48
* @return org.springframework.boot.autoconfigure.jdbc.DataSourceProperties
*/
@ConfigurationProperties("spring.datasource.secondary")
public DataSourceProperties secondaryDataSourceProperties() {
return new DataSourceProperties();
}
/**
* @Author: lequal
* @Description 獲取下一級的屬性(hikari)并創(chuàng)建數據源
* @Date 2022/12/22 16:48
* @return com.zaxxer.hikari.HikariDataSource
*/
@Bean("secondaryDataSource")
@Qualifier(value = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary.hikari")
public HikariDataSource secondaryDataSource() {
return secondaryDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
/**
* @Author: lequal
* @Description 配置事務管理器
* @Date 2022/12/22 16:48
* @param dataSource
* @return org.springframework.transaction.PlatformTransactionManager
*/
@Bean(name = "secondaryTransactionManager")
public PlatformTransactionManager secondaryTransactionManager(@Qualifier("secondaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "secondarySqlSessionFactory")
public SqlSessionFactory secondarySqlSessionFactory(@Qualifier("secondaryDataSource") DataSource dataSource) throws Exception {
final SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(SecondaryDataSourceConfiguration.MAPPER_LOCATION));
return sessionFactoryBean.getObject();
}
/**
* @Author: lequal
* @Description 創(chuàng)建JDBC模板
* @Date 2022/12/22 16:48
* @param dataSource
* @return org.springframework.jdbc.core.JdbcTemplate
*/
@Bean(name = "secondaryJdbcTemplate")
public JdbcTemplate secondaryJdbcTemplate(@Qualifier("secondaryDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
說明一下:
之前我們使用@Transactional的時候,并沒有通過value或者transactionManager設置事務管理器,這是為什么?
這是因為我們在spring容器中只定義了一個事務管理器(實現TransactionManagementConfigurer接口的annotationDrivenTransactionManager()方法,該方法返回的事務管理器就是系統(tǒng)默認使用的),spring啟動事務的時候,默認會按類型在容器中查找事務管理器,剛好容器中只有一個,就拿過來用了,如果有多個的時候,如果你不指定,spring是不知道具體要用哪個事務管理器的。
使用事務時大概是這樣子: @Transactional(transactionManager = “transactionManager1”, propagation = Propagation.REQUIRED),聲明式事務中自己指定需要使用的事務管理器,就是我們剛剛自己手動給每個數據源配置的事務管理器。
假如,系統(tǒng)中有多個事務管理器,你需要系統(tǒng)指定其中的一個事務管理器是默認的,那么只需要自己寫一個配置類實現TransactionManagementConfigurer接口,并覆寫**annotationDrivenTransactionManager()**即可。
源碼如下:
package org.springframework.transaction.annotation;
import org.springframework.transaction.TransactionManager;
public interface TransactionManagementConfigurer {
TransactionManager annotationDrivenTransactionManager();
}
示例:
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;
import javax.annotation.Resource;
/**
* @ClassName: CustomTransactionManagement
* @Author: lequal
* @Date: 2023/04/10
* @Description: 系統(tǒng)啟動時注入默認的事務管理器
*/
@Configuration
public class CustomTransactionManagement implements TransactionManagementConfigurer {
// 假設自己需要把主數據源配置的事務管理器作為默認的,也可以直接在PrimaryDataSourceConfiguration類實現接口
@Resource(name = "primaryTransactionManager")
private PlatformTransactionManager txManager;
/**
* @Author: lequal
* @Description 其返回值代表在擁有多個事務管理器的情況下默認使用的事務管理器
* @Date 2023/04/10 8:55
* @return org.springframework.transaction.TransactionManager
*/
@Override
public TransactionManager annotationDrivenTransactionManager() {
return txManager;
}
}
如果事務不生效,自己手動開啟事務支持:@EnableTransactionManagement,默認情況下是自動裝配的時候配置了事務支持的,詳情請查看 TransactionAutoConfiguration這個類的源碼文章來源:http://www.zghlxwxcb.cn/news/detail-446518.html
至于你后面獲取到不同的數據源如何操作取決于你自己了.文章來源地址http://www.zghlxwxcb.cn/news/detail-446518.html
到了這里,關于Spring Boot 2.7.5 HikariCP 連接池多數據源配置的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!