diff --git a/mybatis-flex-solon-plugin/README.md b/mybatis-flex-solon-plugin/README.md
index fca44ecd..ee151791 100644
--- a/mybatis-flex-solon-plugin/README.md
+++ b/mybatis-flex-solon-plugin/README.md
@@ -1,3 +1,5 @@
+
+
```xml
com.mybatis-flex
@@ -10,57 +12,48 @@
数据扩展插件,为 Solon Data 提供基于 mybatis-flex([代码仓库](https://gitee.com/mybatis-flex/mybatis-flex))的框架适配,以提供ORM支持。
-#### 2、强调多数据源支持
+可注入类型:
-> Solon 的 ORM 框架都是基于多数据源理念进行适配的。关于 Solon 数据源概念的描述,可参考:[多数据源与动态数据源](https://solon.noear.org/article/353)
-
-* 强调多数据源的配置。例:demo.db1...,demo.db2...
-* 强调带 name 的 DataSource Bean
-* 强调使用 @Db("name") 的数据源注解
-
-
-@Db 可注入类型:
-
-| 支持类型 | 说明 |
-| -------- | -------- |
-| Mapper.class | 注入 Mapper。例:`@Db("db1") UserMapper userMapper` |
-| FlexConfiguration | 注入 FlexConfiguration,一般仅用于配置。例:`@Db("db1") FlexConfiguration db1Cfg` |
-| FlexGlobalConfig | 注入 FlexGlobalConfig,一般仅用于配置。例:`@Db("db1") FlexGlobalConfig db1Gc` |
-| SqlSessionFactory | 注入 SqlSessionFactory。例:`@Db("db1") SqlSessionFactory db1` (不推荐直接使用) |
-| RowMapperInvoker | 注入 RowMapperInvoker。例:`@Db("db1") RowMapperInvoker rowMapper` |
+| 支持类型 | 说明 |
+| -------- |----------------------------------------------------------------------|
+| Mapper.class | 注入 Mapper。例:`@Inject UserMapper userMapper` |
+| FlexConfiguration | 注入 FlexConfiguration,一般仅用于配置。例:`@Inject FlexConfiguration db1Cfg` |
+| FlexGlobalConfig | 注入 FlexGlobalConfig,一般仅用于配置。例:`@Inject FlexGlobalConfig db1Gc` |
+| SqlSessionFactory | 注入 SqlSessionFactory。例:`@Inject SqlSessionFactory db1` (不推荐直接使用) |
+| RowMapperInvoker | 注入 RowMapperInvoker。例:`@Inject RowMapperInvoker rowMapper` |
#### 3、数据源配置
```yml
-# 配置数据源
-demo.db1:
- schema: rock
- jdbcUrl: jdbc:mysql://localhost:3306/rock?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=true
- driverClassName: com.mysql.cj.jdbc.Driver
- username: root
- password: 123456
-
+# 配置数据源(或者使用 solon.dataSources 配置数据源,效果一样)
+mybatis-flex.datasource:
+ db1:
+ jdbcUrl: jdbc:mysql://localhost:3306/rock?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=true
+ driverClassName: com.mysql.cj.jdbc.Driver
+ username: root
+ password: 123456
+
# 配置数据源对应的 mybatis 信息(要与 DataSource bean 的名字对上)
-mybatis.db1:
- typeAliases: #支持包名 或 类名(大写开头 或 *)//支持 ** 或 * 占位符
+mybatis-flex:
+ type-aliases-package: #支持包名 或 类名(大写开头 或 *)//支持 ** 或 * 占位符
- "demo4021.model"
- "demo4021.model.*" #这个表达式同上效果
- typeHandlers: #支持包名 或 类名(大写开头 或 *)//支持 ** 或 * 占位符
+ type-handlers-package: #支持包名 或 类名(大写开头 或 *)//支持 ** 或 * 占位符
- "demo4021.dso.mybaits.handler"
- "demo4021.dso.mybaits.handler.*" #这个表达式同上效果
- mappers: #支持包名 或 类名(大写开头 或 *)或 xml(.xml结尾)//支持 ** 或 * 占位符
+ mapper-locations: #支持包名 或 类名(大写开头 或 *)或 xml(.xml结尾)//支持 ** 或 * 占位符
- "demo4021.**.mapper"
- "demo4021.**.mapper.*" #这个表达式同上效果
- "classpath:demo4035/**/mapper.xml"
- - "classpath:demo4035/**/mapping/*.xml"
+ - "classpath:demo4035/**/mapping/*.xml"
configuration: #扩展配置(要与 FlexConfiguration 类的属性一一对应)
cacheEnabled: false
mapUnderscoreToCamelCase: true
- globalConfig: #全局配置(要与 FlexGlobalConfig 类的属性一一对应)//只是示例,别照抄
+ global-config: #全局配置(要与 FlexGlobalConfig 类的属性一一对应)//只是示例,别照抄
printBanner: false
- keyConfig:
+ keyConfig:
keyType: "Generator"
value: "snowFlakeId"
@@ -70,66 +63,77 @@ mybatis.db1:
#
```
-> configuration、globalConfig 没有对应属性时,可用代码处理
+主要支持的属性说明:
+
+| 支持属性 | 别名(保持与之前的兼容) | 说明 |
+|-------------------------|----------------------------|----------------------------------|
+| type-aliases-package | typeAliases | 类型别名 |
+| type-aliases-super-type | typeAliasesSuperType | 类型别名的父类(用于过滤) |
+| type-handlers-package | typeHandlers | 类型处理器 |
+| mapper-locations | mappers | mapper 类或xml文件 |
+| configuration | configuration | mybatis 配置。对应类:FlexConfiguration |
+| global-config | globalConfig | 全局部置。对应类:FlexGlobalConfig |
+
+
##### Mapper 配置注意事项:
* 通过 mapper 类包名配置。 xml 与 mapper 需同包同名
```yml
-mybatis.db1.mappers: "demo4035.dso.mapper"
+mybatis-flex.mapper-locations: "demo4035.dso.mapper"
```
* 通过 xml 目录进行配置。xml 可以固定在一个资源目录下
```yml
-mybatis.db1.mappers: "classpath:mybatis/db1/*.xml"
+mybatis-flex.mapper-locations: "classpath:mybatis/db1/*.xml"
```
#### 4、代码应用
```java
-//配置数据源
+//配置 mf (如果配置不能满足需求,可以进一步代助代码)
@Configuration
public class Config {
- //此下的 db1 与 mybatis.db1 将对应在起来 //可以用 @Db("db1") 注入mapper
- //typed=true,表示默认数据源。@Db 可不带名字注入
- @Bean(value = "db1", typed = true)
- public DataSource db1(@Inject("${demo.db1}") HikariDataSource ds) {
- return ds;
+ @Bean
+ public void ormConfig(@Inject FlexConfiguration cfg,
+ @Inject FlexGlobalConfig globalConfig) {
+
+ cfg.setCacheEnabled(false);
+
}
-
- //@Bean(value = "db2", typed = true)
- //public DataSource db2(@Inject("${demo.db2}") HikariDataSource ds) {
- // return ds;
- //}
-
- //调整 db1 的配置(如:增加插件)// (配置可以解决的,不需要这块代码)
- //@Bean
- //public void db1_cfg(@Db("db1") FlexConfiguration cfg,
- // @Db("db1") FlexGlobalConfig globalConfig) {
-
- // cfg.setCacheEnabled(false);
-
- //}
}
//应用
-@ProxyComponent
-public class AppService{
- //可用 @Db 或 @Db("db1") 注入
- @Db
+@Component
+public class AppService {
+ @Inject
AppMapper appMapper; //xml sql mapper
- //可用 @Db 或 @Db("db1")
- @Db
+ @Inject
BaseMapper appBaseMapper; //base mapper
-
- public void test(){
+
+ public void test0() {
App app1 = appMapper.getAppById(12);
App app2 = appBaseMapper.selectOneById(12);
}
+
+ @UseDataSource("db1")
+ public void test1() {
+ App app1 = appMapper.getAppById(12);
+ App app2 = appBaseMapper.selectOneById(12);
+ }
+
+ public void test2() {
+ try {
+ DataSourceKey.use("db1");
+ App app1 = appMapper.getAppById(12);
+ App app2 = appBaseMapper.selectOneById(12);
+ } finally {
+ DataSourceKey.clear();
+ }
+ }
}
```
-
diff --git a/mybatis-flex-solon-plugin/pom.xml b/mybatis-flex-solon-plugin/pom.xml
index c0aeb516..fa7a8610 100644
--- a/mybatis-flex-solon-plugin/pom.xml
+++ b/mybatis-flex-solon-plugin/pom.xml
@@ -21,7 +21,7 @@
org.noear
- mybatis-solon-plugin
+ solon-data
${solon.version}
@@ -30,6 +30,13 @@
mybatis-flex-core
${mybatis-flex.version}
+
+
+ org.noear
+ solon-aot
+ ${solon.version}
+ provided
+
diff --git a/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/core/datasource/FlexDataSourceRouting.java b/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/core/datasource/FlexDataSourceRouting.java
new file mode 100644
index 00000000..29c32ec3
--- /dev/null
+++ b/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/core/datasource/FlexDataSourceRouting.java
@@ -0,0 +1,22 @@
+package com.mybatisflex.core.datasource;
+
+import org.noear.solon.data.datasource.RoutingDataSource;
+
+import javax.sql.DataSource;
+
+/**
+ * @author noear 2024/12/3 created
+ */
+public class FlexDataSourceRouting {
+ public static DataSource determineCurrentTarget(DataSource original) {
+ if (original instanceof FlexDataSource) {
+ return ((FlexDataSource) original).getDataSource();
+ }
+
+ if (original instanceof RoutingDataSource) {
+ return ((RoutingDataSource) original).determineCurrentTarget();
+ }
+
+ return original;
+ }
+}
diff --git a/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/aot/MybatisRuntimeNativeRegistrar.java b/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/aot/MybatisRuntimeNativeRegistrar.java
new file mode 100644
index 00000000..47f81990
--- /dev/null
+++ b/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/aot/MybatisRuntimeNativeRegistrar.java
@@ -0,0 +1,137 @@
+package com.mybatisflex.solon.aot;
+
+import com.mybatisflex.solon.integration.XPluginImpl;
+import com.mybatisflex.solon.mybtais.MybatisAdapterDefault;
+import org.apache.ibatis.cache.decorators.FifoCache;
+import org.apache.ibatis.cache.decorators.LruCache;
+import org.apache.ibatis.cache.decorators.SoftCache;
+import org.apache.ibatis.cache.decorators.WeakCache;
+import org.apache.ibatis.cache.impl.PerpetualCache;
+import org.apache.ibatis.executor.Executor;
+import org.apache.ibatis.executor.statement.StatementHandler;
+import org.apache.ibatis.io.DefaultVFS;
+import org.apache.ibatis.io.JBoss6VFS;
+import org.apache.ibatis.javassist.util.proxy.ProxyFactory;
+import org.apache.ibatis.javassist.util.proxy.RuntimeSupport;
+import org.apache.ibatis.logging.Log;
+import org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl;
+import org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl;
+import org.apache.ibatis.logging.log4j2.Log4j2Impl;
+import org.apache.ibatis.logging.nologging.NoLoggingImpl;
+import org.apache.ibatis.logging.slf4j.Slf4jImpl;
+import org.apache.ibatis.logging.stdout.StdOutImpl;
+import org.apache.ibatis.mapping.BoundSql;
+import org.apache.ibatis.scripting.defaults.RawLanguageDriver;
+import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.noear.solon.aot.NativeMetadataUtils;
+import org.noear.solon.aot.RuntimeNativeMetadata;
+import org.noear.solon.aot.RuntimeNativeRegistrar;
+import org.noear.solon.aot.hint.ExecutableMode;
+import org.noear.solon.aot.hint.MemberCategory;
+import org.noear.solon.core.AppContext;
+import org.noear.solon.core.util.ResourceUtil;
+import org.noear.solon.core.wrap.MethodWrap;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.TreeSet;
+import java.util.stream.Stream;
+
+/**
+ * mybatis aot 注册 native 元数据
+ *
+ * @author songyinyin
+ * @since 2.3
+ * @link MyBatisNativeConfiguration
+ */
+public class MybatisRuntimeNativeRegistrar implements RuntimeNativeRegistrar {
+
+ @Override
+ public void register(AppContext context, RuntimeNativeMetadata metadata) {
+ Stream.of(RawLanguageDriver.class,
+ XMLLanguageDriver.class,
+ RuntimeSupport.class,
+ ProxyFactory.class,
+ Slf4jImpl.class,
+ Log.class,
+ JakartaCommonsLoggingImpl.class,
+ Log4j2Impl.class,
+ Jdk14LoggingImpl.class,
+ StdOutImpl.class,
+ NoLoggingImpl.class,
+ SqlSessionFactory.class,
+ PerpetualCache.class,
+ FifoCache.class,
+ LruCache.class,
+ SoftCache.class,
+ WeakCache.class,
+ ArrayList.class,
+ HashMap.class,
+ TreeSet.class,
+ HashSet.class
+ ).forEach(x -> metadata.registerReflection(x, MemberCategory.values()));
+
+ Stream.of(
+ "org/apache/ibatis/builder/xml/.*.dtd",
+ "org/apache/ibatis/builder/xml/.*.xsd"
+ ).forEach(metadata::registerResourceInclude);
+
+ metadata.registerJdkProxy(Executor.class);
+ metadata.registerReflection(Executor.class, MemberCategory.INTROSPECT_PUBLIC_METHODS);
+ metadata.registerAllDeclaredMethod(Executor.class, ExecutableMode.INVOKE);
+
+ metadata.registerJdkProxy(StatementHandler.class);
+ metadata.registerReflection(StatementHandler.class, MemberCategory.INTROSPECT_PUBLIC_METHODS);
+ metadata.registerAllDeclaredMethod(StatementHandler.class, ExecutableMode.INVOKE);
+
+ metadata.registerReflection(BoundSql.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS);
+ metadata.registerAllDeclaredMethod(BoundSql.class, ExecutableMode.INVOKE);
+
+ metadata.registerReflection(Configuration.class, MemberCategory.DECLARED_FIELDS);
+ metadata.registerAllDeclaredMethod(Configuration.class, ExecutableMode.INVOKE);
+
+ metadata.registerReflection(JBoss6VFS.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
+ metadata.registerReflection(DefaultVFS.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
+
+
+ registerMybatisAdapter(context, metadata, XPluginImpl.getAdapterFlex());
+
+ }
+
+ protected void registerMybatisAdapter(AppContext context, RuntimeNativeMetadata metadata, MybatisAdapterDefault bean) {
+ if (bean == null) {
+ return;
+ }
+
+ //注册 xml 资源
+ for (String res : bean.getMappers()) {
+ if (res.startsWith(ResourceUtil.TAG_classpath)) {
+ res = res.substring(ResourceUtil.TAG_classpath.length());
+ res = res.replace("**", "*");
+ res = res.replace("*", ".*");
+ metadata.registerResourceInclude(res);
+ }
+ }
+
+ //注册 mapper 代理
+ for (Class> clz : bean.getConfiguration().getMapperRegistry().getMappers()) {
+ metadata.registerJdkProxy(clz);
+ metadata.registerReflection(clz, MemberCategory.INTROSPECT_PUBLIC_METHODS);
+ Method[] declaredMethods = clz.getDeclaredMethods();
+ for (Method method : declaredMethods) {
+ MethodWrap methodWrap = context.methodGet(method);
+ NativeMetadataUtils.registerMethodAndParamAndReturnType(metadata, methodWrap);
+ }
+ }
+
+ // 注册 entity
+ for (Class> clz : bean.getConfiguration().getTypeAliasRegistry().getTypeAliases().values()) {
+ metadata.registerReflection(clz, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
+ metadata.registerDefaultConstructor(clz);
+ }
+ }
+}
diff --git a/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/integration/MybatisAdapterFactoryFlex.java b/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/integration/MybatisAdapterFactoryFlex.java
deleted file mode 100644
index 3b13caac..00000000
--- a/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/integration/MybatisAdapterFactoryFlex.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (c) 2022-2025, 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.solon.integration;
-
-import org.apache.ibatis.solon.MybatisAdapter;
-import org.apache.ibatis.solon.MybatisAdapterFactory;
-import org.noear.solon.core.BeanWrap;
-import org.noear.solon.core.Props;
-
-/**
- * MyBatis-Flex 适配器工厂。
- *
- * @author noear
- * @since 2.2
- */
-public class MybatisAdapterFactoryFlex implements MybatisAdapterFactory {
-
- @Override
- public MybatisAdapter create(BeanWrap dsWrap) {
- return new MybatisAdapterFlex(dsWrap);
- }
-
- @Override
- public MybatisAdapter create(BeanWrap dsWrap, Props dsProps) {
- return new MybatisAdapterFlex(dsWrap, dsProps);
- }
-
-}
diff --git a/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/integration/MybatisAdapterFlex.java b/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/integration/MybatisAdapterFlex.java
index 03fa3a69..22d2c1a2 100644
--- a/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/integration/MybatisAdapterFlex.java
+++ b/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/integration/MybatisAdapterFlex.java
@@ -20,14 +20,15 @@ import com.mybatisflex.core.FlexGlobalConfig;
import com.mybatisflex.core.mybatis.FlexConfiguration;
import com.mybatisflex.core.mybatis.FlexSqlSessionFactoryBuilder;
import com.mybatisflex.core.row.RowMapperInvoker;
+import com.mybatisflex.solon.mybtais.MybatisAdapterDefault;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.SqlSessionFactory;
-import org.apache.ibatis.solon.integration.MybatisAdapterDefault;
import org.noear.solon.Utils;
import org.noear.solon.core.BeanWrap;
import org.noear.solon.core.Props;
import org.noear.solon.core.VarHolder;
import org.noear.solon.core.event.EventBus;
+import org.noear.solon.core.util.ClassUtil;
import javax.sql.DataSource;
@@ -41,6 +42,7 @@ public class MybatisAdapterFlex extends MybatisAdapterDefault {
private FlexSqlSessionFactoryBuilder factoryBuilderPlus;
private FlexGlobalConfig globalConfig;
private RowMapperInvoker rowMapperInvoker;
+ private Class> typeAliasesBaseType;
protected MybatisAdapterFlex(BeanWrap dsWrap) {
super(dsWrap);
@@ -70,6 +72,11 @@ public class MybatisAdapterFlex extends MybatisAdapterDefault {
//for configuration section
config = new FlexConfiguration(environment);
+ String typeAliasesBaseTypeStr = dsProps.get("typeAliasesSuperType");
+ if (Utils.isNotEmpty(typeAliasesBaseTypeStr)) {
+ typeAliasesBaseType = ClassUtil.loadClass(typeAliasesBaseTypeStr);
+ }
+
Props cfgProps = dsProps.getProp("configuration");
if (cfgProps.size() > 0) {
Utils.injectProperties(config, cfgProps);
@@ -134,4 +141,29 @@ public class MybatisAdapterFlex extends MybatisAdapterDefault {
varH.setValue(rowMapperInvoker);
}
}
+
+ @Override
+ protected boolean isTypeAliasesType(Class> type) {
+ //typeAliasesSuperType
+ if (typeAliasesBaseType == null) {
+ return true;
+ } else {
+ return typeAliasesBaseType.isAssignableFrom(type);
+ }
+ }
+
+ @Override
+ protected boolean isTypeAliasesKey(String key) {
+ return super.isTypeAliasesKey(key) || key.startsWith("typeAliasesPackage[");
+ }
+
+ @Override
+ protected boolean isTypeHandlersKey(String key) {
+ return super.isTypeHandlersKey(key) || key.startsWith("typeHandlersPackage[");
+ }
+
+ @Override
+ protected boolean isMappersKey(String key) {
+ return super.isMappersKey(key) || key.startsWith("mapperLocations[");
+ }
}
diff --git a/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/integration/XPluginImpl.java b/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/integration/XPluginImpl.java
index 6cebe6a4..2f339bde 100644
--- a/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/integration/XPluginImpl.java
+++ b/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/integration/XPluginImpl.java
@@ -16,9 +16,28 @@
package com.mybatisflex.solon.integration;
-import org.apache.ibatis.solon.integration.MybatisAdapterManager;
+import com.mybatisflex.annotation.UseDataSource;
+import com.mybatisflex.core.FlexConsts;
+import com.mybatisflex.core.FlexGlobalConfig;
+import com.mybatisflex.core.MybatisFlexBootstrap;
+import com.mybatisflex.core.datasource.DataSourceKey;
+import com.mybatisflex.core.mybatis.FlexConfiguration;
+import com.mybatisflex.core.row.RowMapperInvoker;
+import com.mybatisflex.solon.aot.MybatisRuntimeNativeRegistrar;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.noear.solon.annotation.Inject;
+import org.noear.solon.aot.RuntimeNativeRegistrar;
import org.noear.solon.core.AppContext;
+import org.noear.solon.core.BeanWrap;
import org.noear.solon.core.Plugin;
+import org.noear.solon.core.Props;
+import org.noear.solon.core.runtime.NativeDetector;
+import org.noear.solon.core.util.ClassUtil;
+import org.noear.solon.core.util.TmplUtil;
+import org.noear.solon.data.datasource.DsUtils;
+
+import javax.sql.DataSource;
+import java.util.Map;
/**
* 配置 MyBatis-Flex 插件。
@@ -27,9 +46,105 @@ import org.noear.solon.core.Plugin;
* @since 2.2
*/
public class XPluginImpl implements Plugin {
+ private static final String CONFIG_PREFIX = "mybatisFlex";
+ private static final String CONFIG_DS_PREFIX = "mybatisFlex.datasource";
+
+ private static MybatisAdapterFlex adapterFlex;
+
+ public static MybatisAdapterFlex getAdapterFlex() {
+ return adapterFlex;
+ }
+
@Override
public void start(AppContext context) throws Throwable {
- // 此插件的 solon.plugin.priority 会大于 mybatis-solon-plugin 的值
- MybatisAdapterManager.setAdapterFactory(new MybatisAdapterFactoryFlex());
+ // 注册动态数据源的事务路由
+ //TranManager.routing(FlexDataSource.class, new FlexDataSourceRouting());
+
+ // 订阅数据源
+ context.subWrapsOfType(DataSource.class, bw -> {
+ loadDs(context, bw);
+ });
+
+ // aot
+ if (NativeDetector.isAotRuntime() && ClassUtil.hasClass(() -> RuntimeNativeRegistrar.class)) {
+ context.wrapAndPut(MybatisRuntimeNativeRegistrar.class);
+ }
+
+
+ // 构建 mf 配置的数据源
+ Class> dsDefClz = ClassUtil.loadClass("com.zaxxer.hikari.HikariDataSource");
+ Props dsProps = context.cfg().getProp(CONFIG_DS_PREFIX);
+ if (dsProps.size() > 0) {
+ Map dsMap = DsUtils.buildDsMap(dsProps, dsDefClz);
+
+ for (Map.Entry entry : dsMap.entrySet()) {
+ String dsName = entry.getKey();
+ DataSource ds = entry.getValue();
+ BeanWrap bw = context.wrap(dsName, ds);
+ loadDs(context, bw);
+ }
+ }
+ }
+
+ private void loadDs(AppContext context, BeanWrap bw) {
+ boolean isInit = MybatisFlexBootstrap.getInstance().getDataSource() == null;
+ MybatisFlexBootstrap.getInstance().addDataSource(bw.name(), bw.raw());
+
+ if (isInit) {
+ initDo(context);
+ }
+ }
+
+ private void initDo(AppContext context) {
+ BeanWrap dsBw = context.wrap(FlexConsts.NAME, MybatisFlexBootstrap.getInstance().getDataSource(), true);
+ MybatisAdapterFlex dsFlex = new MybatisAdapterFlex(dsBw, context.cfg().getProp(CONFIG_PREFIX));
+ dsFlex.mapperPublish();
+ adapterFlex = dsFlex;
+
+ // 注册到管理器(aot 时会用到)
+ //MybatisAdapterManager.register(dsBw, dsFlex);
+
+ //绑定到容器
+ context.beanInjectorAdd(Inject.class, FlexGlobalConfig.class, ((vh, anno) -> {
+ dsFlex.injectTo(vh);
+ }));
+
+
+ context.beanInjectorAdd(Inject.class, FlexConfiguration.class, ((vh, anno) -> {
+ dsFlex.injectTo(vh);
+ }));
+
+ context.beanInjectorAdd(Inject.class, RowMapperInvoker.class, ((vh, anno) -> {
+ dsFlex.injectTo(vh);
+ }));
+
+ context.beanInjectorAdd(Inject.class, SqlSessionFactory.class, ((vh, anno) -> {
+ dsFlex.injectTo(vh);
+ }));
+
+ context.beanInterceptorAdd(UseDataSource.class, inv -> {
+ UseDataSource anno = inv.getMethodAnnotation(UseDataSource.class);
+
+ if (anno == null) {
+ anno = inv.getTargetAnnotation(UseDataSource.class);
+ }
+
+ if (anno == null) {
+ return inv.invoke();
+ } else {
+ //备份
+ String backup = DataSourceKey.get();
+
+ try {
+ String dsName = TmplUtil.parse(anno.value(), inv);
+
+ DataSourceKey.use(dsName);
+ return inv.invoke();
+ } finally {
+ //还原
+ DataSourceKey.use(backup);
+ }
+ }
+ });
}
}
diff --git a/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/mybtais/MybatisAdapterDefault.java b/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/mybtais/MybatisAdapterDefault.java
new file mode 100644
index 00000000..c5a13f6a
--- /dev/null
+++ b/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/mybtais/MybatisAdapterDefault.java
@@ -0,0 +1,358 @@
+package com.mybatisflex.solon.mybtais;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.builder.xml.XMLMapperBuilder;
+import org.apache.ibatis.executor.ErrorContext;
+import org.apache.ibatis.io.Resources;
+import org.apache.ibatis.mapping.Environment;
+import org.apache.ibatis.plugin.Interceptor;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.apache.ibatis.session.SqlSessionFactoryBuilder;
+import org.apache.ibatis.transaction.TransactionFactory;
+import org.apache.ibatis.type.TypeHandler;
+import org.noear.solon.Solon;
+import org.noear.solon.Utils;
+import org.noear.solon.core.BeanWrap;
+import org.noear.solon.core.LifecycleIndex;
+import org.noear.solon.core.Props;
+import org.noear.solon.core.VarHolder;
+import org.noear.solon.core.event.EventBus;
+import org.noear.solon.core.util.ResourceUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.sql.DataSource;
+import java.io.InputStream;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Mybatis 适配器默认实现
+ *
+ * @author noear
+ * @since 1.1
+ */
+public class MybatisAdapterDefault {
+ protected static final Logger log = LoggerFactory.getLogger(MybatisAdapterDefault.class);
+
+ protected final BeanWrap dsWrap;
+ protected final Props dsProps;
+
+ //mapper 注解验证启用?
+ protected final boolean mapperVerifyEnabled;
+
+ protected Configuration config;
+ protected SqlSessionFactory factory;
+ protected List mappers = new ArrayList<>();
+ protected SqlSessionFactoryBuilder factoryBuilder;
+
+ /**
+ * 构建Sql工厂适配器,使用默认的 typeAliases 和 mappers 配置
+ */
+ protected MybatisAdapterDefault(BeanWrap dsWrap) {
+ this(dsWrap, Solon.cfg().getProp("mybatis"));
+ }
+
+ /**
+ * 构建Sql工厂适配器,使用属性配置
+ */
+ protected MybatisAdapterDefault(BeanWrap dsWrap, Props dsProps) {
+ this.dsWrap = dsWrap;
+ if (dsProps == null) {
+ this.dsProps = new Props();
+ } else {
+ this.dsProps = dsProps;
+ }
+
+ this.mapperVerifyEnabled = dsProps.getBool("configuration.mapperVerifyEnabled", false);
+ this.factoryBuilder = new SqlSessionFactoryBuilder();
+
+ DataSource dataSource = getDataSource();
+ String dataSourceId = dsWrap.name();
+ if (Utils.isEmpty(dataSourceId)) {
+ dataSourceId = "_main";
+ }
+
+ TransactionFactory tf = new SolonManagedTransactionFactory();
+ Environment environment = new Environment(dataSourceId, tf, dataSource);
+
+ initConfiguration(environment);
+
+ //加载插件(通过Bean)
+ dsWrap.context().lifecycle(LifecycleIndex.PLUGIN_BEAN_USES, () -> {
+ dsWrap.context().beanForeach(bw -> {
+ if (bw.raw() instanceof Interceptor) {
+ config.addInterceptor(bw.raw());
+ }
+ });
+ });
+
+ //1.分发事件,推给扩展处理
+ EventBus.publish(config);
+
+ //2.初始化(顺序不能乱)
+ initDo();
+
+ dsWrap.context().getBeanAsync(SqlSessionFactoryBuilder.class, bean -> {
+ factoryBuilder = bean;
+ });
+ }
+
+ public List getMappers() {
+ return mappers;
+ }
+
+ protected DataSource getDataSource() {
+ return dsWrap.raw();
+ }
+
+ protected void initConfiguration(Environment environment) {
+ config = new Configuration(environment);
+
+ //for configuration section
+ Props cfgProps = dsProps.getProp("configuration");
+ if (cfgProps.size() > 0) {
+ Utils.injectProperties(config, cfgProps);
+ }
+ }
+
+ protected boolean isTypeAliasesType(Class> type) {
+ return true;
+ }
+
+ protected boolean isTypeAliasesKey(String key){
+ return key.startsWith("typeAliases[") || key.equals("typeAliases");
+ }
+
+ protected boolean isTypeHandlersKey(String key){
+ return key.startsWith("typeHandlers[") || key.equals("typeHandlers");
+ }
+
+ protected boolean isMappersKey(String key){
+ return key.startsWith("mappers[") || key.equals("mappers");
+ }
+
+ protected void initDo() {
+ //for typeAliases & typeHandlers section
+ dsProps.forEach((k, v) -> {
+ if (k instanceof String && v instanceof String) {
+ String key = (String) k;
+ String valStr = (String) v;
+
+ if (isTypeAliasesKey(key)) {
+ for (String val : valStr.split(",")) {
+ val = val.trim();
+ if (val.length() == 0) {
+ continue;
+ }
+
+ //package || type class,转为类表达式
+ for (Class> clz : ResourceUtil.scanClasses(dsWrap.context().getClassLoader(), val)) {
+ if (clz.isInterface() == false) {
+ if (isTypeAliasesType(clz)) {
+ getConfiguration().getTypeAliasRegistry().registerAlias(clz);
+ }
+ }
+ }
+ }
+ }
+
+ if (isTypeHandlersKey(key)) {
+ for (String val : valStr.split(",")) {
+ val = val.trim();
+ if (val.length() == 0) {
+ continue;
+ }
+
+ //package || type class,转为类表达式
+ for (Class> clz : ResourceUtil.scanClasses(dsWrap.context().getClassLoader(), val)) {
+ if (TypeHandler.class.isAssignableFrom(clz)) {
+ getConfiguration().getTypeHandlerRegistry().register(clz);
+ }
+ }
+ }
+ }
+ }
+ });
+
+ //todo: 上面的完成后,才能做下面这个
+
+ //for mappers section
+ dsProps.forEach((k, v) -> {
+ if (k instanceof String && v instanceof String) {
+ String key = (String) k;
+ String valStr = (String) v;
+
+ if (isMappersKey(key)) {
+ for (String val : valStr.split(",")) {
+ val = val.trim();
+ if (val.length() == 0) {
+ continue;
+ }
+
+ mappers.add(val);
+
+ if (ResourceUtil.hasClasspath(val)) {
+ //mapper xml, 新方法,替代旧的 *.xml (基于表达式;更自由,更语义化)
+ for (String uri : ResourceUtil.scanResources(val)) {
+ addMapperByXml(uri);
+ }
+
+ //todo: 兼容提醒:
+ compatibilityTipsOfXml(val);
+ } else {
+ //package || type class,转为类表达式
+ for (Class> clz : ResourceUtil.scanClasses(dsWrap.context().getClassLoader(), val)) {
+ if (clz.isInterface()) {
+ if (mapperVerifyEnabled) {
+ if (isMapper(clz)) {
+ getConfiguration().addMapper(clz);
+ }
+ } else {
+ getConfiguration().addMapper(clz);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ });
+
+ if (mappers.size() == 0) {
+ if (Utils.isEmpty(dsWrap.name())) {
+ log.warn("Mybatis: Missing mappers configuration!");
+ } else {
+ log.warn("Mybatis: Missing mappers configuration. name='{}'", dsWrap.name());
+ }
+ //throw new IllegalStateException("Please add the mappers configuration!");
+ } else {
+ //如果有配置,但是没有 mapper 注册成功;说明有问题了
+ if (config.getMapperRegistry().getMappers().size() == 0) {
+ //log.warn("Mybatis: Missing mapper registration, please check the mappers configuration!");
+ if (Utils.isEmpty(dsWrap.name())) {
+ throw new IllegalStateException("Missing mapper registration, please check the mappers configuration!");
+ } else {
+ throw new IllegalStateException("Missing mapper registration, please check the mappers configuration. name='" + dsWrap.name() + "'");
+ }
+ }
+ }
+
+ //for plugins section
+ List interceptors = MybatisPluginUtils.resolve(dsProps, "plugins");
+ for (Interceptor itp : interceptors) {
+ getConfiguration().addInterceptor(itp);
+ }
+ }
+
+ protected boolean isMapper(Class> clz) {
+ return clz.isAnnotationPresent(Mapper.class);
+ }
+
+ /**
+ * 获取配置器
+ */
+ public Configuration getConfiguration() {
+ return config;
+ }
+
+ /**
+ * 获取会话工厂
+ */
+ public SqlSessionFactory getFactory() {
+ if (factory == null) {
+ factory = factoryBuilder.build(getConfiguration());//new SqlSessionFactoryProxy(factoryBuilder.build(config));
+ }
+
+ return factory;
+ }
+
+ Map, Object> mapperCached = new HashMap<>();
+
+ public T getMapper(Class mapperClz) {
+ Object mapper = mapperCached.get(mapperClz);
+
+ if (mapper == null) {
+ synchronized (mapperClz) {
+ mapper = mapperCached.get(mapperClz);
+ if (mapper == null) {
+ MybatisMapperInterceptor handler = new MybatisMapperInterceptor(getFactory(), mapperClz);
+
+ mapper = Proxy.newProxyInstance(
+ mapperClz.getClassLoader(),
+ new Class[]{mapperClz},
+ handler);
+ mapperCached.put(mapperClz, mapper);
+ }
+ }
+ }
+
+ return (T) mapper;
+ }
+
+ public void injectTo(VarHolder vh) {
+ //@Db("db1") SqlSessionFactory factory;
+ if (SqlSessionFactory.class.isAssignableFrom(vh.getType())) {
+ vh.setValue(this.getFactory());
+ return;
+ }
+
+ //@Db("db1") Configuration cfg;
+ if (Configuration.class.isAssignableFrom(vh.getType())) {
+ vh.setValue(this.getConfiguration());
+ return;
+ }
+
+ //@Db("db1") UserMapper userMapper;
+ if (vh.getType().isInterface()) {
+ Object mapper = this.getMapper(vh.getType());
+
+ vh.setValue(mapper);
+ return;
+ }
+ }
+
+ protected void addMapperByXml(String uri) {
+ try {
+ // resource 配置方式
+ ErrorContext.instance().resource(uri);
+
+ //读取mapper文件
+ InputStream stream = Resources.getResourceAsStream(uri);
+
+ //mapper映射文件都是通过XMLMapperBuilder解析
+ XMLMapperBuilder mapperParser = new XMLMapperBuilder(stream, getConfiguration(), uri, getConfiguration().getSqlFragments());
+ mapperParser.parse();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void compatibilityTipsOfXml(String val) {
+ //todo: 兼容提醒:
+ //if (val.endsWith("*.xml") && val.indexOf("*") == val.indexOf("*.xml")) {
+ //@Deprecated //弃用提示
+ // log.warn("Mybatis-新文件表达式提示:'" + val + "' 不包括深度子目录;如有需要可增加'/**/'段");
+ //}
+ }
+
+
+ public void mapperPublish() {
+ for (Class> clz : getConfiguration().getMapperRegistry().getMappers()) {
+ mapperPublishDo(clz);
+ }
+ }
+
+ private void mapperPublishDo(Class> clz) {
+ if (clz != null && clz.isInterface()) {
+ Object mapper = getMapper(clz);
+
+ //进入容器,用于 @Inject 注入
+ dsWrap.context().wrapAndPut(clz, mapper);
+ }
+ }
+}
diff --git a/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/mybtais/MybatisMapperInterceptor.java b/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/mybtais/MybatisMapperInterceptor.java
new file mode 100644
index 00000000..1daad1cb
--- /dev/null
+++ b/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/mybtais/MybatisMapperInterceptor.java
@@ -0,0 +1,31 @@
+package com.mybatisflex.solon.mybtais;
+
+import org.apache.ibatis.session.SqlSession;
+import org.apache.ibatis.session.SqlSessionFactory;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+
+/**
+ * Mybatis Mapper Interceptor
+ *
+ * @author noear
+ * @since 1.6
+ */
+public class MybatisMapperInterceptor implements InvocationHandler {
+ private SqlSessionFactory factory;
+ private Class> mapperClz;
+
+ public MybatisMapperInterceptor(SqlSessionFactory factory, Class> mapperClz) {
+ this.factory = factory;
+ this.mapperClz = mapperClz;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ try (SqlSession session = factory.openSession(true)) {
+ Object mapper = session.getMapper(mapperClz);
+ return method.invoke(mapper, args);
+ }
+ }
+}
diff --git a/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/mybtais/MybatisPluginUtils.java b/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/mybtais/MybatisPluginUtils.java
new file mode 100644
index 00000000..72999635
--- /dev/null
+++ b/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/mybtais/MybatisPluginUtils.java
@@ -0,0 +1,60 @@
+package com.mybatisflex.solon.mybtais;
+
+import org.apache.ibatis.plugin.Interceptor;
+import org.noear.solon.core.Props;
+import org.noear.solon.core.util.ClassUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 插件解析工具
+ *
+ * @author noear
+ * @since 1.10
+ */
+public class MybatisPluginUtils {
+ /**
+ * 解析
+ *
+ * @param prefix 配置前缀
+ */
+ public static List resolve(Props configRoot, String prefix) {
+ List interceptors = new ArrayList<>();
+
+ int index = 0;
+ while (true) {
+ Props props = configRoot.getProp(prefix + "[" + index + "]");
+ if (props.size() == 0) {
+ break;
+ } else {
+ index++;
+
+ String name = null;
+ for (Map.Entry kv : props.entrySet()) {
+ if (kv.getKey() instanceof String) {
+ String key = (String) kv.getKey();
+ if (key.endsWith(".class")) {
+ name = key.split("\\.")[0];
+ }
+ }
+ }
+
+ if (name != null) {
+ props = props.getProp(name);
+ Interceptor plugin = ClassUtil.tryInstance(props.get("class"));
+ if (plugin == null) {
+ throw new IllegalArgumentException("Mybatis plugin [" + name + "].class load failed");
+ }
+ props.remove("class");
+
+ plugin.setProperties(props);
+ interceptors.add(plugin);
+ }
+ }
+ }
+
+ return interceptors;
+ }
+}
diff --git a/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/mybtais/SolonManagedTransaction.java b/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/mybtais/SolonManagedTransaction.java
new file mode 100644
index 00000000..ba41edc0
--- /dev/null
+++ b/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/mybtais/SolonManagedTransaction.java
@@ -0,0 +1,62 @@
+package com.mybatisflex.solon.mybtais;
+
+import com.mybatisflex.core.datasource.FlexDataSourceRouting;
+import org.apache.ibatis.transaction.Transaction;
+import org.noear.solon.data.tran.TranUtils;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.SQLException;
+
+/**
+ * @author noear
+ * @since 1.6
+ */
+public class SolonManagedTransaction implements Transaction {
+ private DataSource dataSource;
+ private Connection connection;
+
+ public SolonManagedTransaction(DataSource dataSource) {
+ this.dataSource = dataSource;
+ }
+
+ @Override
+ public Connection getConnection() throws SQLException {
+ if (connection == null) {
+ DataSource selected = FlexDataSourceRouting.determineCurrentTarget(dataSource);
+ connection = TranUtils.getConnectionProxy(selected);
+ }
+
+ return connection;
+ }
+
+ @Override
+ public void commit() throws SQLException {
+ if (connection != null) {
+ connection.commit();
+ }
+ }
+
+ @Override
+ public void rollback() throws SQLException {
+ if (connection != null) {
+ connection.rollback();
+ }
+ }
+
+ @Override
+ public void close() throws SQLException {
+ if (connection != null) {
+ connection.close();
+ }
+ }
+
+ @Override
+ public Integer getTimeout() throws SQLException {
+ if (connection != null) {
+ return connection.getNetworkTimeout();
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/mybtais/SolonManagedTransactionFactory.java b/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/mybtais/SolonManagedTransactionFactory.java
new file mode 100644
index 00000000..6a37b3c9
--- /dev/null
+++ b/mybatis-flex-solon-plugin/src/main/java/com/mybatisflex/solon/mybtais/SolonManagedTransactionFactory.java
@@ -0,0 +1,30 @@
+package com.mybatisflex.solon.mybtais;
+
+import org.apache.ibatis.session.TransactionIsolationLevel;
+import org.apache.ibatis.transaction.Transaction;
+import org.apache.ibatis.transaction.TransactionFactory;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.util.Properties;
+
+/**
+ * @author noear
+ * @since 1.6
+ */
+public class SolonManagedTransactionFactory implements TransactionFactory {
+ @Override
+ public void setProperties(Properties properties) {
+
+ }
+
+ @Override
+ public Transaction newTransaction(Connection connection) {
+ throw new UnsupportedOperationException("New Solon transactions require a DataSource");
+ }
+
+ @Override
+ public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel transactionIsolationLevel, boolean b) {
+ return new SolonManagedTransaction(dataSource);
+ }
+}
diff --git a/pom.xml b/pom.xml
index 0a7deaf7..311fd2f3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -76,7 +76,7 @@
5.3.27
2.7.11
- 2.9.3
+ 3.0.1
4.13.2
1.19.3