From 893a1f3455902ca8ac60ab8b2e6906345ff4e9b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=80=E6=BA=90=E6=B5=B7=E5=93=A5?= Date: Sat, 23 Sep 2023 18:44:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E8=AF=BB=E5=86=99?= =?UTF-8?q?=E5=88=86=E7=A6=BB=E7=BB=84=E4=BB=B6=20DataSourceShardingStrate?= =?UTF-8?q?gy.java?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/.vitepress/config.ts | 1 + docs/zh/core/read-write-splitting.md | 97 +++++++++++++++++++ .../core/datasource/DataSourceKey.java | 6 ++ .../core/datasource/DataSourceManager.java | 12 +++ .../DataSourceShardingStrategy.java | 22 +++++ .../core/mybatis/MapperInvocationHandler.java | 7 ++ 6 files changed, 145 insertions(+) create mode 100644 docs/zh/core/read-write-splitting.md create mode 100644 mybatis-flex-core/src/main/java/com/mybatisflex/core/datasource/DataSourceShardingStrategy.java diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 557164ad..ab98cf90 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -88,6 +88,7 @@ export default defineConfig({ {text: 'SQL 审计', link: '/zh/core/audit'}, {text: 'SQL 打印', link: '/zh/core/sql-print'}, {text: '多数据源', link: '/zh/core/multi-datasource'}, + {text: '读写分离 💥', link: '/zh/core/read-write-splitting'}, {text: '数据源加密', link: '/zh/core/datasource-encryption'}, {text: '动态表名', link: '/zh/core/dynamic-table'}, {text: '事务管理', link: '/zh/core/tx'}, diff --git a/docs/zh/core/read-write-splitting.md b/docs/zh/core/read-write-splitting.md new file mode 100644 index 00000000..0648a5b0 --- /dev/null +++ b/docs/zh/core/read-write-splitting.md @@ -0,0 +1,97 @@ +# 读写分离 + +MyBatis-Flex 的读写分离功能是基于 【多数据源】 功能来实现的。 + +读写分离的功能,要求当前的环境必须是多个数据库(也可理解为多个数据源),其原理是: +让主数据库(master)处理事务性操作,比如:增、删、改(INSERT、DELETE、UPDATE),而从数据库(slave)处理 SELECT 查询操作。 + +在 MyBatis 框架中,我们知道: 所有关于数据库的的操作都是通过 Mapper 来实现的,Mapper 里的一个方法,往往是和一个 SQL 一一对应。 + +因此,在 MyBatis-Flex 中,提供了一种基于 Mapper 方法的读写分离策略。 + +## 分片策略 + +自定义 `DataSourceShardingStrategy` 例如: + +```java +public class MyStrategy implements DataSourceShardingStrategy { + + public String doSharding(String currentDataSourceKey + , Object mapper, Method mapperMethod, Object[] methodArgs){ + + //返回新的数据源 key + return "newDataSourceKey"; + } +} +``` + +doSharding 的参数分别为: + +- currentDataSourceKey:当前由用户端已配置的 key +- mapper:当前的 mapper 对象 +- mapperMethod: 当前的 mapper 方法 +- methodArgs:当前的 mapper 方法的参数内容 + +自定义好 数据源分片策略后,在项目启动时,需要通过 `DataSourceManager` 配置自己的自定义分片策略: + +```java +DataSourceManager.setDataSourceShardingStrategy(new MyStrategy()); +``` + +## 示例代码 + +假设数据源配置如下: + + +```yaml +mybatis-flex: + datasource: + master: + type: druid + url: jdbc:mysql://127.0.0.1:3306/master-db + username: root + password: 123456 + slave1: + type: com.your.datasource.type2 + url: jdbc:mysql://127.0.0.1:3306/slave1 + username: root + password: 123456 + slave2: + type: com.your.datasource.type2 + url: jdbc:mysql://127.0.0.1:3306/slave2 + username: root + password: 123456 + other: + type: com.your.datasource.type2 + url: jdbc:mysql://127.0.0.1:3306/other + username: root + password: 123456 +``` +以上配置中,一共有 4 个数据源,分别为 `master`、`slave1`、`slave2`、`other`。 +假设我们的需求是:在 增删改 时,走 master,而在查询时,自动使用 `slave1`、`slave2` 进行负载均衡。 + + +那么,我们的分片策略代码如下: + +```java +public class MyStrategy implements DataSourceShardingStrategy { + + public String doSharding(String currentDataSourceKey + , Object mapper, Method mapperMethod, Object[] methodArgs){ + + // 不管 other 数据源的情况 + if ("other".equals(currentDataSourceKey)){ + return currentDataSourceKey; + } + + // 如果 mapper 的方法属于 增删改,使用 master 数据源 + if (StringUtil.startWithAny(mapperMethod.getName(), + "insert", "delete", "update")){ + return "master"; + } + + //其他场景,使用 slave1 或者 slave2 进行负载均衡 + return "slave*"; + } +} +``` diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/datasource/DataSourceKey.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/datasource/DataSourceKey.java index 1979b471..a8cd92e2 100644 --- a/mybatis-flex-core/src/main/java/com/mybatisflex/core/datasource/DataSourceKey.java +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/datasource/DataSourceKey.java @@ -15,6 +15,7 @@ */ package com.mybatisflex.core.datasource; +import java.lang.reflect.Method; import java.util.function.Supplier; /** @@ -86,4 +87,9 @@ public class DataSourceKey { public static void setManualKeyThreadLocal(ThreadLocal manualKeyThreadLocal) { DataSourceKey.manualKeyThreadLocal = manualKeyThreadLocal; } + + public static String getByShardingStrategy(String dataSource, Object mapper, Method method, Object[] args) { + String shardingDsKey = DataSourceManager.getByShardingStrategy(dataSource, mapper, method, args); + return shardingDsKey != null ? shardingDsKey : dataSource; + } } diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/datasource/DataSourceManager.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/datasource/DataSourceManager.java index 343ccd3e..497142a0 100644 --- a/mybatis-flex-core/src/main/java/com/mybatisflex/core/datasource/DataSourceManager.java +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/datasource/DataSourceManager.java @@ -37,6 +37,15 @@ public class DataSourceManager { DataSourceManager.decipher = decipher; } + private static DataSourceShardingStrategy dataSourceShardingStrategy; + + public static DataSourceShardingStrategy getDataSourceShardingStrategy() { + return dataSourceShardingStrategy; + } + + public static void setDataSourceShardingStrategy(DataSourceShardingStrategy dataSourceShardingStrategy) { + DataSourceManager.dataSourceShardingStrategy = dataSourceShardingStrategy; + } public static void decryptDataSource(DataSource dataSource) { if (decipher == null) { @@ -87,4 +96,7 @@ public class DataSourceManager { } + static String getByShardingStrategy(String dataSource, Object mapper, Method method, Object[] args) { + return dataSourceShardingStrategy != null ? dataSourceShardingStrategy.doSharding(dataSource, mapper, method, args) : null; + } } diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/datasource/DataSourceShardingStrategy.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/datasource/DataSourceShardingStrategy.java new file mode 100644 index 00000000..a2b3fa8b --- /dev/null +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/datasource/DataSourceShardingStrategy.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mybatisflex.core.datasource; + +import java.lang.reflect.Method; + +public interface DataSourceShardingStrategy { + String doSharding(String currentDataSourceKey, Object mapper, Method mapperMethod, Object[] methodArgs); +} diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/mybatis/MapperInvocationHandler.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/mybatis/MapperInvocationHandler.java index 63636dc0..db6e1430 100644 --- a/mybatis-flex-core/src/main/java/com/mybatisflex/core/mybatis/MapperInvocationHandler.java +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/mybatis/MapperInvocationHandler.java @@ -67,6 +67,13 @@ public class MapperInvocationHandler implements InvocationHandler { } } + //最终通过数据源 自定义分片 策略去获取 + String shardingDataSourceKey = DataSourceKey.getByShardingStrategy(dataSourceKey, proxy, method, args); + if (shardingDataSourceKey != null && !shardingDataSourceKey.equals(dataSourceKey)) { + DataSourceKey.use(dataSourceKey); + needClearDsKey = true; + } + //优先获取用户自己配置的 dbType DbType dbType = DialectFactory.getHintDbType(); if (dbType == null) {