!506 重新适配 mybatis-flex-solon-plugin

Merge pull request !506 from 西东/main
This commit is contained in:
Michael Yang 2024-12-03 07:10:19 +00:00 committed by Gitee
commit c1e16abc0a
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
13 changed files with 928 additions and 112 deletions

View File

@ -1,3 +1,5 @@
```xml
<dependency>
<groupId>com.mybatis-flex</groupId>
@ -10,47 +12,38 @@
数据扩展插件,为 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
# 配置数据源(或者使用 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"
@ -58,7 +51,7 @@ mybatis.db1:
configuration: #扩展配置(要与 FlexConfiguration 类的属性一一对应)
cacheEnabled: false
mapUnderscoreToCamelCase: true
globalConfig: #全局配置(要与 FlexGlobalConfig 类的属性一一对应)//只是示例,别照抄
global-config: #全局配置(要与 FlexGlobalConfig 类的属性一一对应)//只是示例,别照抄
printBanner: false
keyConfig:
keyType: "Generator"
@ -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
@Component
public class AppService {
//可用 @Db@Db("db1") 注入
@Db
@Inject
AppMapper appMapper; //xml sql mapper
//可用 @Db@Db("db1")
@Db
@Inject
BaseMapper<App> 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();
}
}
}
```

View File

@ -21,7 +21,7 @@
<dependencies>
<dependency>
<groupId>org.noear</groupId>
<artifactId>mybatis-solon-plugin</artifactId>
<artifactId>solon-data</artifactId>
<version>${solon.version}</version>
</dependency>
@ -30,6 +30,13 @@
<artifactId>mybatis-flex-core</artifactId>
<version>${mybatis-flex.version}</version>
</dependency>
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-aot</artifactId>
<version>${solon.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -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;
}
}

View File

@ -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 <a href="https://github.com/kazuki43zoo/mybatis-native-demo/blob/main/src/main/java/com/example/nativedemo/MyBatisNativeConfiguration.java">MyBatisNativeConfiguration</a>
*/
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);
}
}
}

View File

@ -1,42 +0,0 @@
/*
* Copyright (c) 2022-2025, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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);
}
}

View File

@ -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[");
}
}

View File

@ -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<String, DataSource> dsMap = DsUtils.buildDsMap(dsProps, dsDefClz);
for (Map.Entry<String, DataSource> 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);
}
}
});
}
}

View File

@ -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<String> 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<String> 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<Interceptor> 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<Class<?>, Object> mapperCached = new HashMap<>();
public <T> T getMapper(Class<T> 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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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<Interceptor> resolve(Props configRoot, String prefix) {
List<Interceptor> 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;
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -76,7 +76,7 @@
<spring.version>5.3.27</spring.version>
<spring-boot.version>2.7.11</spring-boot.version>
<solon.version>2.9.3</solon.version>
<solon.version>3.0.1</solon.version>
<junit.version>4.13.2</junit.version>
<testcontainers.version>1.19.3</testcontainers.version>