<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
我們在進行軟體開發的過程中,剛開始的時候因為無法估量系統後期的存取量和並行量,所以一開始會採用單體架構,後期如果網站流量變大, 並行量變大,那麼就可能會將架構擴充套件為微服務架構,各個微服務對應一個資料庫,不過這樣的成本就有點大了,可能只是有些模組用的人比較多, 有些模組沒什麼人用,如果都進行服務拆分,其實也沒那個必要,如果有些模組用的人比較多,那麼我們可以採用讀寫分離來減輕壓力,這樣的話, 可以在一定程度上提升系統的使用者體驗,不過這只是在資料庫的I/O上面做方案,如果系統的壓力很大,那麼肯定要做負載均衡,我們今天就先說 實現資料庫的讀寫分離。我們要在程式碼層面實現資料庫的讀寫分離,那麼核心就是資料來源的切換,本文基於AOP來實現資料來源的切換。
└─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
在下面的實現中,我們一個有三個資料來源,其中有一個是預設的,如果不指定具體的資料來源,那麼就使用預設的,我們是基於申明式的方式來切換 資料來源,只需要在具體的介面上加上註解便能實現資料來源的切換。
主資料來源直接使用spring的設定,其他資料來源採用自定義的方式,這裡採用一個map結構來定義,方便進行解析,可以在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' } }
對於主資料來源,我們單獨拿出來放在一個類裡面,不過其實也可以放到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; }
其他資料來源使用一個Map來接受yml檔案中的資料來源設定
/** * @author 劉牌 * @date 2022/3/213:47 */ @Component @ConfigurationProperties(prefix = "dynamic") @Data public class DynamicDatasourceProperties { private Map<String,Map<String,String>> datasource; }
設定類中主要對DataSource進行設定,主資料來源我們按照正常的bean來定義連線屬性,而其他資料資料來源則使用反射的方式來進行連線屬性的 設定,因為主資料來源一般是不會變動的,但是其他資料來源可能會發生變動,可能會新增,這時候如果通過寫死取設定,那麼每增加一個資料來源,就需要 增加一個設定,顯然不太行,所以就使用反射來進行賦值。
/** * @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); //轉換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); }); //設定目標資料來源 datasourceChooser.setTargetDataSources(datasourceMap); //設定預設資料來源 datasourceChooser.setDefaultTargetDataSource(mainDataSource); return datasourceChooser; } }
上面使用資料來源設定類中使用反射對其他資料來源進行連線屬性的設定,然後設定目標資料來源和預設資料來源,裡面有一個DatasourceChooser
。
DatasourceChooser
繼承自AbstractRoutingDataSource
,AbstractRoutingDataSource
可以實現資料來源的切換,它裡面的 determineCurrentLookupKey()
方法需要我們返回一個資料來源的名稱,它會自動給我們匹配上資料來源。
/** * @author 劉牌 * @date 2022/3/112:21 */ public class DatasourceChooser extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DatasourceContext.getDatasource(); } }
如下是AbstractRoutingDataSource
的部分原始碼,我們可以看出資料來源是一個Map結構,可以通過資料來源名稱查詢到對應的資料來源。
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
內部是一個ThreadLocal
,主要是用來儲存每一個執行緒的資料來源名稱和獲取資料來源名稱,而資料來源的名稱我們用過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(); } }
DatasourceScope
標準在方法上面,通過scope
來指定資料來源,不指定預設為主資料來源main
。
/** * @author 劉牌 * @date 2022/3/111:22 */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DatasourceScope { String scope() default "main"; }
我們在存取每一個帶有DatasourceScope
註解的方法時,都會經過資料來源切面DynamicDatasourceAspect
,獲取到註解上面的 scope
的值後,通過DatasourceContext
設定資料來源名稱,便可實現對資料來源的切換。
/** * @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(); } }
只需要在具體的方法上面標註資料來源註解@DatasourceScope
,並指定scope
的值,便可實現切換,如果不指定,那麼就使用主資料來源。
/** * @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(); } }
到此這篇關於SpringBoot實現多資料來源的切換實踐的文章就介紹到這了,更多相關SpringBoot多資料來源切換內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45