SpringBoot实现多数据源(六)【dynamic-datasource 多数据源组件】

上一篇文章SpringBoot实现多数据源(五)【多数据源事务控制】

六、dynamic-datasource 多数据源组件


官方文档https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611

基于 SpringBoot 的多数据源组件,功能强悍,支持 Seata 分布式事务

  • 支持 数据源分组,适用于多种场景,纯粹多库、读写分离、一主多从混合模式
  • 支持数据源敏感配置信息加密 ENC()
  • 支持每个数据库独立初始化表结构 scheme 和数据库 database
  • 支持无数据源启动,支持懒加载数据源(需要的时候再创建连接)
  • 支持 自定义注解,需继承 DS(3.2.0+)
  • 提供并简化 Druid,HikariCp、BeeCp、Dbcp2的快速集成
  • 提供对 Mybatis-Plus、Quartz、ShardingJdbc、P6sy、Jndi等组件的集成方案
  • 提供 自定义数据源来源 方案(如全从数据库加载)
  • 提供项目启动后 动态增加移除数据源 方案
  • 提供 Mybatis 环境下的 纯读写分离 方案
  • 提供使用 spel 动态参数解析数据源方案,内置 spel,session、header,支持自定义
  • 支持 多层数据源嵌套切换(ServiceA >>> ServiceB >>> ServiceC)
  • 提供基于 Seata 的分布式事务方案
  • 提供本地数据源事务方案(附:不能和原生 Spring 事务混合)

约定

  1. 本框架只做 切换数据源 这件核心的事情,并不限制你的具体操作,切换了数据源可以做任何CRUD
  2. 配置文件所有以"_"下划线分割的数据源 首部 即为组的名称,相同组名称的数据源会放在一个组下
  3. 切换数据源可以是组名,也可以是具体数据源名称。组名则切换时采用负载均衡算法切换,默认使用轮询
  4. 默认的数据源名称为 master,你可以通过 spring.datasource.dynamic.primary 修改
  5. 方法上的注解优先于类上的注解
  6. DS 支持继承抽象类上的 DS,暂不支持继承接口上的 DS

DynamicDataSource 原理

  1. 通过 DynamicDataSourceAutoConfiguration 自动配置类
  2. 配置了 DynamicRoutingDataSource,相当于前面定义的 DynamicDataSource,用来动态提供数据源
  3. 配置了 DynamicDataSourceAnnotationAdvisor 就相当于前面定义的切面类
  4. 设置了 DynamicDataSourceAnnotationInterceptor,当前 advisor 的拦截器,可以理解为前面定义的环绕通知
  5. 当执行方法,会执行 DynamicDataSourceAnnotationInterceptor#invoke 来进行增强
public Object invoke(MethodInvocation invocation) throws Throwable {
    
    
    // 获取当前@DS注解的value值
    String dsKey = this.determineDatasourceKey(invocation);
    // 设置当前数据源的标识ThreadLocal中
    DynamicDataSourceContextHolder.push(dsKey);

    Object var3;
    try {
    
    
        // 执行目标方法
        var3 = invocation.proceed();
    } finally {
    
    
        DynamicDataSourceContextHolder.poll();
    }

    return var3;
}
  1. 在执行数据库操作的时候,就会调用DataSource.getConnection(),此时DataSource指的就是 DynamicRoutingDataSource
  2. 然后执行模板抽象方法 AbstractRoutingDataSource#determineDataSource,被 DynamicRoutingDataSource 重写后调用
// AbstractRoutingDataSource 抽象类方法
protected abstract DataSource determineDataSource();

public Connection getConnection() throws SQLException {
    
    
    String xid = TransactionContext.getXID();
    if (StringUtils.isEmpty(xid)) {
    
    
        return this.determineDataSource().getConnection();
    } else {
    
    
        String ds = DynamicDataSourceContextHolder.peek();
        ds = StringUtils.isEmpty(ds) ? "default" : ds;
        ConnectionProxy connection = ConnectionFactory.getConnection(ds);
        return (Connection)(connection == null ? this.getConnectionProxy(ds, this.determineDataSource().getConnection()) : connection);
    }
}
// DynamicRoutingDataSource 类中的方法
public DataSource determineDataSource() {
    
    
    // 拿到切换的数据源标识
    String dsKey = DynamicDataSourceContextHolder.peek();
    // 通过该表示获取对应的数据源
    return this.getDataSource(dsKey);
}

用例测试

  1. 创建一个 dynamic_datasource_framework 的 SpringBoot 模块,并导入依赖
    • pom.xml
