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

cnblogs 2024-07-29 16:09:00 阅读 71

背景:之前的项目做读写分离的时候用的 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

<code><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>

<!--Shardingjdbc-->

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

<!-- 添加正确版本的 SnakeYAML shardingsphere-jdbc里面的依赖版本有问题,会报错-->

<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机制加载的,对应的加载源码部分如下:

image

所以如果我们要让自定义的ReadQueryLoadBalanceAlgorithm类生效,需要在项目中的 META-INF的services文件夹中创建org.apache.shardingsphere.readwritesplitting.spi.ReadQueryLoadBalanceAlgorithm 文件,并且把自定义的类填入该文件中

源码中的配置如下:

image

那么我们按照源码的配置直接在自己的项目中创建即可

image

最后自定义的CustomLoadBalanceAlgorithm 实现

<code>

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

image

六. 源码

Gitee: https://gitee.com/luowenjie98/sharing-sphere-mybatisplus-demo

如果觉得对你有帮助,请给我点一个star,非常感谢 !



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。