spring boot + mybatis如何實(shí)現(xiàn)數(shù)據(jù)庫的讀寫分離
介紹
隨著業(yè)務(wù)的發(fā)展,除了拆分業(yè)務(wù)模塊外,數(shù)據(jù)庫的讀寫分離也是常見的優(yōu)化手段。
方案使用了AbstractRoutingDataSource和mybatis plugin來動(dòng)態(tài)的選擇數(shù)據(jù)源
選擇這個(gè)方案的原因主要是不需要改動(dòng)原有業(yè)務(wù)代碼,非常友好
注:
demo中使用了mybatis-plus,實(shí)際使用mybatis也是一樣的
demo中使用的數(shù)據(jù)庫是postgres,實(shí)際任一類型主從備份的數(shù)據(jù)庫示例都是一樣的
demo中使用了alibaba的druid數(shù)據(jù)源,實(shí)際其他類型的數(shù)據(jù)源也是一樣的
環(huán)境
首先,我們需要兩個(gè)數(shù)據(jù)庫實(shí)例,一為master,一為slave。
所有的寫操作,我們在master節(jié)點(diǎn)上操作
所有的讀操作,我們在slave節(jié)點(diǎn)上操作
需要注意的是:對(duì)于一次有讀有寫的事務(wù),事務(wù)內(nèi)的讀操作也不應(yīng)該在slave節(jié)點(diǎn)上,所有操作都應(yīng)該在master節(jié)點(diǎn)上
先跑起來兩個(gè)pg的實(shí)例,其中15432端口對(duì)應(yīng)的master節(jié)點(diǎn),15433端口對(duì)應(yīng)的slave節(jié)點(diǎn):
docker run \ --name pg-master \ -p 15432:5432 \ --env 'PG_PASSWORD=postgres' \ --env 'REPLICATION_MODE=master' \ --env 'REPLICATION_USER=repluser' \ --env 'REPLICATION_PASS=repluserpass' \ -d sameersbn/postgresql:10-2 docker run \ --name pg-slave \ -p 15433:5432 \ --link pg-master:master \ --env 'PG_PASSWORD=postgres' \ --env 'REPLICATION_MODE=slave' \ --env 'REPLICATION_SSLMODE=prefer' \ --env 'REPLICATION_HOST=master' \ --env 'REPLICATION_PORT=5432' \ --env 'REPLICATION_USER=repluser' \ --env 'REPLICATION_PASS=repluserpass' \ -d sameersbn/postgresql:10-2
實(shí)現(xiàn)
整個(gè)實(shí)現(xiàn)主要有3個(gè)部分:
- 配置兩個(gè)數(shù)據(jù)源
- 實(shí)現(xiàn)AbstractRoutingDataSource來動(dòng)態(tài)的使用數(shù)據(jù)源
- 實(shí)現(xiàn)mybatis plugin來動(dòng)態(tài)的選擇數(shù)據(jù)源
配置數(shù)據(jù)源
將數(shù)據(jù)庫連接信息配置到application.yml文件中
spring: mvc: servlet: path: /api datasource: write: driver-class-name: org.postgresql.Driver url: "${DB_URL_WRITE:jdbc:postgresql://localhost:15432/postgres}" username: "${DB_USERNAME_WRITE:postgres}" password: "${DB_PASSWORD_WRITE:postgres}" read: driver-class-name: org.postgresql.Driver url: "${DB_URL_READ:jdbc:postgresql://localhost:15433/postgres}" username: "${DB_USERNAME_READ:postgres}" password: "${DB_PASSWORD_READ:postgres}" mybatis-plus: configuration: map-underscore-to-camel-case: true
write寫數(shù)據(jù)源,對(duì)應(yīng)到master節(jié)點(diǎn)的15432端口
read讀數(shù)據(jù)源,對(duì)應(yīng)到slave節(jié)點(diǎn)的15433端口
將兩個(gè)數(shù)據(jù)源信息注入為DataSourceProperties:
@Configuration public class DataSourcePropertiesConfig { @Primary @Bean("writeDataSourceProperties") @ConfigurationProperties("datasource.write") public DataSourceProperties writeDataSourceProperties() { return new DataSourceProperties(); } @Bean("readDataSourceProperties") @ConfigurationProperties("datasource.read") public DataSourceProperties readDataSourceProperties() { return new DataSourceProperties(); } }
實(shí)現(xiàn)AbstractRoutingDataSource
spring提供了AbstractRoutingDataSource,提供了動(dòng)態(tài)選擇數(shù)據(jù)源的功能,替換原有的單一數(shù)據(jù)源后,即可實(shí)現(xiàn)讀寫分離:
@Component public class CustomRoutingDataSource extends AbstractRoutingDataSource { @Resource(name = "writeDataSourceProperties") private DataSourceProperties writeProperties; @Resource(name = "readDataSourceProperties") private DataSourceProperties readProperties; @Override public void afterPropertiesSet() { DataSource writeDataSource = writeProperties.initializeDataSourceBuilder().type(DruidDataSource.class).build(); DataSource readDataSource = readProperties.initializeDataSourceBuilder().type(DruidDataSource.class).build(); setDefaultTargetDataSource(writeDataSource); Map<Object, Object> dataSourceMap = new HashMap<>(); dataSourceMap.put(WRITE_DATASOURCE, writeDataSource); dataSourceMap.put(READ_DATASOURCE, readDataSource); setTargetDataSources(dataSourceMap); super.afterPropertiesSet(); } @Override protected Object determineCurrentLookupKey() { String key = DataSourceHolder.getDataSource(); if (key == null) { // default datasource return WRITE_DATASOURCE; } return key; } }
AbstractRoutingDataSource內(nèi)部維護(hù)了一個(gè)Map<Object, Object>的Map
在初始化過程中,我們將write、read兩個(gè)數(shù)據(jù)源加入到這個(gè)map
調(diào)用數(shù)據(jù)源時(shí):determineCurrentLookupKey()方法返回了需要使用的數(shù)據(jù)源對(duì)應(yīng)的key
當(dāng)前線程需要使用的數(shù)據(jù)源對(duì)應(yīng)的key,是在DataSourceHolder類中維護(hù)的:
public class DataSourceHolder { public static final String WRITE_DATASOURCE = "write"; public static final String READ_DATASOURCE = "read"; private static final ThreadLocal<String> local = new ThreadLocal<>(); public static void putDataSource(String dataSource) { local.set(dataSource); } public static String getDataSource() { return local.get(); } public static void clearDataSource() { local.remove(); } }
實(shí)現(xiàn)mybatis plugin
上面提到了當(dāng)前線程使用的數(shù)據(jù)源對(duì)應(yīng)的key,這個(gè)key需要在mybatis plugin根據(jù)sql類型來確定
MybatisDataSourceInterceptor類:
@Component @Intercepts({ @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}), @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})}) public class MybatisDataSourceInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { boolean synchronizationActive = TransactionSynchronizationManager.isSynchronizationActive(); if(!synchronizationActive) { Object[] objects = invocation.getArgs(); MappedStatement ms = (MappedStatement) objects[0]; if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) { DataSourceHolder.putDataSource(DataSourceHolder.READ_DATASOURCE); } } return invocation.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { } }
僅當(dāng)未在事務(wù)中,并且調(diào)用的sql是select類型時(shí),在DataSourceHolder中將數(shù)據(jù)源設(shè)為read
其他情況下,AbstractRoutingDataSource會(huì)使用默認(rèn)的write數(shù)據(jù)源
至此,項(xiàng)目已經(jīng)可以自動(dòng)的在讀、寫數(shù)據(jù)源間切換,無需修改原有的業(yè)務(wù)代碼
最后,提供demo使用依賴版本
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.7.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <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.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.2.2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.9</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatisplus-spring-boot-starter</artifactId> <version>1.0.5</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> <version>2.1.9</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.8.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.8.0</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.20</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
MyBatis將查詢出的兩列數(shù)據(jù)裝配成鍵值對(duì)的操作方法
這篇文章主要介紹了MyBatis將查詢出的兩列數(shù)據(jù)裝配成鍵值對(duì)的操作代碼,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-08-08SpringCloud2020整合Nacos-Bootstrap配置不生效的解決
這篇文章主要介紹了SpringCloud2020整合Nacos-Bootstrap配置不生效的解決,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01SpringBoot全局Controller返回值格式統(tǒng)一
本文主要介紹了SpringBoot全局Controller返回值格式統(tǒng)一,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-07-07SpringBoot替換默認(rèn)的tomcat服務(wù)器的方法
Tomcat是Apache基金下的一個(gè)輕量級(jí)的Servlet容器,支持Servlet和JSP,Tomcat具有Web服務(wù)器特有的功能,在SpringBoot框架中,我們使用最多的是Tomcat,這是SpringBoot默認(rèn)的容器技術(shù),本文給大家介紹了Spring?Boot如何替換默認(rèn)的tomcat服務(wù)器,需要的朋友可以參考下2024-08-08java Servlet 實(shí)現(xiàn)動(dòng)態(tài)驗(yàn)證碼圖片示例
這篇文章主要介紹了java Servlet 實(shí)現(xiàn)動(dòng)態(tài)驗(yàn)證碼圖片示例的資料,這里整理了詳細(xì)的代碼,有需要的小伙伴可以參考下。2017-02-02mybatis水平分表實(shí)現(xiàn)動(dòng)態(tài)表名的項(xiàng)目實(shí)例
本文主要介紹了mybatis水平分表實(shí)現(xiàn)動(dòng)態(tài)表名的項(xiàng)目實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07如何解決java獲取時(shí)間相差8小時(shí)的問題
最近使用new date()獲取的時(shí)間會(huì)和真實(shí)的本地時(shí)間相差8小時(shí)。本文就詳細(xì)的來介紹一下解決java獲取時(shí)間相差8小時(shí)的問題,感興趣的可以了解一下2021-09-09Spring AOP如何實(shí)現(xiàn)注解式的Mybatis多數(shù)據(jù)源切換詳解
這篇文章主要給大家介紹了關(guān)于Spring AOP如何實(shí)現(xiàn)注解式的Mybatis多數(shù)據(jù)源切換的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11