溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

SpringBoot怎么實現多數據源的切換

發布時間:2022-03-04 09:05:13 來源:億速云 閱讀:146 作者:iii 欄目:開發技術

這篇文章主要介紹“SpringBoot怎么實現多數據源的切換”,在日常操作中,相信很多人在SpringBoot怎么實現多數據源的切換問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”SpringBoot怎么實現多數據源的切換”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

    前言

    我們在進行軟件開發的過程中,剛開始的時候因為無法估量系統后期的訪問量和并發量,所以一開始會采用單體架構,后期如果網站流量變大, 并發量變大,那么就可能會將架構擴展為微服務架構,各個微服務對應一個數據庫,不過這樣的成本就有點大了,可能只是有些模塊用的人比較多, 有些模塊沒什么人用,如果都進行服務拆分,其實也沒那個必要,如果有些模塊用的人比較多,那么我們可以采用讀寫分離來減輕壓力,這樣的話, 可以在一定程度上提升系統的用戶體驗,不過這只是在數據庫的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

    在下面的實現中,我們一個有三個數據源,其中有一個是默認的,如果不指定具體的數據源,那么就使用默認的,我們是基于申明式的方式來切換 數據源,只需要在具體的接口上加上注解便能實現數據源的切換。

    編碼實現

    yml文件

    主數據源直接使用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'
        }
      }

    主數據源MainDatasourceProperties

    對于主數據源,我們單獨拿出來放在一個類里面,不過其實也可以放到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;
    }

    其他數據源DynamicDatasourceProperties

    其他數據源使用一個Map來接受yml文件中的數據源配置

    /**
     * @author 劉牌
     * @date 2022/3/213:47
     */
    @Component
    @ConfigurationProperties(prefix = "dynamic")
    @Data
    public class DynamicDatasourceProperties {
        private Map<String,Map<String,String>> datasource;
    }

    數據源配置類DatasourceConfiguration

    配置類中主要對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

    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

    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

    DatasourceScope標準在方法上面,通過scope來指定數據源,不指定默認為主數據源main。

    /**
     * @author 劉牌
     * @date 2022/3/111:22
     */
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface DatasourceScope {
        String scope() default "main";
    }

    數據源切面DynamicDatasourceAspect

    我們在訪問每一個帶有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怎么實現多數據源的切換”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女