springboot+dynamic-datasource realizes dynamic switching of multiple data sources, not @DS annotation method

springboot+dynamic-datasource realizes dynamic switching of multiple data sources, non-annotation

I. Introduction

Recently, I have been analyzing the functions of the SaaS platform多租户 , which must involve the functions of the database. Multi-tenant design solutions must consider tenant isolation data and tenant shared data. Shared data is easy to implement, but isolated data is relatively complicated. Generally, isolation and scalability must be considered . , tenant cost and operation and maintenance complexity ;
usually there are three main solutions for SaaS multi-tenant data storage:

  1. Independent database: one database per tenant.
  2. Shared database, isolated data schema: multiple or all tenants share database, but tenantdiffer schema.
  3. Shared database, shared data architecture: tenants share one database, one , and distinguish tenants' data schemain the table .tenantID

Note: Since multi-data functions are involved in daily work, today we share the first solution, which is not only suitable for multi-tenant application scenarios;

2. Plan ideas

Insert image description here
Details:
1) The tenant database connection information is queried from the tenant management database;
2) The corresponding tenant database information is dynamically added and deleted according to the tenant configuration; 3) The database connection used is determined
based on the fields in the tenant request header ;clientId非@DS注解方式

3. Code implementation

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>

Note: Using dynamic-datasourcethe function of implementing multiple data sources, in dynamic-datasourcethe usage samples, it is more about using @DS注解the implementation. Today's sample sharing uses aop to dynamically switch data sources

Controller.java

@RestController
@AllArgsConstructor
public class TestController {
    
    

    private final UserService userService;

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


}

Test functional interface, beans and dao are no longer examples

application.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;
        }
    }
}

constant configuration

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;
    
}

Data source information table

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;
            }
        };
    }
}

Query all data source information, load each database connection

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==");
    }

}

Use aop to use different database connections based on the clientId data in the request header

Request 1
Insert image description here
Request 2
Insert image description here

Guess you like

Origin blog.csdn.net/weixin_37598243/article/details/131626784