From a39fb3d014502e191e577182a6c8e5ff84f8d676 Mon Sep 17 00:00:00 2001 From: oc Date: Wed, 19 Feb 2025 12:09:20 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=BA=90=E7=BC=BA=E5=A4=B1=E5=A4=84=E7=90=86=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/zh/core/multi-datasource.md | 37 +++++++++++++ .../mybatisflex/core/FlexGlobalConfig.java | 23 +++++++- .../datasource/DataSourceMissingHandler.java | 34 ++++++++++++ .../core/datasource/FlexDataSource.java | 53 ++++++++++++++++--- 4 files changed, 140 insertions(+), 7 deletions(-) create mode 100644 mybatis-flex-core/src/main/java/com/mybatisflex/core/datasource/DataSourceMissingHandler.java diff --git a/docs/zh/core/multi-datasource.md b/docs/zh/core/multi-datasource.md index ade477ab..75ae1454 100644 --- a/docs/zh/core/multi-datasource.md +++ b/docs/zh/core/multi-datasource.md @@ -206,6 +206,43 @@ public class Account { `DataSourceKey.use()` > `@UseDataSource()在方法上` > `@UseDataSource()在类上` >`@Table(dataSource="...")` ::: +## 数据源缺失处理器 + +当无法根据 `dataSourceKey` 找到数据源时,默认情况下会抛出 `IllegalStateException` 异常。 +数据源缺失处理器(`DataSourceMissingHandler`)提供了更加灵活的处理方式,你可以通过它自定义处理逻辑(如:记录日志、抛出异常或主动初始化新的数据源)。 + +### 使用示例 + +我们推荐使用 `MyBatisFlexCustomizer` 来配置数据源缺失处理器,如下所示: + +```java +@Configuration +public class MyBatisFlexConfiguration implements MyBatisFlexCustomizer { + + @Override + public void customize(FlexGlobalConfig globalConfig) { + // ... + + // 配置数据源缺失处理器:此处演示的是以后备逻辑主动初始化数据源 + globalConfig.setDataSourceMissingHandler((dataSourceKey, dataSourceMap) -> { + // 根据 key 获取数据源 + DataSource ds = customCreateDataSource(dataSourceKey); + + // 取不到的时候返回 null,后续代码逻辑仍然由 FlexDataSource 处理(即抛出异常) + if (ds == null) return null; + + // 添加新的数据源,避免下次再次触发和创建 + dataSourceMap.put(dataSourceKey, ds); + + return dataSourceMap; + }); + + // ... + } + +} +``` + ## 更多的 Spring 或 Solon Yaml 配置支持 ```yaml mybatis-flex: diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/FlexGlobalConfig.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/FlexGlobalConfig.java index 3dc26379..bc52601c 100644 --- a/mybatis-flex-core/src/main/java/com/mybatisflex/core/FlexGlobalConfig.java +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/FlexGlobalConfig.java @@ -20,6 +20,7 @@ import com.mybatisflex.annotation.KeyType; import com.mybatisflex.annotation.Listener; import com.mybatisflex.annotation.SetListener; import com.mybatisflex.annotation.UpdateListener; +import com.mybatisflex.core.datasource.DataSourceMissingHandler; import com.mybatisflex.core.datasource.FlexDataSource; import com.mybatisflex.core.dialect.DbType; import com.mybatisflex.core.exception.FlexAssert; @@ -115,6 +116,11 @@ public class FlexGlobalConfig { */ private UnMappedColumnHandler unMappedColumnHandler; + /** + * 数据源缺失处理器 + */ + private DataSourceMissingHandler dataSourceMissingHandler; + public boolean isPrintBanner() { return printBanner; } @@ -349,6 +355,21 @@ public class FlexGlobalConfig { FlexGlobalConfig.globalConfigs = globalConfigs; } + /** + * 获取数据源缺失处理器。 + * @return DataSourceMissingHandler 数据源缺失处理器实例,用于自定义处理逻辑(如:记录日志、抛出异常或提供默认数据源)。 + */ + public DataSourceMissingHandler getDataSourceMissingHandler() { + return dataSourceMissingHandler; + } + + /** + * 设置获取数据源缺失处理器。 + * @param dataSourceMissingHandler 数据源缺失处理器实例,用于自定义处理逻辑(如:记录日志、抛出异常或提供默认数据源)。 + */ + public void setDataSourceMissingHandler(final DataSourceMissingHandler dataSourceMissingHandler) { + this.dataSourceMissingHandler = dataSourceMissingHandler; + } /** * 对应的是 注解 {@link com.mybatisflex.annotation.Id} 的配置 @@ -386,7 +407,7 @@ public class FlexGlobalConfig { } - /////static factory methods///// + /// //static factory methods///// private static ConcurrentHashMap globalConfigs = new ConcurrentHashMap<>(); private static FlexGlobalConfig defaultConfig = new FlexGlobalConfig(); diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/datasource/DataSourceMissingHandler.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/datasource/DataSourceMissingHandler.java new file mode 100644 index 00000000..d30bd4b6 --- /dev/null +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/datasource/DataSourceMissingHandler.java @@ -0,0 +1,34 @@ +package com.mybatisflex.core.datasource; + +import javax.sql.DataSource; +import java.util.Map; + +/** + * 数据源缺失处理器接口。当尝试获取指定数据源但不存在时,通过此接口进行动态处理。 + * + *

+ * 该接口被设计为函数式接口,可通过Lambda表达式或方法引用实现,用于在运行时动态处理缺失的数据源。
+ * 常见应用场景:
+ * - 多租户系统中根据租户ID动态创建并缓存数据源;
+ * - 数据源缺失时的主动初始化; + *

+ */ +@FunctionalInterface +public interface DataSourceMissingHandler { + /** + * 处理缺失数据源的核心方法。 + * + * @param dataSourceKey 当前请求的数据源键(标识符),通常用于识别目标数据源 + * @param dataSourceMap 当前已存在的数据源集合(key: 数据源键,value: 数据源实例) + * @return 处理后的新数据源集合,通常应包含原有数据源及新增处理的数据源 + * @implSpec 实现类应通过此方法实现:
+ * 1. 根据dataSourceKey识别需要补充的数据源
+ * 2. 创建/配置新的DataSource实例
+ * 3. 将新数据源添加到dataSourceMap中
+ * 4. 返回更新后的数据源集合
+ * 5. 如返回 null 或 Map 为空,后续会抛出异常。
+ * @example 示例场景: + * 当请求"tenant_123"数据源不存在时,在此方法中创建对应数据源并放入返回的Map + */ + Map handle(String dataSourceKey, Map dataSourceMap); +} diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/datasource/FlexDataSource.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/datasource/FlexDataSource.java index 10dafd79..d9d7658d 100644 --- a/mybatis-flex-core/src/main/java/com/mybatisflex/core/datasource/FlexDataSource.java +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/datasource/FlexDataSource.java @@ -15,6 +15,7 @@ */ package com.mybatisflex.core.datasource; +import com.mybatisflex.core.FlexGlobalConfig; import com.mybatisflex.core.dialect.DbType; import com.mybatisflex.core.dialect.DbTypeUtil; import com.mybatisflex.core.transaction.TransactionContext; @@ -30,7 +31,11 @@ import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.sql.Connection; import java.sql.SQLException; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.concurrent.ThreadLocalRandom; /** @@ -182,7 +187,7 @@ public class FlexDataSource extends AbstractDataSource { } catch (SQLException e) { if (log.isDebugEnabled()) { log.debug("Error resetting autoCommit to true before closing the connection. " + - "Cause: " + e); + "Cause: " + e); } } } @@ -217,38 +222,74 @@ public class FlexDataSource extends AbstractDataSource { return (iface.isInstance(this) || getDataSource().isWrapperFor(iface)); } + /** + * 获取数据源缺失处理器。 + * @return DataSourceMissingHandler 数据源缺失处理器实例,用于自定义处理逻辑(如:记录日志、抛出异常或提供默认数据源)。 + */ + public DataSourceMissingHandler getDataSourceMissingHandler() { + return FlexGlobalConfig.getDefaultConfig().getDataSourceMissingHandler(); + } protected DataSource getDataSource() { DataSource dataSource = defaultDataSource; + DataSourceMissingHandler dataSourceMissingHandler = getDataSourceMissingHandler(); + if (dataSourceMap.size() > 1) { String dataSourceKey = DataSourceKey.get(); + if (StringUtil.hasText(dataSourceKey)) { - //负载均衡 key + // 负载均衡 key if (dataSourceKey.charAt(dataSourceKey.length() - 1) == LOAD_BALANCE_KEY_SUFFIX) { String prefix = dataSourceKey.substring(0, dataSourceKey.length() - 1); List matchedKeys = new ArrayList<>(); + for (String key : dataSourceMap.keySet()) { if (key.startsWith(prefix)) { matchedKeys.add(key); } } + // 当找不到匹配的 key 时,尝试后备匹配 + if (matchedKeys.isEmpty() && dataSourceMissingHandler != null) { + Map dsMap = dataSourceMissingHandler.handle(dataSourceKey, dataSourceMap); + + if (dsMap != null && !dsMap.isEmpty()) { + for (String key : dsMap.keySet()) { + if (key.startsWith(prefix)) { + matchedKeys.add(key); + } + } + } + } + if (matchedKeys.isEmpty()) { throw new IllegalStateException("Can not matched dataSource by key: \"" + dataSourceKey + "\""); } String randomKey = matchedKeys.get(ThreadLocalRandom.current().nextInt(matchedKeys.size())); + return dataSourceMap.get(randomKey); } - //非负载均衡 key + // 非负载均衡 key else { dataSource = dataSourceMap.get(dataSourceKey); + + // 当找不到匹配的 key 时,尝试后备匹配 + if (dataSource == null && dataSourceMissingHandler != null) { + Map dsMap = dataSourceMissingHandler.handle(dataSourceKey, dataSourceMap); + + if (dsMap != null && !dsMap.isEmpty()) { + dataSource = dsMap.get(dataSourceKey); + } + } + if (dataSource == null) { throw new IllegalStateException("Cannot get target dataSource by key: \"" + dataSourceKey + "\""); } } } } + return dataSource; } @@ -267,11 +308,11 @@ public class FlexDataSource extends AbstractDataSource { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (ArrayUtil.contains(proxyMethods, method.getName()) && isTransactional()) { - //do nothing + // do nothing return null; } - //setAutoCommit: true + // setAutoCommit: true if ("close".equalsIgnoreCase(method.getName())) { resetAutoCommit(original); }