springboot+dynamic-datasource は、複数のデータソースの動的切り替えを実装します。非 @DS アノテーション方式です。

springboot+dynamic-datasource は、複数のデータソースの動的切り替えを実装します(アノテーションなし)

I.はじめに

最近、 SaaS プラットフォームの機能を分析しています多租户が、これにはデータベース部分の機能が関与する必要があります。マルチテナント設計スキームでは、テナント分離データとテナント共有データを考慮する必要があります。共有データは実装が簡単ですが、分離データは比較的実装が困難です。一般に、分離性とスケーラビリティテナントのコスト、運用と保守複雑さを考慮する必要があります通常、SaaS マルチテナント データ ストレージには 3 つの主要なソリューションがあります。

  1. 独立したデータベース: テナントごとに 1 つのデータベース。
  2. 共有データベース、分離されたデータ アーキテクチャ: 共有されていますdatabaseが、複数またはすべてのテナントtenantによって異なりますschema
  3. 共有データベース、共有データ アーキテクチャ: テナントは、テーブル内のテナントのデータを区別することで、1 つdatabase、1 つを共有します。schematenantID

注: マルチデータ関数は日常業務に関与しているため、今日は最初のソリューションを共有します。これはマルチテナント アプリケーション シナリオにのみ適用できるものではありません。

2. 企画のアイデア

ここに画像の説明を挿入します
詳細:
1) テナント管理データベースからテナントデータベースの接続情報を照会する;
2) テナントの構成に応じて、該当するテナントデータベースの情報を動的に追加および削除する; 3)
テナントのフィールドに応じてclientId使用されるデータベース接続を判断するリクエストヘッダー非@DS注解方式

3. コードの実装

pom.xml

<!--mybatis-plus的springboot支持-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.6</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>

注:dynamic-datasourceマルチデータ ソース機能は、dynamic-datasource使用例でより頻繁に実装されています@DS注解。今日の例では、データ ソースを動的に切り替えるための aop の使用を共有しています。

コントローラー.java

@RestController
@AllArgsConstructor
public class TestController {
    
    

    private final UserService userService;

    @GetMapping("/test/list")
    public List<User> getUserList(){
    
    
        return userService.queryAll();
    }


}

テスト機能インターフェイス、Bean、Dao は例ではなくなりました

アプリケーション.yml

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启sql日志
    map-underscore-to-camel-case: true
    # 该配置就是将带有下划线的表字段映射为驼峰格式的实体类属性
spring:
  # 配置时区
  jackson:
    time-zone: GMT+8
  # 数据源相关配置
  datasource:
    dynamic:
      # 主数据源
      primary: db1
      datasource:
        # 数据源1
        db1:
          type: com.alibaba.druid.pool.DruidDataSource
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8
          username: root
          password: admin_123
      # druid 全局配置
      druid:
        initial-size: 5
        min-idle: 5
        max-active: 20
        max-wait: 60000
        time-between-eviction-runs-millis: 60000
        min-evictable-idle-time-millis: 30000
        validation-query: SELECT 1 FROM DUAL
        test-while-idle: true
        test-on-borrow: true
        test-on-return: false
server:
  port: 8089

SourceConstants.java

package com.sk.common;

public class SourceConstants {
    
    

    /** 数据源查询sql */
    public static final String SELECT_SOURCE = "select s.slave, s.username, s.password, s.url, s.driver_class_name from te_source s where s.status = 0 and s.del_flag = 0";

    /** 数据源字段 */
    public enum Details {
    
    
        SLAVE("slave", "数据源编码"),
        USERNAME("username", "用户名"),
        PASSWORD("password", "密码"),
        URL_PREPEND("url_prepend", "连接地址"),
        URL("url", "连接地址"),
        URL_APPEND("url_append", "连接参数"),
        DRIVER_CLASS_NAME("driver_class_name", "驱动");
        private final String code;
        private final String info;
        Details(String code, String info) {
    
    
            this.code = code;
            this.info = info;
        }
        public String getCode() {
    
    
            return code;
        }
        public String getInfo() {
    
    
            return info;
        }
    }
}

