李优秀 发表于 2024-7-29 14:21:06

sharding-jdbc 兼容 MybatisPlus的动态数据源

配景:之前的项目做读写分离的时候用的 MybatisPlus的动态数据做的,很多地方使用的@DS直接指定的读库大概写库实现的业务;随着表数据量越来越大,现在计划把比较大的表举行程度拆分,准备使用 ShardingJDBC实现,但是发现两者配合起来并不是那么顺利,网上大部分文章都是直接把整个Sharding的数据源当成MybatisPlus的一个数据源,那么在原本@DS上面指定的数据源就无法直接使用Sharding的分库等逻辑,以是我研究了一下源码,实现了这一逻辑,给后面有必要的朋侪提供一个案例,避免浪费不必要的时间
一.版本选择

现在ShardingJDBC重要有两个版本,一个是ShardingJDBC早期版本,一个是ShardingSphere项目中的ShardingSphere-JDBC

[*]Sharding-JDBC:Sharding-JDBC 最初由当时的项目发起人在2016年发布。它最早作为一个轻量级的 JDBC 层解决方案,旨在解决数据库分片和读写分离的问题。
[*]ShardingSphere:ShardingSphere 项目是由 Sharding-JDBC 项目发展而来的,并在2018年正式发布。Apache ShardingSphere 致力于构建更为完整的分布式数据库管理生态系统,包含了 Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar等多个组件。
现在独立的ShardingJDBC已经停更,使用到的最多的版本是 4.1.1
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
    <version>4.1.1</version>
</dependency>ShardingSphere项目现在不停处于更新迭代中,ShardingSphere-JDBC 是通过ShardingJDBC 更新迭代过来的,在原有代码的基础举行了一些优化和新功能加入,对于开发者而言,重要是参数的设置发生了一些调整。但是参数的作用和设置方式和以前一样;
这里我为了方便以后会使用到新特性,我直接使用的是 ShardingSphere-JDBC 5.2.1
官方帮助文档:https://www.bookstack.cn/read/shardingsphere-5.1.0-zh/ecf18b21ab3f559c.md
<dependency>
        <groupId>org.apache.shardingsphere</groupId>
        <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
        <version>5.2.1</version>
</dependency>二. 项目依赖

案例全部的 Maven依赖如下:
    <dependencies>
      <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
      </dependency>

      <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
      </dependency>

      <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
            <version>3.4.5</version>
      </dependency>

      <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.0</version>
      </dependency>

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

      <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
      </dependency>

      
      <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
            <version>5.2.1</version>
            <exclusions>
                <exclusion>
                  <groupId>org.yaml</groupId>
                  <artifactId>snakeyaml</artifactId>
                </exclusion>
            </exclusions>
      </dependency>
      
      <dependency>
            <groupId>org.yaml</groupId>
            <artifactId>snakeyaml</artifactId>
            <version>1.33</version>
      </dependency>

      <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
      </dependency>
    </dependencies>三. 参数设置

application.yml 设置
server:
port: 8080

mybatis-plus:
mapper-locations: classpath*:mybatis/*.xml
type-aliases-package: com.game.sharding.dto
configuration:
    map-underscore-to-camel-case: false
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

spring:
application:
    name: sharding-jdbc-test
sharding-sphere:
    datasource:
      names: master,write,read,read2
      master:
      type: com.zaxxer.hikari.HikariDataSource
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://127.0.0.1:3306/game_dev?characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&autoReconnect=true&serverTimezone=Asia/Shanghai
      username: root
      password: 123456
      write:
      type: com.zaxxer.hikari.HikariDataSource
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://127.0.0.1:3306/game_dev?characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&autoReconnect=true&serverTimezone=Asia/Shanghai
      username: root
      password: 123456
      read:
      type: com.zaxxer.hikari.HikariDataSource
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://127.0.0.1:3306/game_dev_read?characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&autoReconnect=true&serverTimezone=Asia/Shanghai
      username: root
      password: 123456
      read2:
      type: com.zaxxer.hikari.HikariDataSource
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://127.0.0.1:3306/game_dev_read?characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&autoReconnect=true&serverTimezone=Asia/Shanghai
      username: root
      password: 123456
    rules:
      sharding:
      tables:
          team_msg:
                  ## 这里的customer-ds是下面配置的读写分离的数据源名称
            actual-data-nodes: customer-ds.team_msg_${0..1}
            table-strategy:
            standard:
                sharding-column: id
                sharding-algorithm-name: msg-id # 对应下面的sharding-algorithms
      sharding-algorithms:
          ## 注意这里名称(例如msg-id)不能用下划线,会加载不了下面的参数导致启动报错
          msg-id:
            type: INLINE
            props:
                        ## 使用id取模算法
            algorithm-expression: team_msg_${id % 2}
       ## 读写分离相关                  
      readwrite-splitting:
      data-sources:
          customer-ds:
            load-balancer-name: customer-lb
            static-strategy:
            write-data-source-name: master
            read-data-source-names: read,read2,write
      load-balancers:
            customer-lb:
                          ## 使用自定义的复杂均衡算法
                type: CUSTOM
    props:
      # 显示处理之后的真实sql
      sql-show: true四. 代码设置

最关键的设置就是必要把MybatisPlus的数据源注册为使用 shardingsphere-jdbc 的数据源,并且保证数据源的名称和原来MybatisPlus的数据源同等,shardingSphereDataSource里面实在有一个Map保存了application.yml中所有设置的数据源,这里重要是为了方便后续使用@DS做动态数据源切换,以是把同一个ShardingSphere的数据库注册为4个动态数据源,避免使用@DS找不到对应的数据源;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.provider.AbstractDataSourceProvider;
import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import org.apache.commons.lang3.StringUtils;
import org.apache.shardingsphere.driver.jdbc.adapter.AbstractDataSourceAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

@Configuration
@AutoConfigureBefore({DynamicDataSourceAutoConfiguration.class, SpringBootConfiguration.class})
public class MyDataSourceConfiguration {

    /**
   * mybatisplus 动态数据源配置项
   */
    @Autowired
    private DynamicDataSourceProperties properties;

    /**
   * shardingjdbc的数据源
   */
    @Lazy
    @Resource(name = "shardingSphereDataSource")
    private AbstractDataSourceAdapter shardingSphereDataSource;

    @Value("${spring.sharding-sphere.datasource.names}")
    private String shardingDataSourceNames;

    /**
   * 注册动态数据源这里非常关键,因为我们需要用到@DS注解配置动态选择数据源,同上又要让选择的数据源使用shardingjdbc的数据源
   * 所以,这里需要动态的把所有的数据源都注册为shardingjdbc的数据源
   */
    @Bean
    public DynamicDataSourceProvider dynamicDataSourceProvider() {
      if (StringUtils.isBlank(shardingDataSourceNames)) {
            throw new RuntimeException("配置 spring.sharding-sphere.datasource.names 不能为空");
      }
      String[] names = shardingDataSourceNames.split(",");
      return new AbstractDataSourceProvider() {
            @Override
            public Map<String, DataSource> loadDataSources() {
                Map<String, DataSource> dataSourceMap = new HashMap<>();
                Arrays.stream(names).forEach(name -> dataSourceMap.put(name, shardingSphereDataSource));
                return dataSourceMap;
            }
      };
    }

    /**
   * 将动态数据源设置为首选数据源
   */
    @Primary
    @Bean
    public DataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider) {
      DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
      dataSource.setPrimary(properties.getPrimary());
      dataSource.setStrict(properties.getStrict());
      dataSource.setStrategy(properties.getStrategy());
      dataSource.setProvider(dynamicDataSourceProvider);
      dataSource.setP6spy(properties.getP6spy());
      dataSource.setSeata(properties.getSeata());
      return dataSource;
    }
}五. 自定义ShardingSphere中的复杂均衡算法

