tags: multi-datasource java springboot
一句話概括:Spring Boot開發中連接多個數據庫進行讀寫操作,使用多套數據源是最直接、簡單的方式。
1. 引言
在開發過程中,避免不了需要同時操作多個數據庫的情況,通常的應用場景如下 :
- 數據庫高性能場景:主從,包括一主一從,一主多從等,在主庫進行增刪改操作,在從庫進行讀操作。
- 數據庫高可用場景:主備,包括一往一備,多主多備等,在數據庫無法訪問時可以切換。
- 同構或異構數據的業務處理:需要處理的數據存儲在不同的數據庫中,包括同構(如都是 MySQL ),異構(如一個MySQL ,另外是 PG 或者 Oracle )。
使用 Spring Boot 該如何處理多個數據庫的讀寫,一般有以下幾種策略:
- 多套數據源:即針對一個數據庫建立一套數據處理邏輯,每套數據庫都包括數據源配置、會話工廠( sessionFactory )、連接、SQL 操作、實體。各套數據庫相互獨立。
- 動態數據源:確定數量的多個數據源共用一個會話工廠,根據條件動態選取數據源進行連接、SQL 操作。
- 參數化變更數據源:根據參數添加數據源,並進行數據源切換,數據源數量不確定。通常用於對多個數據庫的管理工作。
本系列文章“搞定SpringBoot多數據源”將針對以上幾個策略進行描述,本文是第一篇:“多套數據源”,主要以主從場景為實例,結合代碼,對多套數據源的實現進行描述,內容包括搭建 Spring Boot + MyBatis Plus 工程、多數據源配置、多數據源處理與使用邏輯。
本文所涉及到的示例代碼:https://github.com/mianshenglee/my-example/tree/master/multi-datasource
,讀者可結合一起看。
2. 運行環境
- JAVA 運行環境:
JDK1.8
- Spring Boot:
2.2.2.RELEASE
- MyBatis Plus:
3.3.0
- 開發IDE:
IDEA
- 構建工具Maven:
3.3.9
- MySQL :
5.6.26
- Lombok:
1.18.10
3. 多套數據源
多套數據源,顧名思義每一個數據庫有一套獨立的操作。從下往上,數據庫、會話工廠、DAO操作,服務層都是獨立的一套,如下所示:
本示例中,以一主一從兩個數據庫,兩數據庫的分別有一個表 test_user
,表結構一致,為便於說明,兩個表中的數據是不一樣的。兩個表結構可在示例代碼中的 sql
目錄中獲取。
3.1 搭建 Spring Boot 工程
3.1.1 初始化 Spring Boot 工程
使用 spring.io構建初始 Spring Boot 工程,選用以下幾個構件:
- Lombok: 用於簡化操作
- Spring Configuration Processor:配置文件處理器
- Spring Web: 用於構建web服務
- MySQL Driver: 數據庫驅動
3.1.2 添加 MyBatis Plus 依賴
MyBatis Plus 是對 MyBatis 增強,簡化 DAO 操作,提高數據庫操作效率。依賴如下:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.0</version>
</dependency>
3.1.3 添加包結構
主要添加以下幾個包:
├─config ---------------------------------- // 數據源配置
├─controller ------------------------------ // web服務
├─entity ---------------------------------- // 實體類
│ ├─master
│ └─slave
├─mapper ---------------------------------- // dao操作類
│ ├─master
│ └─slave
└─vo -------------------------------------- // 視圖返回對象
注:
- 由於示例簡單,省略service層
- 實體類及mapper均根據主從進行划分
3.2 多套數據源
3.2.1 獨立數據庫連接信息
Spring Boot 的默認配置文件是 application.properties
,由於有兩個數據庫配置,獨立配置數據庫是好的實踐,因此添加配置文件 jbdc.properties
,添加以下自定義的主從數據庫配置:
# master
spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.master.jdbc-url=jdbc:mysql://localhost:3306/mytest?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8
spring.datasource.master.username=root
spring.datasource.master.password=111111
# slave
spring.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.slave.jdbc-url=jdbc:mysql://localhost:3306/my_test1?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8
spring.datasource.slave.username=root
spring.datasource.slave.password=111111
3.2.2 多套數據源配置
有了數據源連接信息,需要把數據源注入到 Spring 中。由於每個數據庫使用獨立的一套數據庫連接,數據庫連接使用的 SqlSession
進行會話連接,SqlSession
是由SqlSessionFactory
生成。因此,需要分別配置SqlSessionFactory
。以下操作均在 config
目錄 下:
(1)添加 DataSourceConfig
配置文件,注入主從數據源
@Configuration
@PropertySource("classpath:config/jdbc.properties")
public class DatasourceConfig {
@Bean("master")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource(){
return DataSourceBuilder.create().build();
}
@Bean("slave")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource(){
return DataSourceBuilder.create().build();
}
}
- 注解
PropertySource
指定配置信息文件- 注解
ConfigurationProperties
指定主從配置前綴- 分別指定主從數據源的 bean 名稱為
master
,slave
(2)添加 MasterMybatisConfig
配置文件,注入 Master 的SqlSessionFactory
@Configuration
@MapperScan(basePackages = "me.mason.demo.basicmultidatasource.mapper.master", sqlSessionFactoryRef = "masterSqlSessionFactory")
public class MasterMybatisConfig {
/**
* 注意,此處需要使用MybatisSqlSessionFactoryBean,不是SqlSessionFactoryBean,
* 否則,使用mybatis-plus的內置函數時就會報invalid bound statement (not found)異常
*/
@Bean("masterSqlSessionFactory")
public SqlSessionFactory masterSqlSessionFactory(@Qualifier("master") DataSource dataSource) throws Exception {
// 設置數據源
MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
mybatisSqlSessionFactoryBean.setDataSource(dataSource);
//mapper的xml文件位置
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
String locationPattern = "classpath*:/mapper/master/*.xml";
mybatisSqlSessionFactoryBean.setMapperLocations(resolver.getResources(locationPattern));
//對應數據庫的entity位置
String typeAliasesPackage = "me.mason.demo.basicmultidatasource.entity.master";
mybatisSqlSessionFactoryBean.setTypeAliasesPackage(typeAliasesPackage);
return mybatisSqlSessionFactoryBean.getObject();
}
}
- 注解
MapperScan
指定那些包下的mapper
使用本數據源,並指定使用哪個SqlSessionFactory
,注意,此處的sqlSessionFactoryRef
即本配置中的注入的SqlSessionFactory
。- 設置指定的數據源,此處是名為
master
的數據源,使用Qualifier
指定。- MyBatis Plus 對應的 Mapper 若有自定義的 mapper.xml, 則使用
setMapperLocations
指定。- 若需要對實體進行別名處理,則使用
setTypeAliasesPackage
指定。
(3)添加 SlaveMybatisConfig
配置文件,注入Slave 的 SqlSessionFactory
與(2)一致,把 master 改為 slave即可。
3.2.3 多套實體
在 MyBatis 配置中,實體設置 typeAliases
可以簡化 xml 的配置,前面提到,使用 typeAliasesPackage
設置實體路徑,在 entity
包下分別設置 master
和 slave
包,存放兩個庫對應的表實體,使用 Lombok 簡化實體操作。如下:
@Data
@TableName("test_user")
public class MasterTestUser implements Serializable {
private static final long serialVersionUID = 1L;
/** id */
private Long id;
/** 姓名 */
private String name;
...
}
3.2.4 多套Mapper操作
在 mapper
包下,分別添加 master
和 slave
包,存放兩個庫對應的 Mapper ,由於 MyBatis Plus 本身已包含基本的 CRUD 操作,所以很多時候可以不用 xml 文件配置。若需要自定義操作,需要結合 xml文件,與此同時需要指定對應的 xml 文件所在目錄。如下:
@Repository
public interface MasterTestUserMapper extends BaseMapper<MasterTestUser> {
/**
* 自定義查詢
* @param wrapper 條件構造器
*/
List<MasterTestUser> selectAll(@Param(Constants.WRAPPER)Wrapper<MasterTestUser> wrapper);
}
slave對應的Mapper與此類似
3.2.5 多套 mapper xml 文件
MyBatis Plus 的默認mapper xml 文件路徑為 classpath*:/mapper/**/*.xml
,即 resources/mapper
下,同樣設置 master
及 slave
目錄,分別存放對應的mapper xml 文件。以下是 master 的自定義操作:
<mapper namespace="me.mason.demo.basicmultidatasource.mapper.master.MasterTestUserMapper">
<select id="selectAll" resultType="masterTestUser">
select * from test_user
<if test="ew!=null">
${ew.customSqlSegment}
</if>
</select>
</mapper>
3.3 多數據源使用
經過上面的多套數據源配置,可知道,若需要操作哪個數據庫,直接使用對應的 mapper 進行 CRUD 操作即可。如下為 Controller 中分別查詢兩個庫,獲取到的數據合在一起返回:
@RestController
@RequestMapping("/user")
public class TestUserController {
@Autowired
private MasterTestUserMapper masterTestUserMapper;
@Autowired
private SlaveTestUserMapper slaveTestUserMapper;
/**
* 查詢全部
*/
@GetMapping("/listall")
public Object listAll() {
//master庫,自定義接口查詢
QueryWrapper<MasterTestUser> queryWrapper = new QueryWrapper<>();
List<MasterTestUser> resultData = masterTestUserMapper.selectAll(queryWrapper.isNotNull("name"));
//slave庫,mp內置接口
List<SlaveTestUser> resultDataSlave = slaveTestUserMapper.selectList(null);
//返回
Map<String, Object> result = new HashMap<>();
result.put("master" , resultData);
result.put("slave" , resultDataSlave);
return ResponseResult.success(result);
}
}
- 使用Autowired注解注入對應的mapper
- 使用對應數據庫的 mapper 進行業務操作
- 根據業務在數據庫中執行相應的操作,如主只做增刪改操作、從只讀操作
至此,多數據源的實現已完成,當前示例是兩個同構的數據庫,當然,若是異構的數據庫,或者多於兩個的數據庫,處理方式是一樣的,只不過是把數據源增加一套而已。
4. 優缺點
由上述說明,我們可以總結一下使用多套數據源的方法進行多數據庫操作,它的優缺點是什么。
4.1 優點
- 簡單、直接:一個庫對應一套處理方式,很好理解。
- 符合開閉原則( OCP ):開發的設計模式告訴我們,對擴展開放,對修改關閉,添加多一個數據庫,原來的那一套不需要改動,只添加即可。
4.2 缺點
- 資源浪費:針對每一個數據源寫一套操作,連接數據庫的資源也是獨立的,分別占用同樣多的資源。
SqlSessionFactory
是一個工廠,建議是使用單例,完全可以重用,不需要建立多個,只需要更改數據源即可,跟多線程,使用線程池減少資源消耗是同一道理。 - 代碼冗余:在前面的多數據源配置中可以看出,其實 master 和 slave 的很多操作是一樣的,只是改個名稱而已,因此會造成代碼冗余。
- 缺乏靈活:所有需要使用的地方都需要引入對應的mapper,對於很多操作,只是選擇數據源的不一樣,代碼邏輯是一致的。另外,對於一主多從的情況,若需要對多個從庫進行負載均衡,相對比較麻煩。
正因為有上述的缺點,所以還有改進的空間。於是就有了動態數據源,至於動態數據源如何實現,下回分解。
5. 總結
本文對多個數據庫的操作進行了初步探討,並對使用多套源的策略進行講解,結合主從代碼示例,搭建了 Spring Boot + MyBatis Plus 工程,配置多數據源及使用 Mapper 進行多數據源操作,最后對多套數據源的優缺點進行總結。希望小伙伴們可以對多數據源操作有個初步印象。
本文有配套的示例代碼,有興趣的可以跑一下示例來感受一下。
參考資料
- Spring主從數據庫的配置和動態數據源切換原理:
https://www.liaoxuefeng.com/article/1182502273240832
- 多數據源與動態數據源的權衡:
https://juejin.im/post/5b790a866fb9a019ea01f38c
- 談談Spring Boot 數據源加載及其多數據源簡單實現:
https://juejin.im/post/5cb0023d5188250df17d4ffc
- Spring Boot 和 MyBatis 實現多數據源、動態數據源切換:
https://juejin.im/post/5a927d23f265da4e7e10d740
往期文章
- java開發必學知識:動態代理
- 2019 讀過的好書推薦
- springboot+apache前后端分離部署https
- springboot+logback 日志輸出企業實踐(下)
- springboot+logback 日志輸出企業實踐(上)
我的公眾號(搜索Mason技術記錄
),獲取更多技術記錄: