SpringBoot實現(xiàn)多數(shù)據(jù)源的切換實踐
前言
我們在進行軟件開發(fā)的過程中,剛開始的時候因為無法估量系統(tǒng)后期的訪問量和并發(fā)量,所以一開始會采用單體架構(gòu),后期如果網(wǎng)站流量變大, 并發(fā)量變大,那么就可能會將架構(gòu)擴展為微服務(wù)架構(gòu),各個微服務(wù)對應(yīng)一個數(shù)據(jù)庫,不過這樣的成本就有點大了,可能只是有些模塊用的人比較多, 有些模塊沒什么人用,如果都進行服務(wù)拆分,其實也沒那個必要,如果有些模塊用的人比較多,那么我們可以采用讀寫分離來減輕壓力,這樣的話, 可以在一定程度上提升系統(tǒng)的用戶體驗,不過這只是在數(shù)據(jù)庫的I/O上面做方案,如果系統(tǒng)的壓力很大,那么肯定要做負載均衡,我們今天就先說 實現(xiàn)數(shù)據(jù)庫的讀寫分離。我們要在代碼層面實現(xiàn)數(shù)據(jù)庫的讀寫分離,那么核心就是數(shù)據(jù)源的切換,本文基于AOP來實現(xiàn)數(shù)據(jù)源的切換。
工程結(jié)構(gòu)
└─com └─steak └─transaction │ TransactionDemoApplication.java │ ├─datasource │ │ DatasourceChooser.java │ │ DatasourceConfiguration.java │ │ DatasourceContext.java │ │ DatasourceScope.java │ │ DynamicDatasourceAspect.java │ │ │ └─properties │ DynamicDatasourceProperties.java │ MainDatasourceProperties.java │ ├─execute │ PlaceOrderExecute.java │ ├─rest │ PlaceOrderApi.java │ ├─result │ R.java │ └─service IntegralService.java OrderService.java StockService.java
在下面的實現(xiàn)中,我們一個有三個數(shù)據(jù)源,其中有一個是默認的,如果不指定具體的數(shù)據(jù)源,那么就使用默認的,我們是基于申明式的方式來切換 數(shù)據(jù)源,只需要在具體的接口上加上注解便能實現(xiàn)數(shù)據(jù)源的切換。
編碼實現(xiàn)
yml文件
主數(shù)據(jù)源直接使用spring的配置,其他數(shù)據(jù)源采用自定義的方式,這里采用一個map結(jié)構(gòu)來定義,方便進行解析,可以在yml文件里進行添加多個,在代碼 邏輯層面不用改動。
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource druid: username: root password: 123456 url: jdbc:mysql://127.0.0.1:3306/db?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC driver-class-name: com.mysql.cj.jdbc.Driver dynamic: datasource: { slave1: { username: 'root', password: '123456', url: ' url: jdbc:mysql://127.0.0.1:3306/db?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC', driver-class-name: 'com.mysql.cj.jdbc.Driver' }, slave2: { username: 'root', password: '123456', url: ' url: jdbc:mysql://127.0.0.1:3306/db?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC', driver-class-name: 'com.mysql.cj.jdbc.Driver' } }
主數(shù)據(jù)源MainDatasourceProperties
對于主數(shù)據(jù)源,我們單獨拿出來放在一個類里面,不過其實也可以放到dynamic里面,只是需要做一定的處理,我們就簡單的放幾個連接屬性。
/** * @author 劉牌 * @date 2022/3/220:14 */ @Component @ConfigurationProperties(prefix = "spring.datasource.druid") @Data public class MainDatasourceProperties { private String username; private String password; private String url; private String driverClassName; }
其他數(shù)據(jù)源DynamicDatasourceProperties
其他數(shù)據(jù)源使用一個Map來接受yml文件中的數(shù)據(jù)源配置
/** * @author 劉牌 * @date 2022/3/213:47 */ @Component @ConfigurationProperties(prefix = "dynamic") @Data public class DynamicDatasourceProperties { private Map<String,Map<String,String>> datasource; }
數(shù)據(jù)源配置類DatasourceConfiguration
配置類中主要對DataSource進行配置,主數(shù)據(jù)源我們按照正常的bean來定義連接屬性,而其他數(shù)據(jù)數(shù)據(jù)源則使用反射的方式來進行連接屬性的 配置,因為主數(shù)據(jù)源一般是不會變動的,但是其他數(shù)據(jù)源可能會發(fā)生變動,可能會添加,這時候如果通過硬編碼取配置,那么每增加一個數(shù)據(jù)源,就需要 增加一個配置,顯然不太行,所以就使用反射來進行賦值。
/** * @author 劉牌 * @date 2022/3/111:34 */ @Configuration @AllArgsConstructor public class DatasourceConfiguration { final MainDatasourceProperties mainDatasourceProperties; final DynamicDatasourceProperties dynamicDatasourceProperties; @Bean public DataSource datasource(){ Map<Object,Object> datasourceMap = new HashMap<>(); DatasourceChooser datasourceChooser = new DatasourceChooser(); /** * main database */ DruidDataSource mainDataSource = new DruidDataSource(); mainDataSource.setUsername(mainDatasourceProperties.getUsername()); mainDataSource.setPassword(mainDatasourceProperties.getPassword()); mainDataSource.setUrl(mainDatasourceProperties.getUrl()); mainDataSource.setDriverClassName(mainDatasourceProperties.getDriverClassName()); datasourceMap.put("main",mainDataSource); /** * other database */ Map<String, Map<String, String>> sourceMap = dynamicDatasourceProperties.getDatasource(); sourceMap.forEach((datasourceName,datasourceMaps) -> { DruidDataSource dataSource = new DruidDataSource(); datasourceMaps.forEach((K,V) -> { String setField = "set" + K.substring(0, 1).toUpperCase() + K.substring(1); //轉(zhuǎn)換yml文件中帶有-符號的屬性 String[] strings = setField.split(""); StringBuilder newStr = new StringBuilder(); for (int i = 0; i < strings.length; i++) { if (strings[i].equals("-")) strings[i + 1] = strings[i + 1].toUpperCase(); if (!strings[i].equals("-")) newStr.append(strings[i]); } try { DruidDataSource.class.getMethod(newStr.toString(),String.class).invoke(dataSource,V); } catch (Exception e) { e.printStackTrace(); } }); datasourceMap.put(datasourceName,dataSource); }); //設(shè)置目標(biāo)數(shù)據(jù)源 datasourceChooser.setTargetDataSources(datasourceMap); //設(shè)置默認數(shù)據(jù)源 datasourceChooser.setDefaultTargetDataSource(mainDataSource); return datasourceChooser; } }
上面使用數(shù)據(jù)源配置類中使用反射對其他數(shù)據(jù)源進行連接屬性的設(shè)置,然后設(shè)置目標(biāo)數(shù)據(jù)源和默認數(shù)據(jù)源,里面有一個DatasourceChooser
。
DatasourceChooser
DatasourceChooser
繼承自AbstractRoutingDataSource
,AbstractRoutingDataSource
可以實現(xiàn)數(shù)據(jù)源的切換,它里面的 determineCurrentLookupKey()
方法需要我們返回一個數(shù)據(jù)源的名稱,它會自動給我們匹配上數(shù)據(jù)源。
/** * @author 劉牌 * @date 2022/3/112:21 */ public class DatasourceChooser extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DatasourceContext.getDatasource(); } }
如下是AbstractRoutingDataSource
的部分源碼,我們可以看出數(shù)據(jù)源是一個Map結(jié)構(gòu),可以通過數(shù)據(jù)源名稱查找到對應(yīng)的數(shù)據(jù)源。
package org.springframework.jdbc.datasource.lookup; public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean { @Nullable private Map<Object, DataSource> resolvedDataSources; public AbstractRoutingDataSource() { } protected DataSource determineTargetDataSource() { Object lookupKey = this.determineCurrentLookupKey(); DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey); } @Nullable protected abstract Object determineCurrentLookupKey(); }
DatasourceContext
DatasourceContext
內(nèi)部是一個ThreadLocal
,主要是用來存儲每一個線程的數(shù)據(jù)源名稱和獲取數(shù)據(jù)源名稱,而數(shù)據(jù)源的名稱我們用過AOP切面 來獲取。
/** * @author 劉牌 * @date 2022/3/112:22 */ public class DatasourceContext { private static final ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void setDatasource(String key){ threadLocal.set(key); } public static String getDatasource(){ return threadLocal.get(); } }
數(shù)據(jù)源注解DatasourceScope
DatasourceScope
標(biāo)準(zhǔn)在方法上面,通過scope
來指定數(shù)據(jù)源,不指定默認為主數(shù)據(jù)源main
。
/** * @author 劉牌 * @date 2022/3/111:22 */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DatasourceScope { String scope() default "main"; }
數(shù)據(jù)源切面DynamicDatasourceAspect
我們在訪問每一個帶有DatasourceScope
注解的方法時,都會經(jīng)過數(shù)據(jù)源切面DynamicDatasourceAspect
,獲取到注解上面的 scope
的值后,通過DatasourceContext
設(shè)置數(shù)據(jù)源名稱,便可實現(xiàn)對數(shù)據(jù)源的切換。
/** * @author 劉牌 * @date 2022/3/111:34 */ @Aspect @Component public class DynamicDatasourceAspect { @Pointcut("@annotation(dataSourceScope)") public void dynamicPointcut(DatasourceScope dataSourceScope){} @Around(value = "dynamicPointcut(dataSourceScope)", argNames = "joinPoint,dataSourceScope") public Object dynamicAround(ProceedingJoinPoint joinPoint , DatasourceScope dataSourceScope) throws Throwable { String scope = dataSourceScope.scope(); DatasourceContext.setDatasource(scope); return joinPoint.proceed(); } }
使用
只需要在具體的方法上面標(biāo)注數(shù)據(jù)源注解@DatasourceScope
,并指定scope
的值,便可實現(xiàn)切換,如果不指定,那么就使用主數(shù)據(jù)源。
/** * @author 劉牌 * @date 2022/3/19:49 */ @Service @AllArgsConstructor public class OrderService { private JdbcTemplate jdbcTemplate; @DatasourceScope(scope = "slave1") public R saveOrder(Integer userId , Integer commodityId){ String sql = "INSERT INTO `order`(user_id,commodity_id) VALUES("+userId+","+commodityId+")"; jdbcTemplate.execute(sql); return R.builder().code(200).msg("save order success").build(); } }
到此這篇關(guān)于SpringBoot實現(xiàn)多數(shù)據(jù)源的切換實踐的文章就介紹到這了,更多相關(guān)SpringBoot多數(shù)據(jù)源切換內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- springboot dynamic多數(shù)據(jù)源demo以及常見切換、事務(wù)的問題
- Springboot實現(xiàn)多數(shù)據(jù)源切換詳情
- SpringBoot多數(shù)據(jù)源配置并通過注解實現(xiàn)動態(tài)切換數(shù)據(jù)源
- SpringBoot基于AbstractRoutingDataSource實現(xiàn)多數(shù)據(jù)源動態(tài)切換
- SpringBoot多數(shù)據(jù)源切換實現(xiàn)代碼(Mybaitis)
- SpringBoot?+DynamicDataSource切換多數(shù)據(jù)源的全過程
- springboot中mybatis多數(shù)據(jù)源動態(tài)切換實現(xiàn)
- Springboot如何設(shè)置多數(shù)據(jù)源,隨時切換
相關(guān)文章
mybatis 多表關(guān)聯(lián)mapper文件寫法操作
這篇文章主要介紹了mybatis 多表關(guān)聯(lián)mapper文件寫法操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12Java網(wǎng)絡(luò)編程TCP實現(xiàn)文件上傳功能
這篇文章主要為大家詳細介紹了Java網(wǎng)絡(luò)編程TCP實現(xiàn)文件上傳功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-07-07