springboot+dynamic-datasource实现多数据源动态切换,非@DS注解方式

springboot+dynamic-datasource实现多数据源动态切换,非注解

一、前言

最近在分析SaaS平台多租户的功能,必然涉及数据库部分的功能,多租户的设计方案要考虑租户隔离数据和租户共享数据,共享数据好实现,但是隔离数据相对复杂一些,一般要考虑隔离性扩展性租户成本运维复杂性
通常SaaS多租户在数据存储上存在三种主要的方案:

  1. 独立数据库:一个租户一个数据库。
  2. 共享数据库,隔离数据架构:多个或所有租户共享database,但不同的tenantschema
  3. 共享数据库,共享数据架构:租户共享一个database、一个schema,在表中通过tenantID区分租户的数据。

注:由于平时工作中会涉及到多数据的功能,所以今天我们分享第一种方案,不只是适用于多租户的应用场景;

二、方案思路

在这里插入图片描述
详情:
1)租户数据库连接信息是从租户管理数据库中查询到的;
2)根据租户配置动态的新增、删除对应租户数据库信息;
3)根据租户请求头中的clientId字段判断使用的数据库连接,非@DS注解方式

三、代码实现

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动态切换数据源

Controller.java

@RestController
@AllArgsConstructor
public class TestController {

    private final UserService userService;

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


}

测试功能接口,bean和dao不再示例

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

常量配置

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
在这里插入图片描述

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring Boot中集成dynamic-datasource可以帮助我们实现动态数据源切换的功能。面是一些基本步骤: 1. 添加依赖:在pom.xml文件中添加dynamic-datasource的依赖。 ```xml <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>latest-version</version> </dependency> ``` 2. 配置数据源:在application.properties或application.yml文件中配置数据源信息。 ```properties # 数据源1 spring.datasource.dynamic.datasource.master.url=jdbc:mysql://localhost:3306/db1 spring.datasource.dynamic.datasource.master.username=root spring.datasource.dynamic.datasource.master.password=123456 spring.datasource.dynamic.datasource.master.driver-class-name=com.mysql.jdbc.Driver # 数据源2 spring.datasource.dynamic.datasource.slave.url=jdbc:mysql://localhost:3306/db2 spring.datasource.dynamic.datasource.slave.username=root spring.datasource.dynamic.datasource.slave.password=123456 spring.datasource.dynamic.datasource.slave.driver-class-name=com.mysql.jdbc.Driver ``` 3. 配置动态数据源:创建一个配置类,用于配置动态数据源。 ```java @Configuration public class DynamicDataSourceConfig { @Primary @Bean("dynamicDataSource") @ConfigurationProperties(prefix = "spring.datasource.dynamic") public DataSource dynamicDataSource() { return new DruidDataSource(); } @Bean public DataSourceTransactionManager transactionManager(@Qualifier("dynamicDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dataSource) throws Exception { MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource); return sessionFactory.getObject(); } } ``` 4. 使用动态数据源:在需要使用数据源的地方,通过注解`@DS`指定数据源。 ```java @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override @DS("master") // 使用master数据源 public List<User> getMasterUsers() { return userMapper.getUsers();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dylan~~~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值