mybatis-flex-solon-plugin:改为 MybatisFlexAutoConfiguration 容器自动装配的风格,方便熟悉 spring 的同学阅读和维护

This commit is contained in:
noear 2024-12-17 11:19:01 +08:00
parent 9f52273905
commit 40c67468e9
17 changed files with 483 additions and 760 deletions

View File

@ -63,19 +63,6 @@ mybatis-flex:
#
```
主要支持的属性说明:
| 支持属性 | 别名(保持与之前的兼容) | 说明 |
|-------------------------|----------------------------|----------------------------------|
| 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 需同包同名
@ -95,13 +82,15 @@ mybatis-flex.mapper-locations: "classpath:mybatis/db1/*.xml"
```java
//配置 mf (如果配置不能满足需求,可以进一步代助代码)
@Configuration
public class Config {
@Bean
public void ormConfig(@Inject FlexConfiguration cfg,
@Inject FlexGlobalConfig globalConfig) {
@Component
public class MyBatisFlexCustomizerImpl implements MyBatisFlexCustomizer, ConfigurationCustomizer {
@Override
public void customize(FlexGlobalConfig globalConfig) {
cfg.setCacheEnabled(false);
}
@Override
public void customize(FlexConfiguration configuration) {
}
}

View File

@ -5,16 +5,18 @@ 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();
return determineCurrentTarget(((FlexDataSource) original).getDataSource());
}
if (original instanceof RoutingDataSource) {
return ((RoutingDataSource) original).determineCurrentTarget();
return determineCurrentTarget(((RoutingDataSource) original).determineCurrentTarget());
}
return original;

View File

@ -0,0 +1,34 @@
/*
* 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;
import com.mybatisflex.core.mybatis.FlexConfiguration;
/**
* {@link FlexConfiguration} 做自定义的配置支持
* @author michael
*/
@FunctionalInterface
public interface ConfigurationCustomizer {
/**
* 自定义配置 {@link FlexConfiguration}
*
* @param configuration MyBatis Flex Configuration
*/
void customize(FlexConfiguration configuration);
}

View File

@ -0,0 +1,48 @@
/*
* 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;
import com.mybatisflex.core.FlexGlobalConfig;
/**
* <p>MyBatis-Flex 配置
*
* <p>一般可以用于去初始化
*
* <ul>
* <li>FlexGlobalConfig 的全局配置
* <li>自定义主键生成器
* <li>多租户配置
* <li>动态表名配置
* <li>逻辑删除处理器配置
* <li>自定义脱敏规则
* <li>SQL 审计配置
* <li>SQL 打印配置
* <li>数据源解密器配置
* <li>自定义数据方言配置
* <li>...
* </ul>
*/
@FunctionalInterface
public interface MyBatisFlexCustomizer {
/**
* 自定义 MyBatis-Flex 配置
*
* @param globalConfig 全局配置
*/
void customize(FlexGlobalConfig globalConfig);
}

View File

@ -0,0 +1,231 @@
package com.mybatisflex.solon;
import com.mybatisflex.core.FlexConsts;
import com.mybatisflex.core.FlexGlobalConfig;
import com.mybatisflex.core.MybatisFlexBootstrap;
import com.mybatisflex.core.mybatis.FlexConfiguration;
import com.mybatisflex.core.mybatis.FlexSqlSessionFactoryBuilder;
import com.mybatisflex.core.row.RowMapperInvoker;
import com.mybatisflex.solon.transaction.MybatisMapperInterceptor;
import com.mybatisflex.solon.transaction.SolonManagedTransactionFactory;
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.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.type.TypeHandler;
import org.noear.solon.Utils;
import org.noear.solon.annotation.Bean;
import org.noear.solon.annotation.Condition;
import org.noear.solon.annotation.Configuration;
import org.noear.solon.annotation.Inject;
import org.noear.solon.core.AppContext;
import org.noear.solon.core.event.EventBus;
import org.noear.solon.core.util.ResourceUtil;
import javax.sql.DataSource;
import java.io.InputStream;
import java.lang.reflect.Proxy;
/**
* Mybatis-Flex 自动装配
*
* @author noear 2024/12/17 created
*/
@Configuration
public class MybatisFlexAutoConfiguration {
private DataSource getDataSource() {
return MybatisFlexBootstrap.getInstance().getDataSource();
}
@Inject
private AppContext appContext;
@Bean
public FlexConfiguration configuration(MybatisFlexProperties flexProperties,
@Inject(required = false) ConfigurationCustomizer configurationCustomizer) {
TransactionFactory tf = new SolonManagedTransactionFactory();
Environment environment = new Environment(FlexConsts.NAME, tf, getDataSource());
FlexConfiguration configuration = new FlexConfiguration(environment);
if (Utils.isNotEmpty(flexProperties.getConfigurationProperties())) {
Utils.injectProperties(configuration, flexProperties.getConfigurationProperties());
}
if (flexProperties.getConfiguration() != null) {
flexProperties.getConfiguration().applyTo(configuration);
}
if (configurationCustomizer != null) {
configurationCustomizer.customize(configuration);
}
//增加事件总线扩展
EventBus.publish(configuration);
return configuration;
}
@Bean
@Condition(onMissingBean = SqlSessionFactoryBuilder.class)
public SqlSessionFactoryBuilder sqlSessionFactoryBuilder() {
return new FlexSqlSessionFactoryBuilder();
}
@Bean
public SqlSessionFactory sqlSessionFactory(MybatisFlexProperties flexProperties,
FlexConfiguration flexConfiguration,
SqlSessionFactoryBuilder sqlSessionFactoryBuilder) {
appContext.subBeansOfType(Interceptor.class, bean -> {
flexConfiguration.addInterceptor(bean);
});
if (isNotEmpty(flexProperties.getTypeAliasesPackage())) {
Class<?> typeAliasesSuperType = flexProperties.getTypeAliasesSuperType();
for (String val : flexProperties.getTypeAliasesPackage()) {
//package || type class转为类表达式
for (Class<?> clz : ResourceUtil.scanClasses(appContext.getClassLoader(), val)) {
if (isTypeAliases(clz, typeAliasesSuperType)) {
flexConfiguration.getTypeAliasRegistry().registerAlias(clz);
}
}
}
}
if (isNotEmpty(flexProperties.getTypeHandlersPackage())) {
for (String val : flexProperties.getTypeHandlersPackage()) {
for (Class<?> clz : ResourceUtil.scanClasses(appContext.getClassLoader(), val)) {
if (TypeHandler.class.isAssignableFrom(clz)) {
flexConfiguration.getTypeHandlerRegistry().register(clz);
}
}
}
}
if (isNotEmpty(flexProperties.getMapperLocations())) {
for (String val : flexProperties.getMapperLocations()) {
if (ResourceUtil.hasClasspath(val)) {
//mapper xml 新方法替代旧的 *.xml 基于表达式更自由更语义化
for (String uri : ResourceUtil.scanResources(val)) {
addMapperByXml(flexConfiguration, uri);
}
} else {
//package || type class转为类表达式
for (Class<?> clz : ResourceUtil.scanClasses(appContext.getClassLoader(), val)) {
if (clz.isInterface()) {
//no mapperVerifyEnabled ...
flexConfiguration.addMapper(clz);
}
}
}
}
//如果有配置但是没有 mapper 注册成功说明有问题了
if (flexConfiguration.getMapperRegistry().getMappers().size() == 0) {
throw new IllegalStateException("Missing mapper registration, please check the 'mapperLocations' configuration!");
}
}
return sqlSessionFactoryBuilder.build(flexConfiguration);
}
@Bean
public FlexGlobalConfig globalConfig(MybatisFlexProperties flexProperties,
FlexConfiguration flexConfiguration,
SqlSessionFactory sqlSessionFactory,
@Inject(required = false) MyBatisFlexCustomizer flexCustomizer) {
FlexGlobalConfig globalConfig = FlexGlobalConfig.getDefaultConfig();
if (flexProperties.getGlobalConfig() != null) {
flexProperties.getGlobalConfig().applyTo(globalConfig);
}
if (globalConfig.getKeyConfig() == null) {
//如果没有给个默认值
globalConfig.setKeyConfig(new FlexGlobalConfig.KeyConfig());
}
globalConfig.setConfiguration(flexConfiguration);
globalConfig.setSqlSessionFactory(sqlSessionFactory);
if (flexCustomizer != null) {
flexCustomizer.customize(globalConfig);
}
//增加事件总线扩展
EventBus.publish(globalConfig);
return globalConfig;
}
@Bean
public void mapperPublish(FlexConfiguration flexConfiguration,
FlexGlobalConfig globalConfig,
SqlSessionFactory sqlSessionFactory) {
for (Class<?> mapperClz : flexConfiguration.getMapperRegistry().getMappers()) {
MybatisMapperInterceptor handler = new MybatisMapperInterceptor(sqlSessionFactory, mapperClz);
Object mapperProxy = Proxy.newProxyInstance(
mapperClz.getClassLoader(),
new Class[]{mapperClz},
handler);
//推入容器之后可以被注入
appContext.wrapAndPut(mapperClz, mapperProxy);
}
}
@Bean
public RowMapperInvoker rowMapperInvoker(SqlSessionFactory sqlSessionFactory) {
return new RowMapperInvoker(sqlSessionFactory);
}
/////////////
/**
* 添加 xml mapper
*/
private void addMapperByXml(FlexConfiguration flexConfiguration, String uri) {
try {
// resource 配置方式
ErrorContext.instance().resource(uri);
//读取mapper文件
InputStream stream = Resources.getResourceAsStream(uri);
//mapper映射文件都是通过XMLMapperBuilder解析
XMLMapperBuilder mapperParser = new XMLMapperBuilder(stream, flexConfiguration, uri, flexConfiguration.getSqlFragments());
mapperParser.parse();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 是否为非空数组
*/
private boolean isNotEmpty(String[] ary) {
return ary != null && ary.length > 0;
}
/**
* 是否为类型别名
*/
private boolean isTypeAliases(Class<?> clz, Class<?> typeAliasesSuperType) {
if (clz.isInterface()) {
return false;
}
if (typeAliasesSuperType != null) {
return typeAliasesSuperType.isAssignableFrom(clz);
} else {
return true;
}
}
}

View File

@ -24,13 +24,11 @@ import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.*;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import org.noear.solon.annotation.Inject;
import org.noear.solon.core.util.ResourceUtil;
import java.net.URI;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.*;
import java.util.stream.Stream;
/**
@ -42,6 +40,8 @@ import java.util.stream.Stream;
* @author micahel
* @author 王帅
*/
@org.noear.solon.annotation.Configuration
@Inject("${mybatisFlex}")
public class MybatisFlexProperties {
private String defaultDatasourceKey;
@ -77,7 +77,7 @@ public class MybatisFlexProperties {
/**
* Packages to search type aliases. (Package delimiters are ",; \t\n")
*/
private String typeAliasesPackage;
private String[] typeAliasesPackage;
/**
* The super class for filtering type alias. If this not specifies, the MyBatis deal as type alias all classes that
@ -88,7 +88,7 @@ public class MybatisFlexProperties {
/**
* Packages to search for type handlers. (Package delimiters are ",; \t\n")
*/
private String typeHandlersPackage;
private String[] typeHandlersPackage;
/**
* Indicates whether perform presence check of the MyBatis xml config file.
@ -183,19 +183,19 @@ public class MybatisFlexProperties {
this.mapperLocations = mapperLocations;
}
public String getTypeHandlersPackage() {
public String[] getTypeHandlersPackage() {
return this.typeHandlersPackage;
}
public void setTypeHandlersPackage(String typeHandlersPackage) {
public void setTypeHandlersPackage(String[] typeHandlersPackage) {
this.typeHandlersPackage = typeHandlersPackage;
}
public String getTypeAliasesPackage() {
public String[] getTypeAliasesPackage() {
return this.typeAliasesPackage;
}
public void setTypeAliasesPackage(String typeAliasesPackage) {
public void setTypeAliasesPackage(String[] typeAliasesPackage) {
this.typeAliasesPackage = typeAliasesPackage;
}

View File

@ -0,0 +1,35 @@
package com.mybatisflex.solon.annotation;
import com.mybatisflex.annotation.UseDataSource;
import com.mybatisflex.core.datasource.DataSourceKey;
import org.noear.solon.core.aspect.Invocation;
import org.noear.solon.core.aspect.MethodInterceptor;
import org.noear.solon.core.util.TmplUtil;
/**
* @author noear 2024/12/17 created
*/
public class UseDataSourceInterceptor implements MethodInterceptor {
@Override
public Object doIntercept(Invocation inv) throws Throwable {
UseDataSource anno = inv.getMethodAnnotation(UseDataSource.class);
if (anno == null) {
anno = inv.getTargetAnnotation(UseDataSource.class);
}
if (anno == null) {
return inv.invoke();
} else {
String dsName = TmplUtil.parse(anno.value(), inv);
DataSourceKey.use(dsName);
try {
return inv.invoke();
} finally {
//还原
DataSourceKey.clear();
}
}
}
}

View File

@ -1,7 +1,7 @@
package com.mybatisflex.solon.aot;
import com.mybatisflex.solon.integration.XPluginImpl;
import com.mybatisflex.solon.mybtais.MybatisAdapterDefault;
import com.mybatisflex.core.mybatis.FlexConfiguration;
import com.mybatisflex.solon.MybatisFlexProperties;
import org.apache.ibatis.cache.decorators.FifoCache;
import org.apache.ibatis.cache.decorators.LruCache;
import org.apache.ibatis.cache.decorators.SoftCache;
@ -25,6 +25,7 @@ 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.apache.ibatis.type.TypeHandler;
import org.noear.solon.aot.NativeMetadataUtils;
import org.noear.solon.aot.RuntimeNativeMetadata;
import org.noear.solon.aot.RuntimeNativeRegistrar;
@ -98,27 +99,29 @@ public class MybatisRuntimeNativeRegistrar implements RuntimeNativeRegistrar {
metadata.registerReflection(DefaultVFS.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
registerMybatisAdapter(context, metadata, XPluginImpl.getAdapterFlex());
registerMybatisAdapter(context, metadata);
}
protected void registerMybatisAdapter(AppContext context, RuntimeNativeMetadata metadata, MybatisAdapterDefault bean) {
if (bean == null) {
return;
}
protected void registerMybatisAdapter(AppContext context, RuntimeNativeMetadata metadata) {
MybatisFlexProperties flexProperties = context.getBean(MybatisFlexProperties.class);
//注册 xml 资源
for (String res : bean.getMappers()) {
if (res.startsWith(ResourceUtil.TAG_classpath)) {
res = res.substring(ResourceUtil.TAG_classpath.length());
if (flexProperties != null) {
for (String res : flexProperties.getMapperLocations()) {
if (ResourceUtil.hasClasspath(res)) {
res = ResourceUtil.remSchema(res);
res = res.replace("**", "*");
res = res.replace("*", ".*");
metadata.registerResourceInclude(res);
}
}
}
FlexConfiguration flexConfiguration = context.getBean(FlexConfiguration.class);
if (flexConfiguration != null) {
//注册 mapper 代理
for (Class<?> clz : bean.getConfiguration().getMapperRegistry().getMappers()) {
for (Class<?> clz : flexConfiguration.getMapperRegistry().getMappers()) {
metadata.registerJdkProxy(clz);
metadata.registerReflection(clz, MemberCategory.INTROSPECT_PUBLIC_METHODS);
Method[] declaredMethods = clz.getDeclaredMethods();
@ -129,9 +132,16 @@ public class MybatisRuntimeNativeRegistrar implements RuntimeNativeRegistrar {
}
// 注册 entity
for (Class<?> clz : bean.getConfiguration().getTypeAliasRegistry().getTypeAliases().values()) {
for (Class<?> clz : flexConfiguration.getTypeAliasRegistry().getTypeAliases().values()) {
metadata.registerReflection(clz, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
metadata.registerDefaultConstructor(clz);
}
//注处 typeHandler
for (TypeHandler typeHandler : flexConfiguration.getTypeHandlerRegistry().getTypeHandlers()) {
metadata.registerReflection(typeHandler.getClass(), MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
metadata.registerDefaultConstructor(typeHandler.getClass());
}
}
}
}

View File

@ -1,170 +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 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.MybatisFlexProperties;
import com.mybatisflex.solon.mybtais.MybatisAdapterDefault;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.SqlSessionFactory;
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 javax.sql.DataSource;
/**
* MyBatis-Flex 适配器
*
* @author noear
* @since 2.2
*/
public class MybatisAdapterFlex extends MybatisAdapterDefault {
private FlexSqlSessionFactoryBuilder factoryBuilderPlus;
private FlexGlobalConfig globalConfig;
private RowMapperInvoker rowMapperInvoker;
private Class<?> typeAliasesBaseType;
private MybatisFlexProperties flexProperties;
protected MybatisAdapterFlex(BeanWrap dsWrap, Props flexProps, MybatisFlexProperties flexProperties) {
this.factoryBuilderPlus = new FlexSqlSessionFactoryBuilder();
this.flexProperties = flexProperties;
//初始化开始
initStart(dsWrap, flexProps);
//初始化之前
initAfter(dsWrap);
}
protected void initAfter(BeanWrap dsWrap) {
globalConfig.setSqlSessionFactory(getFactory());
}
@Override
protected DataSource getDataSource() {
return dsWrap.raw();
}
@Override
protected void initConfiguration(Environment environment) {
//for configuration section
config = new FlexConfiguration(environment);
if (Utils.isNotEmpty(flexProperties.getConfigurationProperties())) {
Utils.injectProperties(config, flexProperties.getConfigurationProperties());
}
if (flexProperties.getConfiguration() != null) {
flexProperties.getConfiguration().applyTo(config);
}
//for globalConfig section
if (dsWrap.typed()) {
globalConfig = FlexGlobalConfig.getDefaultConfig();
} else {
globalConfig = new FlexGlobalConfig();
}
if (flexProperties.getGlobalConfig() != null) {
flexProperties.getGlobalConfig().applyTo(globalConfig);
}
if (globalConfig.getKeyConfig() == null) {
//如果没有给个默认值
globalConfig.setKeyConfig(new FlexGlobalConfig.KeyConfig());
}
globalConfig.setConfiguration(config);
FlexGlobalConfig.setConfig(environment.getId(), globalConfig, false);
//增加事件扩展机制
EventBus.publish(globalConfig);
}
@Override
protected void loadConfiguration() {
typeAliasesBaseType = flexProperties.getTypeAliasesSuperType();
super.loadConfiguration();
}
/**
* 获取全局配置
*/
public FlexGlobalConfig getGlobalConfig() {
return globalConfig;
}
@Override
public SqlSessionFactory getFactory() {
if (factory == null) {
factory = factoryBuilderPlus.build(getConfiguration());
}
return factory;
}
@Override
public void injectTo(VarHolder varH) {
super.injectTo(varH);
// @Db("db1") FlexGlobalConfig globalConfig
if (FlexGlobalConfig.class.isAssignableFrom(varH.getType())) {
varH.setValue(this.getGlobalConfig());
return;
}
// @Db("db1") RowMapperInvoker rowMapper
if (RowMapperInvoker.class.equals(varH.getType())) {
if (rowMapperInvoker == null) {
rowMapperInvoker = new RowMapperInvoker(getFactory());
}
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

@ -17,35 +17,30 @@
package com.mybatisflex.solon.integration;
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.*;
import com.mybatisflex.core.logicdelete.LogicDeleteManager;
import com.mybatisflex.core.logicdelete.LogicDeleteProcessor;
import com.mybatisflex.core.mybatis.FlexConfiguration;
import com.mybatisflex.core.row.RowMapperInvoker;
import com.mybatisflex.core.table.DynamicSchemaProcessor;
import com.mybatisflex.core.table.DynamicTableProcessor;
import com.mybatisflex.core.table.TableManager;
import com.mybatisflex.core.tenant.TenantFactory;
import com.mybatisflex.core.tenant.TenantManager;
import com.mybatisflex.solon.MybatisFlexAutoConfiguration;
import com.mybatisflex.solon.MybatisFlexProperties;
import com.mybatisflex.solon.annotation.UseDataSourceInterceptor;
import com.mybatisflex.solon.aot.MybatisRuntimeNativeRegistrar;
import org.apache.ibatis.session.SqlSessionFactory;
import org.noear.solon.Utils;
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 javax.sql.DataSource;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* 配置 MyBatis-Flex 插件
@ -53,28 +48,30 @@ import java.util.Map;
* @author noear
* @since 2.2
*/
public class XPluginImpl implements Plugin {
private static final String CONFIG_PREFIX = "mybatisFlex.";
private static MybatisAdapterFlex adapterFlex;
public static MybatisAdapterFlex getAdapterFlex() {
return adapterFlex;
}
private Props flexProps;
private MybatisFlexProperties flexProperties;
public class MybatisFlexPlugin implements Plugin {
@Override
public void start(AppContext context) throws Throwable {
// 注册动态数据源的事务路由
//TranManager.routing(FlexDataSource.class, new FlexDataSourceRouting());
flexProps = context.cfg().getProp(CONFIG_PREFIX);
flexProperties = flexProps.toBean(MybatisFlexProperties.class);
if (flexProperties == null) {
flexProperties = new MybatisFlexProperties();
MybatisFlexProperties flexProperties = context.beanMake(MybatisFlexProperties.class).get();
// 构建 flexProperties 配置的数据源并推入应用上下文可供其它工具使用
if (Utils.isNotEmpty(flexProperties.getDatasource())) {
for (Map.Entry<String, Map<String, String>> entry : flexProperties.getDatasource().entrySet()) {
String dsName = entry.getKey();
DataSource ds = new DataSourceBuilder(entry.getValue()).build();
BeanWrap bw = context.wrap(dsName, ds, dsName.equals(flexProperties.getDefaultDatasourceKey()));
context.putWrap(dsName, bw);
if (bw.typed()) {
context.putWrap(DataSource.class, bw);
}
context.wrapPublish(bw);
}
}
// UseDataSource 拦截器(aop)
context.beanInterceptorAdd(UseDataSource.class, new UseDataSourceInterceptor());
// 数据源解密器
context.getBeanAsync(DataSourceDecipher.class, bean -> {
@ -110,24 +107,11 @@ public class XPluginImpl implements Plugin {
if (NativeDetector.isAotRuntime() && ClassUtil.hasClass(() -> RuntimeNativeRegistrar.class)) {
context.wrapAndPut(MybatisRuntimeNativeRegistrar.class);
}
}
// 构建 mf 配置的数据源
if (Utils.isNotEmpty(flexProperties.getDatasource())) {
for (Map.Entry<String, Map<String, String>> entry : flexProperties.getDatasource().entrySet()) {
String dsName = entry.getKey();
DataSource ds = new DataSourceBuilder(entry.getValue()).build();
BeanWrap bw = context.wrap(dsName, ds, dsName.equals(flexProperties.getDefaultDatasourceKey()));
context.putWrap(dsName, bw);
if (bw.typed()) {
context.putWrap(DataSource.class, bw);
}
context.wrapPublish(bw);
}
}
}
private AtomicBoolean initialized = new AtomicBoolean(false);
private void loadDs(AppContext context, BeanWrap bw) {
boolean isInit = MybatisFlexBootstrap.getInstance().getDataSource() == null;
MybatisFlexBootstrap.getInstance().addDataSource(bw.name(), bw.raw());
if (bw.typed()) {
@ -136,61 +120,9 @@ public class XPluginImpl implements Plugin {
flexDataSource.setDefaultDataSource(bw.name());
}
if (isInit) {
initDo(context);
if (initialized.compareAndSet(false, true)) {
//有数据源后再启动自动装配
context.beanMake(MybatisFlexAutoConfiguration.class);
}
}
private void initDo(AppContext context) {
BeanWrap dsBw = context.wrap(FlexConsts.NAME, MybatisFlexBootstrap.getInstance().getDataSource(), true);
MybatisAdapterFlex dsFlex = new MybatisAdapterFlex(dsBw, flexProps, flexProperties);
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

@ -1,351 +0,0 @@
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.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 BeanWrap dsWrap;
protected Props dsProps;
//mapper 注解验证启用
protected boolean mapperVerifyEnabled;
protected Configuration config;
protected SqlSessionFactory factory;
protected List<String> mappers = new ArrayList<>();
protected SqlSessionFactoryBuilder factoryBuilder;
/**
* 构建Sql工厂适配器使用属性配置
*/
protected void initStart(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);
//1.初始化配置器
initConfiguration(environment);
//加载插件通过Bean
dsWrap.context().lifecycle(LifecycleIndex.PLUGIN_BEAN_USES, () -> {
dsWrap.context().beanForeach(bw -> {
if (bw.raw() instanceof Interceptor) {
config.addInterceptor(bw.raw());
}
});
});
//2.分发事件推给扩展处理
EventBus.publish(config);
//3.加载配置器的内容顺序不能乱
loadConfiguration();
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 loadConfiguration() {
//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

@ -1,60 +0,0 @@
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

@ -1,4 +1,4 @@
package com.mybatisflex.solon.mybtais;
package com.mybatisflex.solon.transaction;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;

View File

@ -1,4 +1,4 @@
package com.mybatisflex.solon.mybtais;
package com.mybatisflex.solon.transaction;
import com.mybatisflex.core.datasource.FlexDataSourceRouting;
import org.apache.ibatis.transaction.Transaction;

View File

@ -1,4 +1,4 @@
package com.mybatisflex.solon.mybtais;
package com.mybatisflex.solon.transaction;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;

View File

@ -1,2 +1,2 @@
solon.plugin=com.mybatisflex.solon.integration.XPluginImpl
solon.plugin.priority=3
solon.plugin=com.mybatisflex.solon.integration.MybatisFlexPlugin
solon.plugin.priority=5

View File

@ -0,0 +1,23 @@
package com.mybatisflex.solon.demo;
import com.mybatisflex.core.FlexGlobalConfig;
import com.mybatisflex.core.mybatis.FlexConfiguration;
import com.mybatisflex.solon.ConfigurationCustomizer;
import com.mybatisflex.solon.MyBatisFlexCustomizer;
import org.noear.solon.annotation.Component;
/**
* @author noear 2024/12/17 created
*/
@Component
public class MyBatisFlexCustomizerImpl implements MyBatisFlexCustomizer, ConfigurationCustomizer {
@Override
public void customize(FlexGlobalConfig globalConfig) {
}
@Override
public void customize(FlexConfiguration configuration) {
}
}