一定の構成

SourceProperties.java

@Data
@Configuration
@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.db1")
public class SourceProperties {
    
    

    /** 数据源驱动 */
    private String driverClassName;

    /** 数据源路径 */
    private String url;

    /** 数据源账号 */
    private String username;

    /** 数据源密码 */
    private String password;
    
}

データソース情報テーブル

DROP TABLE IF EXISTS `te_source`;
CREATE TABLE `te_source`  (
  `slave` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `driver_class_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `status` int(255) NULL DEFAULT NULL,
  `del_flag` int(255) NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of te_source
-- ----------------------------
INSERT INTO `te_source` VALUES ('db2', 'root', 'admin_123', 'jdbc:mysql://localhost:3306/test02?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8', 'com.mysql.cj.jdbc.Driver', 0, 0);
INSERT INTO `te_source` VALUES ('db3', 'root', 'admin_123', 'jdbc:mysql://localhost:3306/test03?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8', 'com.mysql.cj.jdbc.Driver', 0, 0);

DynamicDataSourceLoading.java

/**
 * 子数据源加载
 *
 * @author xueyi
 */
@Configuration
@AllArgsConstructor
public class DynamicDataSourceLoading {
    
    

    private final SourceProperties sourceProperties;

    @Bean
    public DynamicDataSourceProvider jdbcDynamicDataSourceProvider() {
    
    
        return new AbstractJdbcDataSourceProvider(sourceProperties.getDriverClassName(), sourceProperties.getUrl(), sourceProperties.getUsername(), sourceProperties.getPassword()) {
    
    
            @Override
            protected Map<String, DataSourceProperty> executeStmt(Statement statement) throws SQLException {
    
    
                ResultSet rs = statement.executeQuery(SourceConstants.SELECT_SOURCE);
                Map<String, DataSourceProperty> map = new HashMap<>();
                while (rs.next()) {
    
    
                    String name = rs.getString(Details.SLAVE.getCode());
                    String username = rs.getString(Details.USERNAME.getCode());
                    String password = rs.getString(Details.PASSWORD.getCode());
                    //String url = rs.getString(Details.URL_PREPEND.getCode()).concat(rs.getString(Details.URL_APPEND.getCode()));
                    String url = rs.getString(Details.URL.getCode());
                    String driver = rs.getString(Details.DRIVER_CLASS_NAME.getCode());
                    DataSourceProperty property = new DataSourceProperty();
                    property.setUsername(username);
                    property.setPassword(password);
                    property.setUrl(url);
                    property.setDriverClassName(driver);
                    map.put(name, property);
                }
                return map;
            }
        };
    }
}

すべてのデータ ソース情報をクエリし、各データベース接続をロードします

TransformDataSource.java

@Log4j2
@Aspect // FOR AOP
@Configuration // 配置类
public class TransformDataSource {
    
    

    @Pointcut("execution( * com.sk.controller..*.*(..))")
    /**
     * 这个方法的方法名要和下面注解方法名一致
     */
    public void doPointcut() {
    
    
    }

    @Before("doPointcut()")
    public void doBefore(JoinPoint joinPoint) {
    
    
        // 请求开始时间
        ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        String headValue = sra.getRequest().getHeader("clientId");
        log.info("--------------clientId:{}", headValue);
        DynamicDataSourceContextHolder.poll();
        DynamicDataSourceContextHolder.push(headValue);
    }

    @After("doPointcut()")
    public void doAfter() {
    
    
        System.out.println("==doAfter==");
    }

}

aop を使用して、リクエスト ヘッダーの clientId データに基づいて異なるデータベース接続を使用します。

リクエスト1
ここに画像の説明を挿入します
リクエスト2
ここに画像の説明を挿入します

おすすめ

転載: blog.csdn.net/weixin_37598243/article/details/131626784