shardingsphere中的负载均衡必要实现ReadQueryLoadBalanceAlgorithm接口并在getType方法中返回自定义的算法名称,官方自带的又RoundRobinReadQueryLoadBalanceAlgorithm,RandomReadQueryLoadBalanceAlgorithm等,这里我们必须自定义算法才能兼容@DS注解实现自由切换数据源;
ShardingSphere使用的是SPI机制加载的,对应的加载源码部分如下:
https://img2024.cnblogs.com/blog/1556860/202407/1556860-20240729153852435-102928663.png
以是如果我们要让自定义的ReadQueryLoadBalanceAlgorithm类生效,必要在项目中的 META-INF的services文件夹中创建org.apache.shardingsphere.readwritesplitting.spi.ReadQueryLoadBalanceAlgorithm 文件,并且把自定义的类填入该文件中
源码中的设置如下:
https://img2024.cnblogs.com/blog/1556860/202407/1556860-20240729154304859-114563144.png
那么我们按照源码的设置直接在本身的项目中创建即可
https://img2024.cnblogs.com/blog/1556860/202407/1556860-20240729154443732-2030688279.png
最后自定义的CustomLoadBalanceAlgorithm 实现
public class CustomLoadBalanceAlgorithm implements ReadQueryLoadBalanceAlgorithm {
    private Properties props;

    public CustomLoadBalanceAlgorithm() {

    }

    @Override
    public void init(Properties props) {
      this.props = props;
    }

    /**
   * 获取数据源
   *
   * @param name数据源名称(ShardingJDBC使用的)
   * @param writeDataSourceName 写数据源名称
   * @param readDataSourceNames 所有配置的复杂均衡中读数据源名称
   * @param context 事务上下文对象,可以获取context.isInTransaction() 判断是否需要事务,可通过这个来判断是否使用 写数据源
   * @return java.lang.String
   */
    @Override
    public String getDataSource(String name, String writeDataSourceName, List<String> readDataSourceNames, TransactionConnectionContext context) {
      // 获取当前MybatisPlus指定的数据源
      String dsKey = DynamicDataSourceContextHolder.peek();
      if (StringUtils.isNotBlank(dsKey)) {
            if (writeDataSourceName.equals(dsKey)) {
                return dsKey;
            }
            if (readDataSourceNames.contains(dsKey)) {
                return dsKey;
            }
            throw new RuntimeException("@DS 配置错误,当前数据源[" + dsKey + "]不在SharingJDBC数据源列表[" + readDataSourceNames + "]中");
      }
      return writeDataSourceName;
    }

    @Override
    public String getType() {
      return "CUSTOM";
    }

    @Override
    public boolean isDefault() {
      return true;
    }

    @Override
    @Generated
    public Properties getProps() {
      return this.props;
    }
}那么此时你的ShardingSphere就已经完全适配之前MybatisPlus动态数据源了
https://img2024.cnblogs.com/blog/1556860/202407/1556860-20240729155728166-480691289.png
六. 源码

Gitee: https://gitee.com/luowenjie98/sharing-sphere-mybatisplus-demo
如果觉得对你有帮助,请给我点一个star,非常感谢 !

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: sharding-jdbc 兼容 MybatisPlus的动态数据源