<dependencies>
    <!--jdbc-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <!--web-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--aop-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <!--mybatis-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.4</version>
    </dependency>
    <!--druid-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.2.8</version>
    </dependency>
    <!--mysql驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.28</version>
    </dependency>
    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.24</version>
    </dependency>
    <!--dynamic-datasource-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
        <version>3.5.0</version>
    </dependency>
</dependencies>
  1. 应用配置文件
    • application.yml
spring:
  autoconfigure:
    # 排除 Druid 自动配置
    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
  datasource:
    dynamic:
      # 设置默认的数据源或者数据源组,默认值即为master
      primary: master
      # 严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      strict: false
      datasource:
        master:
          # 3.2.0开始支持SPI可省略此配置
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/write?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
          username: root
          password: 123456
          type: com.alibaba.druid.pool.DruidDataSource
        slave:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/read?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
          username: root
          password: 123456
          type: com.alibaba.druid.pool.DruidDataSource
      # 指定使用 druid 数据源
      druid:
        # 连接池初始化大小
        initial-size: 5
        # 最小空闲连接数
        min-idle: 10
        # 最大连接数
        max-active: 20
        # 配置获取连接等待超时的时间
        maxWait: 60000
        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        timeBetweenEvictionRunsMillis: 60000
        # 配置一个连接在池中最小生存的时间,单位是毫秒
        minEvictableIdleTimeMillis: 300000
        # 配置一个连接在池中最大生存的时间,单位是毫秒
        maxEvictableIdleTimeMillis: 900000
        # 配置检测连接是否有效
        validationQuery: SELECT 1 FROM DUAL


        #......省略
        #以上会配置一个默认库master,一个组slave下有两个子库slave_1,slave_2

mybatis:
  mapper-locations: classpath:com/vinjcent/mapper/**/*.xml
  type-aliases-package: com.vinjcent.pojo
  1. 实体类、Mapper层(Dao层)、Service层
  • People
package com.vinjcent.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor
@Data
public class People {
    
    

    private String name;

}
  • PeopleMapper
package com.vinjcent.mapper;

import com.vinjcent.pojo.People;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface PeopleMapper {
    
    

    List<People> list();

    boolean save(People people);

}
  • PeopleServiceImpl(看方法写Service接口)
package com.vinjcent.service.impl;


import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.vinjcent.mapper.PeopleMapper;
import com.vinjcent.pojo.People;
import com.vinjcent.service.PeopleService;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


import java.util.List;

@Service
public class PeopleServiceImpl implements PeopleService {
    
    

    private final PeopleMapper peopleMapper;

    @Autowired
    public PeopleServiceImpl(PeopleMapper peopleMapper) {
    
    
        this.peopleMapper = peopleMapper;
    }

    /**
     * 说明: 不能和原生 Spring 事务混合,不使用 @DSTransactional 注解无法开启事务,即事务不会生效
     */

    // 从库,如果按照下划线命名方式配置多个,可以指定前缀即可.如slave_1、slave_2、slave3...,只需要设置salve即可,默认使用负载均衡算法
    @DS("slave")
    @Override
    public List<People> list() {
    
    
        return peopleMapper.list();
    }

    @DS("master")
    @Override
    public boolean mSave(People people) {
    
    
        return peopleMapper.save(people);
    }

    @DS("slave")
    @Override
    public boolean sSave(People people) {
    
    
        boolean save = peopleMapper.save(people);
        return save;
    }

    @DSTransactional
    public boolean save (People people) {
    
    
        PeopleService peopleService = (PeopleService) AopContext.currentProxy();
        peopleService.sSave(people);
        peopleService.mSave(people);
        // 模拟事务回滚
        int a = 1 / 0;
        return true;
    }


}
  1. 主启动类,扫描mapper接口、暴露代理对象
package com.vinjcent;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@EnableAspectJAutoProxy(exposeProxy = true)
@MapperScan("com.vinjcent.mapper")
@SpringBootApplication
public class DynamicDatasourceFrameworkApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(DynamicDatasourceFrameworkApplication.class, args);
    }

}
  1. 运行并测试接口
    • PeopleController
package com.vinjcent.controller;

import com.vinjcent.pojo.People;
import com.vinjcent.service.PeopleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("people")
public class PeopleController {
    
    


    private final PeopleService peopleService;

    @Autowired
    public PeopleController(PeopleService peopleService) {
    
    
        this.peopleService = peopleService;
    }


    @GetMapping("/list")
    public List<People> getAllPeople() {
    
    
        return peopleService.list();
    }

    @GetMapping("/insert")
    public String addPeople() {
    
    
        peopleService.save(new People("vinjcent"));
        return "添加成功";
    }

}

猜你喜欢

转载自blog.csdn.net/Wei_Naijia/article/details/128069866