diff --git a/mybatis-flex-loveqq-starter/pom.xml b/mybatis-flex-loveqq-starter/pom.xml
new file mode 100644
index 00000000..e767cbce
--- /dev/null
+++ b/mybatis-flex-loveqq-starter/pom.xml
@@ -0,0 +1,46 @@
+
+
+ 4.0.0
+
+ com.mybatis-flex
+ parent
+ 1.10.9
+
+
+ mybatis-flex-loveqq-starter
+ jar
+
+
+ 8
+ 8
+
+
+
+
+ com.kfyty
+ loveqq-boot-starter-mybatis
+ ${loveqq.version}
+
+
+ org.mybatis
+ mybatis
+
+
+
+
+
+ com.mybatis-flex
+ mybatis-flex-core
+ ${mybatis-flex.version}
+
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+ provided
+
+
+
diff --git a/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/FlexSqlSessionFactoryBean.java b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/FlexSqlSessionFactoryBean.java
new file mode 100644
index 00000000..6e2e59c5
--- /dev/null
+++ b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/FlexSqlSessionFactoryBean.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2022-2024, 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.loveqq.framework.boot.autoconfig;
+
+import com.kfyty.loveqq.framework.boot.data.orm.mybatis.autoconfig.SqlSessionFactoryBean;
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Autowired;
+import com.kfyty.loveqq.framework.core.event.ContextRefreshedEvent;
+import com.kfyty.loveqq.framework.core.support.Pair;
+import com.mybatisflex.core.FlexConsts;
+import com.mybatisflex.core.datasource.FlexDataSource;
+import com.mybatisflex.core.mybatis.FlexConfiguration;
+import com.mybatisflex.core.mybatis.FlexSqlSessionFactoryBuilder;
+import com.mybatisflex.loveqq.framework.boot.autoconfig.transaction.FlexTransactionFactory;
+import org.apache.ibatis.builder.xml.XMLConfigBuilder;
+import org.apache.ibatis.mapping.Environment;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.SqlSessionFactory;
+
+/**
+ *
源于 {@link SqlSessionFactoryBean},主要是用于构建 {@link com.mybatisflex.core.mybatis.FlexConfiguration },而不是使用原生的 {@link Configuration}。
+ *
+ * @author kfyty725
+ */
+public class FlexSqlSessionFactoryBean extends SqlSessionFactoryBean {
+ @Autowired
+ private MybatisFlexProperties mybatisFlexProperties;
+
+ @Override
+ public void onApplicationEvent(ContextRefreshedEvent event) {
+ // nothing
+ }
+
+ @Override
+ protected Class extends Configuration> obtainConfigurationClass() {
+ return FlexConfiguration.class;
+ }
+
+ @Override
+ protected Pair buildConfiguration() {
+ Pair configPair = super.buildConfiguration();
+
+ MybatisFlexProperties.CoreConfiguration coreConfiguration = this.mybatisFlexProperties.getConfiguration();
+ if (coreConfiguration != null && coreConfiguration.getDefaultEnumTypeHandler() != null) {
+ configPair.getKey().setDefaultEnumTypeHandler(coreConfiguration.getDefaultEnumTypeHandler());
+ }
+
+ return configPair;
+ }
+
+ @Override
+ protected void buildEnvironment(Configuration configuration) {
+ FlexDataSource flexDataSource;
+
+ if (this.getDataSource() instanceof FlexDataSource) {
+ flexDataSource = (FlexDataSource) this.getDataSource();
+ } else {
+ flexDataSource = new FlexDataSource(FlexConsts.NAME, this.getDataSource());
+ }
+
+ configuration.setEnvironment(
+ new Environment(
+ FlexConsts.NAME,
+ this.getTransactionFactory() == null ? new FlexTransactionFactory() : this.getTransactionFactory(),
+ flexDataSource
+ )
+ );
+ }
+
+ @Override
+ protected void buildMapperLocations(Configuration configuration) {
+ // mybatis-flex 要延迟加载
+ }
+
+ /**
+ * 需先构建 sqlSessionFactory,再去初始化 mapperLocations
+ * 因为 xmlMapperBuilder.parse() 用到 FlexGlobalConfig, FlexGlobalConfig 的初始化是在 sqlSessionFactory 的构建方法里进行的
+ * fixed https://gitee.com/mybatis-flex/mybatis-flex/issues/I6X59V
+ */
+ @Override
+ protected SqlSessionFactory build(Configuration configuration) {
+ SqlSessionFactory sqlSessionFactory = new FlexSqlSessionFactoryBuilder().build(configuration);
+ super.buildMapperLocations(configuration);
+ return sqlSessionFactory;
+ }
+}
diff --git a/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/MybatisFlexAdminAutoConfiguration.java b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/MybatisFlexAdminAutoConfiguration.java
new file mode 100644
index 00000000..21b4a488
--- /dev/null
+++ b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/MybatisFlexAdminAutoConfiguration.java
@@ -0,0 +1,60 @@
+/*
+ * 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.loveqq.framework.boot.autoconfig;
+
+import com.kfyty.loveqq.framework.core.autoconfig.InitializingBean;
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Autowired;
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Component;
+import com.kfyty.loveqq.framework.core.autoconfig.condition.annotation.ConditionalOnProperty;
+import com.mybatisflex.core.audit.AuditManager;
+import com.mybatisflex.core.audit.MessageFactory;
+import com.mybatisflex.core.audit.MessageReporter;
+import com.mybatisflex.core.audit.http.HttpMessageReporter;
+
+/**
+ * MyBatis-Flex-Admin 自动配置。
+ *
+ * @author 王帅
+ * @author kfyty725
+ * @since 2023-07-01
+ */
+@Component
+@ConditionalOnProperty(prefix = "mybatis-flex.adminConfig", value = "enable", havingValue = "true")
+public class MybatisFlexAdminAutoConfiguration implements InitializingBean {
+ @Autowired(required = false)
+ private MessageFactory messageFactory;
+
+ @Autowired(required = false)
+ private MybatisFlexProperties properties;
+
+ @Autowired(required = false)
+ private HttpMessageReporter.JSONFormatter jsonFormatter;
+
+ @Override
+ public void afterPropertiesSet() {
+ AuditManager.setAuditEnable(true);
+ if (messageFactory != null) {
+ AuditManager.setMessageFactory(messageFactory);
+ }
+ MybatisFlexProperties.AdminConfig adminConfig = properties.getAdminConfig();
+ MessageReporter messageReporter = new HttpMessageReporter(
+ adminConfig.getEndpoint(),
+ adminConfig.getSecretKey(),
+ jsonFormatter
+ );
+ AuditManager.setMessageReporter(messageReporter);
+ }
+}
diff --git a/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/MybatisFlexAutoConfiguration.java b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/MybatisFlexAutoConfiguration.java
new file mode 100644
index 00000000..f678d7e6
--- /dev/null
+++ b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/MybatisFlexAutoConfiguration.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) 2022-2024, 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.loveqq.framework.boot.autoconfig;
+
+import com.kfyty.loveqq.framework.aop.Advisor;
+import com.kfyty.loveqq.framework.aop.support.annotated.AnnotationPointcutAdvisor;
+import com.kfyty.loveqq.framework.boot.data.orm.mybatis.autoconfig.MybatisProperties;
+import com.kfyty.loveqq.framework.boot.data.orm.mybatis.autoconfig.SqlSessionFactoryBean;
+import com.kfyty.loveqq.framework.core.autoconfig.InitializingBean;
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Autowired;
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Bean;
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Configuration;
+import com.kfyty.loveqq.framework.core.autoconfig.condition.annotation.ConditionalOnMissingBean;
+import com.kfyty.loveqq.framework.core.exception.ResolvableException;
+import com.kfyty.loveqq.framework.core.utils.IOUtil;
+import com.mybatisflex.annotation.UseDataSource;
+import com.mybatisflex.core.FlexGlobalConfig;
+import com.mybatisflex.core.datasource.DataSourceBuilder;
+import com.mybatisflex.core.datasource.DataSourceDecipher;
+import com.mybatisflex.core.datasource.DataSourceManager;
+import com.mybatisflex.core.datasource.FlexDataSource;
+import com.mybatisflex.core.exception.FlexExceptions;
+import com.mybatisflex.core.logicdelete.LogicDeleteManager;
+import com.mybatisflex.core.logicdelete.LogicDeleteProcessor;
+import com.mybatisflex.core.mybatis.FlexConfiguration;
+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.core.util.MapUtil;
+import com.mybatisflex.loveqq.framework.boot.autoconfig.annotation.ConditionalOnMybatisFlexDatasource;
+import com.mybatisflex.loveqq.framework.boot.autoconfig.customize.ConfigurationCustomizer;
+import com.mybatisflex.loveqq.framework.boot.autoconfig.customize.MyBatisFlexCustomizer;
+import com.mybatisflex.loveqq.framework.boot.autoconfig.datasource.DataSourceInterceptor;
+import com.mybatisflex.loveqq.framework.boot.autoconfig.transaction.FlexTransactionFactory;
+import com.mybatisflex.loveqq.framework.boot.autoconfig.transaction.FlexTransactionManager;
+import org.apache.ibatis.transaction.TransactionFactory;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import javax.sql.DataSource;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Mybatis-Flex 的核心配置。
+ *
+ * @author kfyty725
+ */
+@Configuration
+public class MybatisFlexAutoConfiguration implements InitializingBean {
+ @Autowired
+ protected MybatisProperties mybatisProperties;
+
+ @Autowired
+ protected MybatisFlexProperties mybatisFlexProperties;
+
+ /**
+ * 数据源解密器
+ */
+ @Autowired(required = false)
+ protected DataSourceDecipher dataSourceDecipher;
+
+ /**
+ * 动态表名
+ */
+ @Autowired(required = false)
+ protected DynamicTableProcessor dynamicTableProcessor;
+
+ /**
+ * 动态 schema 处理器
+ */
+ @Autowired(required = false)
+ protected DynamicSchemaProcessor dynamicSchemaProcessor;
+
+ /**
+ * 多租户
+ */
+ @Autowired(required = false)
+ protected TenantFactory tenantFactory;
+
+ /**
+ * 自定义逻辑删除处理器
+ */
+ @Autowired(required = false)
+ protected LogicDeleteProcessor logicDeleteProcessor;
+
+ /**
+ * 配置监听
+ */
+ @Autowired(required = false)
+ protected List configurationCustomizers;
+
+ /**
+ * 初始化监听
+ */
+ @Autowired(required = false)
+ protected List mybatisFlexCustomizers;
+
+ @Override
+ public void afterPropertiesSet() {
+ // 检测 MyBatis 原生配置文件是否存在
+ this.checkConfigFileExists();
+
+ // 添加 MyBatis-Flex 全局配置
+ if (mybatisFlexProperties.getGlobalConfig() != null) {
+ mybatisFlexProperties.getGlobalConfig().applyTo(FlexGlobalConfig.getDefaultConfig());
+ }
+
+ //数据源解密器
+ if (dataSourceDecipher != null) {
+ DataSourceManager.setDecipher(dataSourceDecipher);
+ }
+
+ // 动态表名配置
+ if (dynamicTableProcessor != null) {
+ TableManager.setDynamicTableProcessor(dynamicTableProcessor);
+ }
+
+ // 动态 schema 处理器配置
+ if (dynamicSchemaProcessor != null) {
+ TableManager.setDynamicSchemaProcessor(dynamicSchemaProcessor);
+ }
+
+ //多租户
+ if (tenantFactory != null) {
+ TenantManager.setTenantFactory(tenantFactory);
+ }
+
+ //逻辑删除处理器
+ if (logicDeleteProcessor != null) {
+ LogicDeleteManager.setProcessor(logicDeleteProcessor);
+ }
+
+ //初始化监听器
+ if (mybatisFlexCustomizers != null) {
+ mybatisFlexCustomizers.forEach(myBatisFlexCustomizer -> myBatisFlexCustomizer.customize(FlexGlobalConfig.getDefaultConfig()));
+ }
+ }
+
+ @ConditionalOnMybatisFlexDatasource
+ @Bean(resolveNested = false, independent = true)
+ public DataSource flexDataSource() {
+ FlexDataSource flexDataSource = null;
+
+ Map> dataSourceProperties = this.mybatisFlexProperties.getDatasource();
+
+ if (dataSourceDecipher != null) {
+ DataSourceManager.setDecipher(dataSourceDecipher);
+ }
+
+ if (this.mybatisFlexProperties.getDefaultDatasourceKey() != null) {
+ Map map = dataSourceProperties.remove(this.mybatisFlexProperties.getDefaultDatasourceKey());
+ if (map != null) {
+ flexDataSource = this.addDataSource(MapUtil.entry(this.mybatisFlexProperties.getDefaultDatasourceKey(), map), flexDataSource);
+ } else {
+ throw FlexExceptions.wrap("没有找到默认数据源 \"%s\" 对应的配置,请检查您的多数据源配置。", this.mybatisFlexProperties.getDefaultDatasourceKey());
+ }
+ }
+
+ for (Map.Entry> entry : dataSourceProperties.entrySet()) {
+ flexDataSource = this.addDataSource(entry, flexDataSource);
+ }
+
+ return flexDataSource;
+ }
+
+ /**
+ * 事务管理器
+ *
+ * @param dataSource 数据源
+ * @return 事务管理器
+ */
+ @ConditionalOnMybatisFlexDatasource
+ @Bean(resolveNested = false, independent = true)
+ public PlatformTransactionManager flexDataSourceTransactionManager(DataSource dataSource) {
+ return new FlexTransactionManager();
+ }
+
+ /**
+ * {@link com.mybatisflex.annotation.UseDataSource} 注解切换数据源切面。
+ */
+ @Bean(resolveNested = false, independent = true)
+ @ConditionalOnMissingBean(name = "flexDataSourceAdvisor")
+ public Advisor flexDataSourceAdvisor() {
+ return new AnnotationPointcutAdvisor(UseDataSource.class, new DataSourceInterceptor());
+ }
+
+ @ConditionalOnMissingBean
+ @Bean(resolveNested = false, independent = true)
+ public TransactionFactory flexTransactionFactory() {
+ return new FlexTransactionFactory();
+ }
+
+ @Bean
+ public SqlSessionFactoryBean flexSqlSessionFactoryBean() {
+ SqlSessionFactoryBean factory = new FlexSqlSessionFactoryBean();
+
+ this.mybatisFlexProperties.applyTo(this.mybatisProperties);
+
+ this.applyConfiguration(factory);
+
+ return factory;
+ }
+
+ protected void applyConfiguration(SqlSessionFactoryBean factory) {
+ FlexConfiguration configuration = null;
+ MybatisFlexProperties.CoreConfiguration coreConfiguration = this.mybatisFlexProperties.getConfiguration();
+ if (coreConfiguration != null || !StringUtils.hasText(this.mybatisFlexProperties.getConfigLocation())) {
+ configuration = new FlexConfiguration();
+ }
+ if (configuration != null && coreConfiguration != null) {
+ coreConfiguration.applyTo(configuration);
+ }
+ if (!CollectionUtils.isEmpty(this.configurationCustomizers)) {
+ for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
+ customizer.customize(configuration);
+ }
+ }
+ factory.setConfiguration(configuration);
+ }
+
+ private void checkConfigFileExists() {
+ if (this.mybatisFlexProperties.isCheckConfigLocation() && StringUtils.hasText(this.mybatisFlexProperties.getConfigLocation())) {
+ try (InputStream loaded = IOUtil.load(this.mybatisProperties.getConfigLocation())) {
+ Assert.state(loaded != null, "Cannot find config location: " + this.mybatisProperties.getConfigLocation() + " (please add config file or check your Mybatis configuration)");
+ } catch (IOException e) {
+ throw new ResolvableException(e);
+ }
+ }
+ }
+
+ private FlexDataSource addDataSource(Map.Entry> entry, FlexDataSource flexDataSource) {
+ DataSource dataSource = new DataSourceBuilder(entry.getValue()).build();
+
+ DataSourceManager.decryptDataSource(dataSource);
+
+ if (flexDataSource == null) {
+ flexDataSource = new FlexDataSource(entry.getKey(), dataSource, false);
+ } else {
+ flexDataSource.addDataSource(entry.getKey(), dataSource, false);
+ }
+ return flexDataSource;
+ }
+}
diff --git a/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/MybatisFlexProperties.java b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/MybatisFlexProperties.java
new file mode 100644
index 00000000..56c0a2a9
--- /dev/null
+++ b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/MybatisFlexProperties.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright (c) 2022-2024, 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.loveqq.framework.boot.autoconfig;
+
+import com.kfyty.loveqq.framework.boot.data.orm.mybatis.autoconfig.MybatisProperties;
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Component;
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.ConfigurationProperties;
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.NestedConfigurationProperty;
+import com.kfyty.loveqq.framework.core.lang.util.Mapping;
+import com.mybatisflex.core.FlexConsts;
+import com.mybatisflex.core.FlexGlobalConfig;
+import lombok.Data;
+import org.apache.ibatis.io.VFS;
+import org.apache.ibatis.logging.Log;
+import org.apache.ibatis.mapping.ResultSetType;
+import org.apache.ibatis.scripting.LanguageDriver;
+import org.apache.ibatis.session.AutoMappingBehavior;
+import org.apache.ibatis.session.AutoMappingUnknownColumnBehavior;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.ExecutorType;
+import org.apache.ibatis.session.LocalCacheScope;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.TypeHandler;
+
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+/**
+ * Mybatis-Flex 的配置属性。
+ * 参考自 mybatis-flex-spring-boot-starter
+ *
+ * @author kfyty725
+ */
+@Data
+@Component
+@ConfigurationProperties("mybatis-flex")
+public class MybatisFlexProperties {
+ /**
+ * 默认数据源 key
+ */
+ private String defaultDatasourceKey;
+
+ /**
+ *
多数据源的配置。
+ *
+ *
+ * mybatis-flex.datasource.ds1.url=***
+ * mybatis-flex.datasource.ds2.url=***
+ */
+ private Map> datasource;
+
+ /**
+ * 全局配置。
+ */
+ private GlobalConfig globalConfig;
+
+ /**
+ * MyBatis-Flex-Admin 配置。
+ */
+ private AdminConfig adminConfig;
+
+ /**
+ * Location of MyBatis xml config file.
+ */
+ private String configLocation;
+
+ /**
+ * Locations of MyBatis mapper files.
+ */
+ private String[] mapperLocations = new String[]{"mapper/**/*.xml"};
+
+ /**
+ * Packages to search type aliases. (Package delimiters are ",; \t\n")
+ */
+ private String typeAliasesPackage;
+
+ /**
+ * The super class for filtering type alias. If this not specifies, the MyBatis deal as type alias all classes that
+ * searched from typeAliasesPackage.
+ */
+ private Class> typeAliasesSuperType;
+
+ /**
+ * Packages to search for type handlers. (Package delimiters are ",; \t\n")
+ */
+ private String typeHandlersPackage;
+
+ /**
+ * Indicates whether perform presence check of the MyBatis xml config file.
+ */
+ private boolean checkConfigLocation = false;
+
+ /**
+ * Execution mode for {@link com.kfyty.loveqq.framework.boot.data.orm.mybatis.autoconfig.support.ConcurrentSqlSession}.
+ */
+ private ExecutorType executorType;
+
+ /**
+ * The default scripting language driver class. (Available when use together with mybatis-spring 2.0.2+)
+ */
+ private Class extends LanguageDriver> defaultScriptingLanguageDriver;
+
+ /**
+ * Externalized properties for MyBatis configuration.
+ */
+ private Properties configurationProperties;
+
+ /**
+ * A Configuration object for customize default settings. If {@link #configLocation} is specified, this property is
+ * not used.
+ */
+ private CoreConfiguration configuration;
+
+ /**
+ * 合并到 {@link MybatisProperties} 配置
+ *
+ * @param properties 配置
+ */
+ void applyTo(MybatisProperties properties) {
+ Mapping.from(getConfigLocation()).whenNotNull(properties::setConfigLocation);
+ Mapping.from(getExecutorType()).whenNotNull(properties::setExecutorType);
+ Mapping.from(getTypeAliasesPackage()).whenNotNull(properties::setTypeAliasesPackage);
+ Mapping.from(getTypeHandlersPackage()).whenNotNull(properties::setTypeHandlersPackage);
+ Mapping.from(getMapperLocations()).whenNotNull(properties::setMapperLocations);
+ Mapping.from(getDefaultScriptingLanguageDriver()).whenNotNull(properties::setDefaultScriptingLanguageDriver);
+ Mapping.from(getConfigurationProperties()).whenNotNull(properties::setConfigurationProperties);
+ Mapping.from(getConfiguration()).notNullMap(CoreConfiguration::getVfsImpl).whenNotNull(properties::setVfs);
+ }
+
+ /**
+ * The configuration properties for mybatis core module.
+ *
+ * @since 3.0.0
+ */
+ @Data
+ @NestedConfigurationProperty
+ public static class CoreConfiguration {
+ /**
+ * Allows using RowBounds on nested statements. If allow, set the false. Default is false.
+ */
+ private Boolean safeRowBoundsEnabled;
+
+ /**
+ * Allows using ResultHandler on nested statements. If allow, set the false. Default is true.
+ */
+ private Boolean safeResultHandlerEnabled;
+
+ /**
+ * Enables automatic mapping from classic database column names A_COLUMN to camel case classic Java property names
+ * aColumn. Default is true.
+ */
+ private Boolean mapUnderscoreToCamelCase = true;
+
+ /**
+ * When enabled, any method call will load all the lazy properties of the object. Otherwise, each property is loaded
+ * on demand (see also lazyLoadTriggerMethods). Default is false.
+ */
+ private Boolean aggressiveLazyLoading;
+
+ /**
+ * Allows or disallows multiple ResultSets to be returned from a single statement (compatible driver required).
+ * Default is true.
+ */
+ private Boolean multipleResultSetsEnabled;
+
+ /**
+ * Allows JDBC support for generated keys. A compatible driver is required. This setting forces generated keys to be
+ * used if set to true, as some drivers deny compatibility but still work (e.g. Derby). Default is false.
+ */
+ private Boolean useGeneratedKeys;
+
+ /**
+ * Uses the column label instead of the column name. Different drivers behave differently in this respect. Refer to
+ * the driver documentation, or test out both modes to determine how your driver behaves. Default is true.
+ */
+ private Boolean useColumnLabel;
+
+ /**
+ * Globally enables or disables any caches configured in any mapper under this configuration. Default is true.
+ */
+ private Boolean cacheEnabled;
+
+ /**
+ * Specifies if setters or map's put method will be called when a retrieved value is null. It is useful when you
+ * rely on Map.keySet() or null value initialization. Note primitives such as (int,boolean,etc.) will not be set to
+ * null. Default is false.
+ */
+ private Boolean callSettersOnNulls;
+
+ /**
+ * Allow referencing statement parameters by their actual names declared in the method signature. To use this
+ * feature, your project must be compiled in Java 8 with -parameters option. Default is true.
+ */
+ private Boolean useActualParamName;
+
+ /**
+ * MyBatis, by default, returns null when all the columns of a returned row are NULL. When this setting is enabled,
+ * MyBatis returns an empty instance instead. Note that it is also applied to nested results (i.e. collectioin and
+ * association). Default is false.
+ */
+ private Boolean returnInstanceForEmptyRow;
+
+ /**
+ * Removes extra whitespace characters from the SQL. Note that this also affects literal strings in SQL. Default is
+ * false.
+ */
+ private Boolean shrinkWhitespacesInSql;
+
+ /**
+ * Specifies the default value of 'nullable' attribute on 'foreach' tag. Default is false.
+ */
+ private Boolean nullableOnForEach;
+
+ /**
+ * When applying constructor auto-mapping, argument name is used to search the column to map instead of relying on
+ * the column order. Default is false.
+ */
+ private Boolean argNameBasedConstructorAutoMapping;
+
+ /**
+ * Globally enables or disables lazy loading. When enabled, all relations will be lazily loaded. This value can be
+ * superseded for a specific relation by using the fetchType attribute on it. Default is False.
+ */
+ private Boolean lazyLoadingEnabled;
+
+ /**
+ * Sets the number of seconds the driver will wait for a response from the database.
+ */
+ private Integer defaultStatementTimeout;
+
+ /**
+ * Sets the driver a hint as to control fetching size for return results. This parameter value can be override by a
+ * query setting.
+ */
+ private Integer defaultFetchSize;
+
+ /**
+ * MyBatis uses local cache to prevent circular references and speed up repeated nested queries. By default
+ * (SESSION) all queries executed during a session are cached. If localCacheScope=STATEMENT local session will be
+ * used just for statement execution, no data will be shared between two different calls to the same SqlSession.
+ * Default is SESSION.
+ */
+ private LocalCacheScope localCacheScope;
+
+ /**
+ * Specifies the JDBC type for null values when no specific JDBC type was provided for the parameter. Some drivers
+ * require specifying the column JDBC type but others work with generic values like NULL, VARCHAR or OTHER. Default
+ * is OTHER.
+ */
+ private JdbcType jdbcTypeForNull;
+
+ /**
+ * Specifies a scroll strategy when omit it per statement settings.
+ */
+ private ResultSetType defaultResultSetType;
+
+ /**
+ * Configures the default executor. SIMPLE executor does nothing special. REUSE executor reuses prepared statements.
+ * BATCH executor reuses statements and batches updates. Default is SIMPLE.
+ */
+ private ExecutorType defaultExecutorType;
+
+ /**
+ * Specifies if and how MyBatis should automatically map columns to fields/properties. NONE disables auto-mapping.
+ * PARTIAL will only auto-map results with no nested result mappings defined inside. FULL will auto-map result
+ * mappings of any complexity (containing nested or otherwise). Default is PARTIAL.
+ */
+ private AutoMappingBehavior autoMappingBehavior;
+
+ /**
+ * Specify the behavior when detects an unknown column (or unknown property type) of automatic mapping target.
+ * Default is NONE.
+ */
+ private AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior;
+
+ /**
+ * Specifies the prefix string that MyBatis will add to the logger names.
+ */
+ private String logPrefix;
+
+ /**
+ * Specifies which Object's methods trigger a lazy load. Default is [equals,clone,hashCode,toString].
+ */
+ private Set lazyLoadTriggerMethods;
+
+ /**
+ * Specifies which logging implementation MyBatis should use. If this setting is not present logging implementation
+ * will be autodiscovered.
+ */
+ private Class extends Log> logImpl;
+
+ /**
+ * Specifies VFS implementations.
+ */
+ private Class extends VFS> vfsImpl;
+
+ /**
+ * Specifies an sql provider class that holds provider method. This class apply to the type(or value) attribute on
+ * sql provider annotation(e.g. @SelectProvider), when these attribute was omitted.
+ */
+ private Class> defaultSqlProviderType;
+
+ /**
+ * Specifies the TypeHandler used by default for Enum.
+ */
+ Class extends TypeHandler> defaultEnumTypeHandler;
+
+ /**
+ * Specifies the class that provides an instance of Configuration. The returned Configuration instance is used to
+ * load lazy properties of deserialized objects. This class must have a method with a signature static Configuration
+ * getConfiguration().
+ */
+ private Class> configurationFactory;
+
+ /**
+ * Specify any configuration variables.
+ */
+ private Properties variables;
+
+ void applyTo(Configuration target) {
+ Mapping.from(getSafeRowBoundsEnabled()).whenNotNull(target::setSafeRowBoundsEnabled);
+ Mapping.from(getSafeResultHandlerEnabled()).whenNotNull(target::setSafeResultHandlerEnabled);
+ Mapping.from(getMapUnderscoreToCamelCase()).whenNotNull(target::setMapUnderscoreToCamelCase);
+ Mapping.from(getAggressiveLazyLoading()).whenNotNull(target::setAggressiveLazyLoading);
+ Mapping.from(getMultipleResultSetsEnabled()).whenNotNull(target::setMultipleResultSetsEnabled);
+ Mapping.from(getUseGeneratedKeys()).whenNotNull(target::setUseGeneratedKeys);
+ Mapping.from(getUseColumnLabel()).whenNotNull(target::setUseColumnLabel);
+ Mapping.from(getCacheEnabled()).whenNotNull(target::setCacheEnabled);
+ Mapping.from(getCallSettersOnNulls()).whenNotNull(target::setCallSettersOnNulls);
+ Mapping.from(getUseActualParamName()).whenNotNull(target::setUseActualParamName);
+ Mapping.from(getReturnInstanceForEmptyRow()).whenNotNull(target::setReturnInstanceForEmptyRow);
+ Mapping.from(getShrinkWhitespacesInSql()).whenNotNull(target::setShrinkWhitespacesInSql);
+ Mapping.from(getNullableOnForEach()).whenNotNull(target::setNullableOnForEach);
+ Mapping.from(getArgNameBasedConstructorAutoMapping()).whenNotNull(target::setArgNameBasedConstructorAutoMapping);
+ Mapping.from(getLazyLoadingEnabled()).whenNotNull(target::setLazyLoadingEnabled);
+ Mapping.from(getLogPrefix()).whenNotNull(target::setLogPrefix);
+ Mapping.from(getLazyLoadTriggerMethods()).whenNotNull(target::setLazyLoadTriggerMethods);
+ Mapping.from(getDefaultStatementTimeout()).whenNotNull(target::setDefaultStatementTimeout);
+ Mapping.from(getDefaultFetchSize()).whenNotNull(target::setDefaultFetchSize);
+ Mapping.from(getLocalCacheScope()).whenNotNull(target::setLocalCacheScope);
+ Mapping.from(getJdbcTypeForNull()).whenNotNull(target::setJdbcTypeForNull);
+ Mapping.from(getDefaultResultSetType()).whenNotNull(target::setDefaultResultSetType);
+ Mapping.from(getDefaultExecutorType()).whenNotNull(target::setDefaultExecutorType);
+ Mapping.from(getAutoMappingBehavior()).whenNotNull(target::setAutoMappingBehavior);
+ Mapping.from(getAutoMappingUnknownColumnBehavior()).whenNotNull(target::setAutoMappingUnknownColumnBehavior);
+ Mapping.from(getVariables()).whenNotNull(target::setVariables);
+ Mapping.from(getLogImpl()).whenNotNull(target::setLogImpl);
+ Mapping.from(getVfsImpl()).whenNotNull(target::setVfsImpl);
+ Mapping.from(getDefaultSqlProviderType()).whenNotNull(target::setDefaultSqlProviderType);
+ Mapping.from(getConfigurationFactory()).whenNotNull(target::setConfigurationFactory);
+ Mapping.from(getDefaultEnumTypeHandler()).whenNotNull(target::setDefaultEnumTypeHandler);
+ }
+ }
+
+ /**
+ * {@link FlexGlobalConfig} 配置。
+ *
+ * @author 王帅
+ * @author kfyty725
+ * @since 2023-06-21
+ */
+ @Data
+ @NestedConfigurationProperty
+ public static class GlobalConfig {
+ /**
+ * 启动是否打印 banner 和 版本号。
+ */
+ private boolean printBanner = true;
+
+ /**
+ * 全局的 ID 生成策略配置,当 @Id 未配置 或者 配置 KeyType 为 None 时
+ * 使用当前全局配置。
+ */
+ @NestedConfigurationProperty
+ private FlexGlobalConfig.KeyConfig keyConfig;
+
+ /**
+ * 逻辑删除数据存在标记值。
+ */
+ private Object normalValueOfLogicDelete = FlexConsts.LOGIC_DELETE_NORMAL;
+
+ /**
+ * 逻辑删除数据删除标记值。
+ */
+ private Object deletedValueOfLogicDelete = FlexConsts.LOGIC_DELETE_DELETED;
+
+
+ /**
+ * 默认的分页查询时的每页数据量。
+ */
+ private int defaultPageSize = 10;
+
+
+ /**
+ * 默认的 Relation 注解查询深度。
+ */
+ private int defaultRelationQueryDepth = 2;
+
+ /**
+ * 默认的逻辑删除字段。
+ */
+ private String logicDeleteColumn;
+
+ /**
+ * 默认的多租户字段。
+ */
+ private String tenantColumn;
+
+ /**
+ * 默认的乐观锁字段。
+ */
+ private String versionColumn;
+
+ /**
+ * 全局忽略 @Table 中配置的 schema
+ */
+ private boolean ignoreSchema = false;
+
+ void applyTo(FlexGlobalConfig target) {
+ Mapping.from(isPrintBanner()).whenNotNull(target::setPrintBanner);
+ Mapping.from(getKeyConfig()).whenNotNull(target::setKeyConfig);
+ Mapping.from(getNormalValueOfLogicDelete()).whenNotNull(target::setNormalValueOfLogicDelete);
+ Mapping.from(getDeletedValueOfLogicDelete()).whenNotNull(target::setDeletedValueOfLogicDelete);
+ Mapping.from(getDefaultPageSize()).whenNotNull(target::setDefaultPageSize);
+ Mapping.from(getDefaultRelationQueryDepth()).whenNotNull(target::setDefaultRelationQueryDepth);
+ Mapping.from(getLogicDeleteColumn()).whenNotNull(target::setLogicDeleteColumn);
+ Mapping.from(getVersionColumn()).whenNotNull(target::setVersionColumn);
+ Mapping.from(getTenantColumn()).whenNotNull(target::setTenantColumn);
+ Mapping.from(isIgnoreSchema()).whenNotNull(target::setIgnoreSchema);
+ }
+ }
+
+ /**
+ * MyBatis Flex Admin 配置。
+ *
+ * @author 王帅
+ * @author kfyty725
+ * @since 2023-07-02
+ */
+ @Data
+ @NestedConfigurationProperty
+ public static class AdminConfig {
+ /**
+ * 启用服务。
+ */
+ private boolean enable;
+
+ /**
+ * 连接端点。
+ */
+ private String endpoint;
+
+ /**
+ * 秘密密钥。
+ */
+ private String secretKey;
+ }
+}
diff --git a/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/annotation/ConditionalOnMybatisFlexDatasource.java b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/annotation/ConditionalOnMybatisFlexDatasource.java
new file mode 100644
index 00000000..e3c85a7e
--- /dev/null
+++ b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/annotation/ConditionalOnMybatisFlexDatasource.java
@@ -0,0 +1,52 @@
+/*
+ * 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.loveqq.framework.boot.autoconfig.annotation;
+
+import com.kfyty.loveqq.framework.core.autoconfig.condition.Condition;
+import com.kfyty.loveqq.framework.core.autoconfig.condition.ConditionContext;
+import com.kfyty.loveqq.framework.core.autoconfig.condition.annotation.Conditional;
+import com.kfyty.loveqq.framework.core.autoconfig.env.PropertyContext;
+import com.kfyty.loveqq.framework.core.support.AnnotationMetadata;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Map;
+
+/**
+ *
判断是否有 MyBatis-Flex 的多数据源配置。
+ *
如果配置文件中有 MyBatis-Flex 的多数据源配置,就加载 MyBatis-Flex 多数据源自动配置类。
+ *
+ * @author michael
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Conditional(ConditionalOnMybatisFlexDatasource.OnMybatisFlexDataSourceCondition.class)
+public @interface ConditionalOnMybatisFlexDatasource {
+
+ class OnMybatisFlexDataSourceCondition implements Condition {
+
+ @Override
+ public boolean isMatch(ConditionContext context, AnnotationMetadata> metadata) {
+ PropertyContext propertyContext = context.getBeanFactory().getBean(PropertyContext.class);
+ Map properties = propertyContext.searchMapProperties("mybatis-flex.datasource");
+ return properties != null && !properties.isEmpty();
+ }
+ }
+}
diff --git a/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/customize/ConfigurationCustomizer.java b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/customize/ConfigurationCustomizer.java
new file mode 100644
index 00000000..b4ccf45c
--- /dev/null
+++ b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/customize/ConfigurationCustomizer.java
@@ -0,0 +1,33 @@
+/*
+ * 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.loveqq.framework.boot.autoconfig.customize;
+
+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);
+}
diff --git a/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/customize/MyBatisFlexCustomizer.java b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/customize/MyBatisFlexCustomizer.java
new file mode 100644
index 00000000..0f7c313f
--- /dev/null
+++ b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/customize/MyBatisFlexCustomizer.java
@@ -0,0 +1,46 @@
+/*
+ * 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.loveqq.framework.boot.autoconfig.customize;
+
+import com.mybatisflex.core.FlexGlobalConfig;
+
+/**
+ *
MyBatis-Flex 配置。
+ *
+ *
一般可以用于去初始化:
+ *
+ *
+ * FlexGlobalConfig 的全局配置
+ * 自定义主键生成器
+ * 多租户配置
+ * 动态表名配置
+ * 逻辑删除处理器配置
+ * 自定义脱敏规则
+ * SQL 审计配置
+ * SQL 打印配置
+ * 数据源解密器配置
+ * 自定义数据方言配置
+ * ...
+ *
+ */
+public interface MyBatisFlexCustomizer {
+ /**
+ * 自定义 MyBatis-Flex 配置。
+ *
+ * @param globalConfig 全局配置
+ */
+ void customize(FlexGlobalConfig globalConfig);
+}
diff --git a/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/datasource/DataSourceInterceptor.java b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/datasource/DataSourceInterceptor.java
new file mode 100644
index 00000000..67fbb8ec
--- /dev/null
+++ b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/datasource/DataSourceInterceptor.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2022-2024, 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.loveqq.framework.boot.autoconfig.datasource;
+
+import com.kfyty.loveqq.framework.core.support.Pair;
+import com.mybatisflex.annotation.UseDataSource;
+import com.mybatisflex.core.datasource.DataSourceKey;
+import com.mybatisflex.core.util.StringUtil;
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 多数据源切换拦截器。
+ *
+ * @author 王帅
+ * @author barql
+ * @author michael
+ * @since 2023-06-25
+ */
+public class DataSourceInterceptor implements MethodInterceptor {
+ /**
+ * 缓存方法对应的数据源。
+ */
+ private final Map dsCache = new ConcurrentHashMap<>();
+
+ @Override
+ public Object invoke(MethodInvocation invocation) throws Throwable {
+ String dsKey = getDataSourceKey(invocation.getThis(), invocation.getMethod(), invocation.getArguments());
+ if (StringUtil.noText(dsKey)) {
+ return invocation.proceed();
+ }
+ try {
+ DataSourceKey.use(dsKey);
+ return invocation.proceed();
+ } finally {
+ DataSourceKey.clear();
+ }
+ }
+
+ private String getDataSourceKey(Object target, Method method, Object[] arguments) {
+ Object cacheKey = new Pair<>(method, target.getClass());
+ String dsKey = this.dsCache.get(cacheKey);
+ if (dsKey == null) {
+ dsKey = determineDataSourceKey(method, target.getClass());
+ // 对数据源取值进行动态取值处理
+ if (StringUtil.hasText(dsKey)) {
+ dsKey = DataSourceKey.processDataSourceKey(dsKey, target, method, arguments);
+ }
+ this.dsCache.put(cacheKey, dsKey);
+ }
+ return dsKey;
+ }
+
+ private String determineDataSourceKey(Method method, Class> targetClass) {
+ // 方法上定义有 UseDataSource 注解
+ UseDataSource annotation = method.getAnnotation(UseDataSource.class);
+ if (annotation != null) {
+ return annotation.value();
+ }
+ // 类上定义有 UseDataSource 注解
+ annotation = targetClass.getAnnotation(UseDataSource.class);
+ if (annotation != null) {
+ return annotation.value();
+ }
+ // 接口上定义有 UseDataSource 注解
+ Class>[] interfaces = targetClass.getInterfaces();
+ for (Class> anInterface : interfaces) {
+ annotation = anInterface.getAnnotation(UseDataSource.class);
+ if (annotation != null) {
+ return annotation.value();
+ }
+ }
+ // 哪里都没有 UseDataSource 注解
+ return "";
+ }
+}
diff --git a/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/package-info.java b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/package-info.java
new file mode 100644
index 00000000..ea911cd7
--- /dev/null
+++ b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+/**
+ * MyBatis-Flex loveqq-framework 支持。
+ */
+package com.mybatisflex.loveqq.framework.boot.autoconfig;
diff --git a/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/service/ServiceImpl.java b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/service/ServiceImpl.java
new file mode 100644
index 00000000..1fc1990d
--- /dev/null
+++ b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/service/ServiceImpl.java
@@ -0,0 +1,40 @@
+/*
+ * 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.loveqq.framework.boot.autoconfig.service;
+
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Autowired;
+import com.mybatisflex.core.BaseMapper;
+import com.mybatisflex.core.service.IService;
+
+/**
+ * 由 Mybatis-Flex 提供的顶级增强 Service 接口的默认实现类。
+ *
+ * @param 实体类(Entity)类型
+ * @param 映射类(Mapper)类型
+ * @author 王帅
+ * @since 2023-05-01
+ */
+@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
+public class ServiceImpl, T> implements IService {
+
+ @Autowired
+ protected M mapper;
+
+ @Override
+ public M getMapper() {
+ return mapper;
+ }
+}
diff --git a/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/transaction/FlexTransaction.java b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/transaction/FlexTransaction.java
new file mode 100644
index 00000000..4cccea83
--- /dev/null
+++ b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/transaction/FlexTransaction.java
@@ -0,0 +1,87 @@
+/*
+ * 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.loveqq.framework.boot.autoconfig.transaction;
+
+import com.mybatisflex.core.transaction.TransactionContext;
+import com.mybatisflex.core.util.StringUtil;
+import org.apache.ibatis.transaction.Transaction;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.SQLException;
+
+/**
+ * spring 事务支持,解决 issues: https://gitee.com/mybatis-flex/mybatis-flex/issues/I7HJ4J
+ *
+ * @author life
+ * @author michael
+ */
+public class FlexTransaction implements Transaction {
+
+ private final DataSource dataSource;
+ private Boolean isConnectionTransactional;
+ private Boolean autoCommit;
+ private Connection connection;
+
+ public FlexTransaction(DataSource dataSource) {
+ this.dataSource = dataSource;
+ }
+
+ @Override
+ public Connection getConnection() throws SQLException {
+ if (isConnectionTransactional == null) {
+ connection = dataSource.getConnection();
+ isConnectionTransactional = StringUtil.hasText(TransactionContext.getXID());
+ autoCommit = connection.getAutoCommit();
+ return connection;
+ }
+ // 在事务中,通过 FlexDataSource 去获取
+ // FlexDataSource 内部会进行 connection 缓存以及多数据源下的 key 判断
+ else if (isConnectionTransactional) {
+ return dataSource.getConnection();
+ }
+ // 非事务,返回当前链接
+ else {
+ return connection;
+ }
+ }
+
+ @Override
+ public void commit() throws SQLException {
+ if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
+ this.connection.commit();
+ }
+ }
+
+ @Override
+ public void rollback() throws SQLException {
+ if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
+ this.connection.rollback();
+ }
+ }
+
+ @Override
+ public void close() throws SQLException {
+ if (this.connection != null && !this.isConnectionTransactional) {
+ connection.close();
+ }
+ }
+
+ @Override
+ public Integer getTimeout() throws SQLException {
+ return TimeoutHolder.getTimeToLiveInSeconds();
+ }
+}
diff --git a/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/transaction/FlexTransactionFactory.java b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/transaction/FlexTransactionFactory.java
new file mode 100644
index 00000000..065e91cf
--- /dev/null
+++ b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/transaction/FlexTransactionFactory.java
@@ -0,0 +1,45 @@
+/*
+ * 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.loveqq.framework.boot.autoconfig.transaction;
+
+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;
+
+/**
+ * @author life
+ * @author michael
+ */
+public class FlexTransactionFactory implements TransactionFactory {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
+ return new FlexTransaction(dataSource);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Transaction newTransaction(Connection conn) {
+ throw new UnsupportedOperationException("New Flex transactions require a DataSource");
+ }
+}
diff --git a/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/transaction/FlexTransactionManager.java b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/transaction/FlexTransactionManager.java
new file mode 100644
index 00000000..8bf8d0da
--- /dev/null
+++ b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/transaction/FlexTransactionManager.java
@@ -0,0 +1,119 @@
+/*
+ * 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.loveqq.framework.boot.autoconfig.transaction;
+
+import com.mybatisflex.core.transaction.TransactionContext;
+import com.mybatisflex.core.transaction.TransactionalManager;
+import com.mybatisflex.core.util.StringUtil;
+import org.springframework.jdbc.datasource.JdbcTransactionObjectSupport;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionException;
+import org.springframework.transaction.support.AbstractPlatformTransactionManager;
+import org.springframework.transaction.support.DefaultTransactionStatus;
+
+/**
+ * MyBatis-Flex 事务支持。
+ *
+ * @author michael
+ */
+public class FlexTransactionManager extends AbstractPlatformTransactionManager {
+
+ @Override
+ protected Object doGetTransaction() throws TransactionException {
+ return new TransactionObject(TransactionContext.getXID());
+ }
+
+ @Override
+ protected boolean isExistingTransaction(Object transaction) throws TransactionException {
+ TransactionObject transactionObject = (TransactionObject) transaction;
+ return StringUtil.hasText(transactionObject.prevXid);
+ }
+
+ @Override
+ protected Object doSuspend(Object transaction) throws TransactionException {
+ TransactionContext.release();
+ TransactionObject transactionObject = (TransactionObject) transaction;
+ return transactionObject.prevXid;
+ }
+
+ @Override
+ protected void doResume(Object transaction, Object suspendedResources) throws TransactionException {
+ String xid = (String) suspendedResources;
+ TransactionContext.holdXID(xid);
+ }
+
+ @Override
+ protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException {
+ TransactionObject transactionObject = (TransactionObject) transaction;
+ transactionObject.currentXid = TransactionalManager.startTransactional();
+
+ TimeoutHolder.hold(definition);
+ }
+
+ @Override
+ protected void doCommit(DefaultTransactionStatus status) throws TransactionException {
+ TransactionObject transactionObject = (TransactionObject) status.getTransaction();
+ TransactionalManager.commit(transactionObject.currentXid);
+ transactionObject.clear();
+ }
+
+ @Override
+ protected void doRollback(DefaultTransactionStatus status) throws TransactionException {
+ TransactionObject transactionObject = (TransactionObject) status.getTransaction();
+ TransactionalManager.rollback(transactionObject.currentXid);
+ transactionObject.clear();
+ }
+
+ @Override
+ protected void doSetRollbackOnly(DefaultTransactionStatus status) throws TransactionException {
+ // 在多个事务嵌套时,子事务的传递方式为 REQUIRED(加入当前事务)
+ // 那么,当子事务抛出异常时,会调当前方法,而不是直接调用 doRollback
+ // 此时,需要标识 prevXid 进行 Rollback
+ TransactionObject transactionObject = (TransactionObject) status.getTransaction();
+ transactionObject.setRollbackOnly();
+ }
+
+ @Override
+ protected void doCleanupAfterCompletion(Object transaction) {
+ TimeoutHolder.clear();
+ }
+
+ static class TransactionObject extends JdbcTransactionObjectSupport {
+
+ private static final ThreadLocal ROLLBACK_ONLY_XIDS = new ThreadLocal<>();
+
+ private final String prevXid;
+ private String currentXid;
+
+ public TransactionObject(String prevXid) {
+ this.prevXid = prevXid;
+ }
+
+ public void setRollbackOnly() {
+ ROLLBACK_ONLY_XIDS.set(prevXid);
+ }
+
+ public void clear() {
+ ROLLBACK_ONLY_XIDS.remove();
+ }
+
+ @Override
+ public boolean isRollbackOnly() {
+ return currentXid != null && currentXid.equals(ROLLBACK_ONLY_XIDS.get());
+ }
+ }
+
+}
diff --git a/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/transaction/TimeoutHolder.java b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/transaction/TimeoutHolder.java
new file mode 100644
index 00000000..94854dce
--- /dev/null
+++ b/mybatis-flex-loveqq-starter/src/main/java/com/mybatisflex/loveqq/framework/boot/autoconfig/transaction/TimeoutHolder.java
@@ -0,0 +1,85 @@
+/*
+ * 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.loveqq.framework.boot.autoconfig.transaction;
+
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionTimedOutException;
+
+import java.util.Date;
+
+/**
+ * 事务定义管理器 用于更完整的实现Spring事务
+ * 仅支持传统事务不支持R2DBC事务
+ *
+ * @author Aliothmoon
+ * @author Michael
+ * @since 2024/10/25
+ */
+public final class TimeoutHolder {
+ private static final ThreadLocal TRANSACTION_DEADLINE = new ThreadLocal<>();
+
+ public static void hold(TransactionDefinition definition) {
+ if (definition == null) {
+ return;
+ }
+ int timeout = definition.getTimeout();
+ if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
+ Long deadline = System.currentTimeMillis() + timeout * 1000L;
+ TRANSACTION_DEADLINE.set(deadline);
+ }
+ }
+
+
+ /**
+ * 清除事务上下文
+ */
+ public static void clear() {
+ TRANSACTION_DEADLINE.remove();
+ }
+
+ /**
+ * 获取当前事务可用TTL
+ *
+ * @return int
+ */
+ public static Integer getTimeToLiveInSeconds() {
+ Long deadline = TRANSACTION_DEADLINE.get();
+ if (deadline == null) {
+ return null;
+ }
+ double diff = ((double) getTimeToLiveInMillis(deadline)) / 1000;
+ int secs = (int) Math.ceil(diff);
+ checkTransactionTimeout(secs <= 0, deadline);
+ return secs;
+ }
+
+
+ private static void checkTransactionTimeout(boolean deadlineReached, Long deadline) throws TransactionTimedOutException {
+ if (deadlineReached) {
+ throw new TransactionTimedOutException("Transaction timed out: deadline was " + new Date(deadline));
+ }
+ }
+
+
+ private static long getTimeToLiveInMillis(Long deadline) throws TransactionTimedOutException {
+ if (deadline == null) {
+ throw new IllegalStateException("No timeout specified for this resource holder");
+ }
+ long timeToLive = deadline - System.currentTimeMillis();
+ checkTransactionTimeout(timeToLive <= 0, deadline);
+ return timeToLive;
+ }
+}
diff --git a/mybatis-flex-loveqq-starter/src/main/resources/META-INF/k.factories b/mybatis-flex-loveqq-starter/src/main/resources/META-INF/k.factories
new file mode 100644
index 00000000..653abe35
--- /dev/null
+++ b/mybatis-flex-loveqq-starter/src/main/resources/META-INF/k.factories
@@ -0,0 +1,4 @@
+com.kfyty.loveqq.framework.core.autoconfig.annotation.EnableAutoConfiguration=\
+ com.mybatisflex.loveqq.framework.boot.autoconfig.MybatisFlexProperties,\
+ com.mybatisflex.loveqq.framework.boot.autoconfig.MybatisFlexAutoConfiguration,\
+ com.mybatisflex.loveqq.framework.boot.autoconfig.MybatisFlexAdminAutoConfiguration
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/pom.xml b/mybatis-flex-test/mybatis-flex-loveqq-test/pom.xml
new file mode 100644
index 00000000..5334a5a3
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/pom.xml
@@ -0,0 +1,63 @@
+
+
+ 4.0.0
+
+ mybatis-flex-test
+ com.mybatis-flex
+ 1.10.9
+
+
+ mybatis-flex-loveqq-test
+ jar
+
+
+ 8
+ 8
+
+
+
+
+ com.kfyty
+ loveqq-boot
+ ${loveqq.version}
+
+
+
+ com.mybatis-flex
+ mybatis-flex-loveqq-starter
+ ${mybatis-flex.version}
+
+
+
+ com.kfyty
+ loveqq-boot-starter-logback
+ ${loveqq.version}
+
+
+
+ org.yaml
+ snakeyaml
+ 2.2
+
+
+
+ com.alibaba.fastjson2
+ fastjson2
+ 2.0.32
+
+
+
+ com.mysql
+ mysql-connector-j
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.5.2
+ test
+
+
+
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/DataSourceInitListener.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/DataSourceInitListener.java
new file mode 100644
index 00000000..ce1575b9
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/DataSourceInitListener.java
@@ -0,0 +1,34 @@
+/*
+ * 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.loveqq.test;
+
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Component;
+import com.kfyty.loveqq.framework.core.event.ApplicationListener;
+import com.kfyty.loveqq.framework.core.event.ContextRefreshedEvent;
+import com.mybatisflex.core.FlexGlobalConfig;
+import com.mybatisflex.core.datasource.FlexDataSource;
+
+@Component
+public class DataSourceInitListener implements ApplicationListener {
+
+ @Override
+ public void onApplicationEvent(ContextRefreshedEvent event) {
+ FlexDataSource dataSource = FlexGlobalConfig.getDefaultConfig().getDataSource();
+ System.out.println("onApplicationEvent>>>> datasource:" + dataSource);
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/MyConfigurationCustomizer.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/MyConfigurationCustomizer.java
new file mode 100644
index 00000000..b54c1157
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/MyConfigurationCustomizer.java
@@ -0,0 +1,59 @@
+/*
+ * 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.loveqq.test;
+
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Configuration;
+import com.mybatisflex.core.FlexGlobalConfig;
+import com.mybatisflex.core.datasource.DataSourceDecipher;
+import com.mybatisflex.core.datasource.DataSourceManager;
+import com.mybatisflex.core.mybatis.FlexConfiguration;
+import com.mybatisflex.loveqq.framework.boot.autoconfig.customize.ConfigurationCustomizer;
+import com.mybatisflex.loveqq.framework.boot.autoconfig.customize.MyBatisFlexCustomizer;
+import com.mybatisflex.loveqq.test.unmapped.MyUnMappedColumnHandler;
+import org.apache.ibatis.logging.stdout.StdOutImpl;
+
+@Configuration
+public class MyConfigurationCustomizer implements ConfigurationCustomizer, MyBatisFlexCustomizer {
+
+ @Override
+ public void customize(FlexConfiguration configuration) {
+ System.out.println(">>>>>>> MyConfigurationCustomizer.customize() invoked");
+ configuration.setLogImpl(StdOutImpl.class);
+ }
+
+// @Bean
+// public DataSourceDecipher decipher() {
+// DataSourceDecipher decipher = new DataSourceDecipher() {
+// @Override
+// public String decrypt(DataSourceProperty property, String value) {
+// System.out.println(">>>>>> decipher.decrypt");
+// return value;
+// }
+// };
+// return decipher;
+// }
+
+ @Override
+ public void customize(FlexGlobalConfig globalConfig) {
+ DataSourceDecipher decipher = (property, value) -> {
+ System.out.println(">>>>>> decipher.decrypt");
+ return value;
+ };
+ DataSourceManager.setDecipher(decipher);
+ globalConfig.setUnMappedColumnHandler(new MyUnMappedColumnHandler());
+ }
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/SampleApplication.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/SampleApplication.java
new file mode 100644
index 00000000..c8294a92
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/SampleApplication.java
@@ -0,0 +1,64 @@
+/*
+ * 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.loveqq.test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.kfyty.loveqq.framework.boot.K;
+import com.kfyty.loveqq.framework.boot.data.orm.mybatis.autoconfig.annotation.MapperScan;
+import com.kfyty.loveqq.framework.core.autoconfig.CommandLineRunner;
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Autowired;
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Bean;
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.BootApplication;
+import com.kfyty.loveqq.framework.core.autoconfig.condition.annotation.ConditionalOnMissingBean;
+import com.kfyty.loveqq.framework.core.event.ApplicationListener;
+import com.kfyty.loveqq.framework.core.event.ContextRefreshedEvent;
+import com.kfyty.loveqq.framework.core.utils.JsonUtil;
+import com.mybatisflex.core.audit.AuditManager;
+import com.mybatisflex.core.audit.ConsoleMessageCollector;
+import com.mybatisflex.core.audit.MessageCollector;
+import com.mybatisflex.loveqq.test.mapper.AccountMapper;
+
+@BootApplication
+@MapperScan("com.mybatisflex.loveqq.test.mapper.**.*")
+public class SampleApplication implements CommandLineRunner, ApplicationListener {
+ @Autowired
+ private AccountMapper accountMapper;
+
+ @Override
+ public void run(String... args) throws Exception {
+ System.out.println(this.accountMapper.selectOneById(1));
+ }
+
+ public static void main(String[] args) {
+ K.start(SampleApplication.class, args);
+ }
+
+ @Override
+ public void onApplicationEvent(ContextRefreshedEvent event) {
+ System.out.println("onApplicationEvent");
+ // 开启审计功能
+ AuditManager.setAuditEnable(true);
+ // 设置 SQL 审计收集器
+ MessageCollector collector = new ConsoleMessageCollector((sql, tookTimeMillis) -> System.out.println(sql));
+ AuditManager.setMessageCollector(collector);
+ }
+
+ @ConditionalOnMissingBean
+ @Bean(resolveNested = false, independent = true)
+ public ObjectMapper objectMapper() {
+ return JsonUtil.configure();
+ }
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/alias/BaseEntity.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/alias/BaseEntity.java
new file mode 100644
index 00000000..a94de327
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/alias/BaseEntity.java
@@ -0,0 +1,77 @@
+/*
+ * 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.loveqq.test.alias;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 父类。
+ *
+ * @author 王帅
+ * @since 2023-11-16
+ */
+public abstract class BaseEntity implements Serializable {
+
+ private Date createTime;
+ private String createBy;
+ private Date updateTime;
+ private String updateBy;
+
+ public Date getCreateTime() {
+ return createTime;
+ }
+
+ public void setCreateTime(Date createTime) {
+ this.createTime = createTime;
+ }
+
+ public String getCreateBy() {
+ return createBy;
+ }
+
+ public void setCreateBy(String createBy) {
+ this.createBy = createBy;
+ }
+
+ public Date getUpdateTime() {
+ return updateTime;
+ }
+
+ public void setUpdateTime(Date updateTime) {
+ this.updateTime = updateTime;
+ }
+
+ public String getUpdateBy() {
+ return updateBy;
+ }
+
+ public void setUpdateBy(String updateBy) {
+ this.updateBy = updateBy;
+ }
+
+ @Override
+ public String toString() {
+ return "BaseEntity{" +
+ "createTime=" + createTime +
+ ", createBy='" + createBy + '\'' +
+ ", updateTime=" + updateTime +
+ ", updateBy='" + updateBy + '\'' +
+ '}';
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/alias/DeptVO.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/alias/DeptVO.java
new file mode 100644
index 00000000..edc4bd74
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/alias/DeptVO.java
@@ -0,0 +1,57 @@
+/*
+ * 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.loveqq.test.alias;
+
+import com.mybatisflex.annotation.TableRef;
+
+/**
+ * 部门。
+ *
+ * @author 王帅
+ * @since 2023-11-16
+ */
+@TableRef(SysDept.class)
+public class DeptVO extends BaseEntity {
+
+ private Integer id;
+ private String deptName;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getDeptName() {
+ return deptName;
+ }
+
+ public void setDeptName(String deptName) {
+ this.deptName = deptName;
+ }
+
+ @Override
+ public String toString() {
+ return "SysDept{" +
+ "id=" + id +
+ ", deptName='" + deptName + '\'' +
+ '}' + super.toString();
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/alias/RoleVO.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/alias/RoleVO.java
new file mode 100644
index 00000000..3db6f4fa
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/alias/RoleVO.java
@@ -0,0 +1,67 @@
+/*
+ * 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.loveqq.test.alias;
+
+import com.mybatisflex.annotation.TableRef;
+
+/**
+ * 角色。
+ *
+ * @author 王帅
+ * @since 2023-11-16
+ */
+@TableRef(SysRole.class)
+public class RoleVO extends BaseEntity {
+
+ private Integer id;
+ private String roleKey;
+ private String roleName;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getRoleKey() {
+ return roleKey;
+ }
+
+ public void setRoleKey(String roleKey) {
+ this.roleKey = roleKey;
+ }
+
+ public String getRoleName() {
+ return roleName;
+ }
+
+ public void setRoleName(String roleName) {
+ this.roleName = roleName;
+ }
+
+ @Override
+ public String toString() {
+ return "SysRole{" +
+ "id=" + id +
+ ", roleKey='" + roleKey + '\'' +
+ ", roleName='" + roleName + '\'' +
+ '}' + super.toString();
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/alias/SysDept.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/alias/SysDept.java
new file mode 100644
index 00000000..88f0052e
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/alias/SysDept.java
@@ -0,0 +1,59 @@
+/*
+ * 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.loveqq.test.alias;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.KeyType;
+import com.mybatisflex.annotation.Table;
+
+/**
+ * 部门。
+ *
+ * @author 王帅
+ * @since 2023-11-16
+ */
+@Table("sys_dept")
+public class SysDept extends BaseEntity {
+
+ @Id(keyType = KeyType.Auto)
+ private Integer id;
+ private String deptName;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getDeptName() {
+ return deptName;
+ }
+
+ public void setDeptName(String deptName) {
+ this.deptName = deptName;
+ }
+
+ @Override
+ public String toString() {
+ return "SysDept{" +
+ "id=" + id +
+ ", deptName='" + deptName + '\'' +
+ '}' + super.toString();
+ }
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/alias/SysRole.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/alias/SysRole.java
new file mode 100644
index 00000000..186cde81
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/alias/SysRole.java
@@ -0,0 +1,70 @@
+/*
+ * 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.loveqq.test.alias;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.KeyType;
+import com.mybatisflex.annotation.Table;
+
+/**
+ * 角色。
+ *
+ * @author 王帅
+ * @since 2023-11-16
+ */
+@Table("sys_role")
+public class SysRole extends BaseEntity {
+
+ @Id(keyType = KeyType.Auto)
+ private Integer id;
+ private String roleKey;
+ private String roleName;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getRoleKey() {
+ return roleKey;
+ }
+
+ public void setRoleKey(String roleKey) {
+ this.roleKey = roleKey;
+ }
+
+ public String getRoleName() {
+ return roleName;
+ }
+
+ public void setRoleName(String roleName) {
+ this.roleName = roleName;
+ }
+
+ @Override
+ public String toString() {
+ return "SysRole{" +
+ "id=" + id +
+ ", roleKey='" + roleKey + '\'' +
+ ", roleName='" + roleName + '\'' +
+ '}' + super.toString();
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/alias/SysUser.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/alias/SysUser.java
new file mode 100644
index 00000000..84df58ee
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/alias/SysUser.java
@@ -0,0 +1,106 @@
+/*
+ * 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.loveqq.test.alias;
+
+import com.mybatisflex.annotation.ColumnAlias;
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.KeyType;
+import com.mybatisflex.annotation.Table;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 用户。
+ *
+ * @author 王帅
+ * @since 2023-11-16
+ */
+@Table("sys_user")
+public class SysUser extends BaseEntity {
+
+ @Id(keyType = KeyType.Auto)
+ private Integer id;
+ private String userName;
+ @ColumnAlias("user_age")
+ private Integer age;
+ private Date birthday;
+
+ private List roleList;
+ private List deptList;
+
+ public List getRoleList() {
+ return roleList;
+ }
+
+ public void setRoleList(List roleList) {
+ this.roleList = roleList;
+ }
+
+ public List getDeptList() {
+ return deptList;
+ }
+
+ public void setDeptList(List deptList) {
+ this.deptList = deptList;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ public Integer getAge() {
+ return age;
+ }
+
+ public void setAge(Integer age) {
+ this.age = age;
+ }
+
+ public Date getBirthday() {
+ return birthday;
+ }
+
+ public void setBirthday(Date birthday) {
+ this.birthday = birthday;
+ }
+
+ @Override
+ public String toString() {
+ return "SysUser{" +
+ "id=" + id +
+ ", userName='" + userName + '\'' +
+ ", age=" + age +
+ ", birthday=" + birthday +
+ ", roleList=" + roleList +
+ ", deptList=" + deptList +
+ '}' + super.toString();
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/alias/UserVO.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/alias/UserVO.java
new file mode 100644
index 00000000..07e01471
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/alias/UserVO.java
@@ -0,0 +1,103 @@
+/*
+ * 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.loveqq.test.alias;
+
+import com.mybatisflex.annotation.ColumnAlias;
+import com.mybatisflex.annotation.TableRef;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 用户。
+ *
+ * @author 王帅
+ * @since 2023-11-16
+ */
+@TableRef(SysUser.class)
+public class UserVO extends BaseEntity {
+
+ private Integer id;
+ private String userName;
+ @ColumnAlias("user_age")
+ private Integer age;
+ private Date birthday;
+
+ private List roleList;
+ private List deptList;
+
+ public List getRoleList() {
+ return roleList;
+ }
+
+ public void setRoleList(List roleList) {
+ this.roleList = roleList;
+ }
+
+ public List getDeptList() {
+ return deptList;
+ }
+
+ public void setDeptList(List deptList) {
+ this.deptList = deptList;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ public Integer getAge() {
+ return age;
+ }
+
+ public void setAge(Integer age) {
+ this.age = age;
+ }
+
+ public Date getBirthday() {
+ return birthday;
+ }
+
+ public void setBirthday(Date birthday) {
+ this.birthday = birthday;
+ }
+
+ @Override
+ public String toString() {
+ return "SysUser{" +
+ "id=" + id +
+ ", userName='" + userName + '\'' +
+ ", age=" + age +
+ ", birthday=" + birthday +
+ ", roleList=" + roleList +
+ ", deptList=" + deptList +
+ '}' + super.toString();
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/entity/Inner.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/entity/Inner.java
new file mode 100644
index 00000000..9fbb5b46
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/entity/Inner.java
@@ -0,0 +1,55 @@
+/*
+ * 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.loveqq.test.entity;
+
+import com.mybatisflex.annotation.Table;
+
+/**
+ * @author 王帅
+ * @since 2023-07-01
+ */
+@Table("tb_inner")
+public class Inner {
+
+ private Integer id;
+ private String type;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ @Override
+ public String toString() {
+ return "Inner{" +
+ "id=" + id +
+ ", type='" + type + '\'' +
+ '}';
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/entity/Outer.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/entity/Outer.java
new file mode 100644
index 00000000..2cf4ab3c
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/entity/Outer.java
@@ -0,0 +1,64 @@
+/*
+ * 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.loveqq.test.entity;
+
+import com.mybatisflex.annotation.ColumnAlias;
+import com.mybatisflex.annotation.Table;
+import com.mybatisflex.loveqq.test.model.IdEntity;
+
+/**
+ * @author 王帅
+ * @since 2023-06-16
+ */
+@Table("tb_outer")
+public class Outer extends IdEntity {
+
+ private String name;
+ private Inner inner;
+
+ @Override
+ @ColumnAlias("outer_id")
+ public Integer getId() {
+ return super.getId();
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Inner getInner() {
+ return inner;
+ }
+
+ public void setInner(Inner inner) {
+ this.inner = inner;
+ }
+
+ @Override
+ public String toString() {
+ return "Outer{" +
+ "id='" + id + '\'' +
+ ", name='" + name + '\'' +
+ ", inner=" + inner +
+ '}';
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/entity/TestEntity.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/entity/TestEntity.java
new file mode 100644
index 00000000..ab2824f3
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/entity/TestEntity.java
@@ -0,0 +1,28 @@
+/*
+ * 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.loveqq.test.entity;
+
+import com.mybatisflex.annotation.Table;
+
+/**
+ * @author 王帅
+ * @since 2023-06-16
+ */
+@Table("test")
+public class TestEntity {
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/listener/missingListenerFix/AccountAgeInsertListener.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/listener/missingListenerFix/AccountAgeInsertListener.java
new file mode 100644
index 00000000..6ac30042
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/listener/missingListenerFix/AccountAgeInsertListener.java
@@ -0,0 +1,13 @@
+package com.mybatisflex.loveqq.test.listener.missingListenerFix;
+
+import com.mybatisflex.annotation.InsertListener;
+import com.mybatisflex.loveqq.test.model.AccountMissingListenerTestModel;
+
+public class AccountAgeInsertListener implements InsertListener {
+
+ @Override
+ public void onInsert(Object entity) {
+ AccountMissingListenerTestModel model = (AccountMissingListenerTestModel) entity;
+ model.setAge(18);
+ }
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/listener/missingListenerFix/AccountAgeInsertListenerFlag.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/listener/missingListenerFix/AccountAgeInsertListenerFlag.java
new file mode 100644
index 00000000..a03f05eb
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/listener/missingListenerFix/AccountAgeInsertListenerFlag.java
@@ -0,0 +1,5 @@
+package com.mybatisflex.loveqq.test.listener.missingListenerFix;
+
+public interface AccountAgeInsertListenerFlag {
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/listener/missingListenerFix/AccountTableAnnoInsertListener.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/listener/missingListenerFix/AccountTableAnnoInsertListener.java
new file mode 100644
index 00000000..fbbc136f
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/listener/missingListenerFix/AccountTableAnnoInsertListener.java
@@ -0,0 +1,13 @@
+package com.mybatisflex.loveqq.test.listener.missingListenerFix;
+
+import com.mybatisflex.annotation.InsertListener;
+import com.mybatisflex.loveqq.test.model.AccountMissingListenerTestModel;
+
+public class AccountTableAnnoInsertListener implements InsertListener {
+
+ @Override
+ public void onInsert(Object entity) {
+ AccountMissingListenerTestModel model = (AccountMissingListenerTestModel) entity;
+ model.setUserName("测试缺失的监听器-userName");
+ }
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/listener/missingListenerFix/BaseLogicDelete.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/listener/missingListenerFix/BaseLogicDelete.java
new file mode 100644
index 00000000..7c581bad
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/listener/missingListenerFix/BaseLogicDelete.java
@@ -0,0 +1,18 @@
+package com.mybatisflex.loveqq.test.listener.missingListenerFix;
+
+import com.mybatisflex.annotation.Column;
+
+public class BaseLogicDelete implements LogicDeleteInsertListenerFlag {
+
+ @Column(isLogicDelete = true)
+ private Boolean isDelete;
+
+ public Boolean getDelete() {
+ return isDelete;
+ }
+
+ public void setDelete(Boolean delete) {
+ isDelete = delete;
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/listener/missingListenerFix/LogicDeleteInsertListener.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/listener/missingListenerFix/LogicDeleteInsertListener.java
new file mode 100644
index 00000000..edb00098
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/listener/missingListenerFix/LogicDeleteInsertListener.java
@@ -0,0 +1,12 @@
+package com.mybatisflex.loveqq.test.listener.missingListenerFix;
+
+import com.mybatisflex.annotation.InsertListener;
+
+public class LogicDeleteInsertListener implements InsertListener {
+
+ @Override
+ public void onInsert(Object entity) {
+ BaseLogicDelete logicDeleteEntity = (BaseLogicDelete) entity;
+ logicDeleteEntity.setDelete(false);
+ }
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/listener/missingListenerFix/LogicDeleteInsertListenerFlag.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/listener/missingListenerFix/LogicDeleteInsertListenerFlag.java
new file mode 100644
index 00000000..acd52c60
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/listener/missingListenerFix/LogicDeleteInsertListenerFlag.java
@@ -0,0 +1,5 @@
+package com.mybatisflex.loveqq.test.listener.missingListenerFix;
+
+public interface LogicDeleteInsertListenerFlag {
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/AccountMapper.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/AccountMapper.java
new file mode 100644
index 00000000..6981033b
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/AccountMapper.java
@@ -0,0 +1,28 @@
+/*
+ * 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.loveqq.test.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.mybatisflex.loveqq.test.model.Account;
+
+/**
+ * @author 王帅
+ * @since 2023-06-23
+ */
+public interface AccountMapper extends BaseMapper {
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/AccountMissingListenerTestModelMapper.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/AccountMissingListenerTestModelMapper.java
new file mode 100644
index 00000000..1499f773
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/AccountMissingListenerTestModelMapper.java
@@ -0,0 +1,10 @@
+package com.mybatisflex.loveqq.test.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.mybatisflex.loveqq.test.model.AccountMissingListenerTestModel;
+
+/**
+ * @author noear 2024/12/24 created
+ */
+public interface AccountMissingListenerTestModelMapper extends BaseMapper {
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/ArticleMapper.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/ArticleMapper.java
new file mode 100644
index 00000000..795fd494
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/ArticleMapper.java
@@ -0,0 +1,10 @@
+package com.mybatisflex.loveqq.test.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.mybatisflex.loveqq.test.model.Article;
+
+/**
+ * @author noear 2024/12/24 created
+ */
+public interface ArticleMapper extends BaseMapper {
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/FieldMappingInnerMapper.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/FieldMappingInnerMapper.java
new file mode 100644
index 00000000..f437f87e
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/FieldMappingInnerMapper.java
@@ -0,0 +1,10 @@
+package com.mybatisflex.loveqq.test.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.mybatisflex.loveqq.test.model.FieldMappingInner;
+
+/**
+ * @author noear 2024/12/24 created
+ */
+public interface FieldMappingInnerMapper extends BaseMapper {
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/FieldMappingMapper.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/FieldMappingMapper.java
new file mode 100644
index 00000000..ac12666f
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/FieldMappingMapper.java
@@ -0,0 +1,10 @@
+package com.mybatisflex.loveqq.test.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.mybatisflex.loveqq.test.model.FieldMapping;
+
+/**
+ * @author noear 2024/12/24 created
+ */
+public interface FieldMappingMapper extends BaseMapper {
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/GoodMapper.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/GoodMapper.java
new file mode 100644
index 00000000..22403503
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/GoodMapper.java
@@ -0,0 +1,10 @@
+package com.mybatisflex.loveqq.test.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.mybatisflex.loveqq.test.model.Good;
+
+/**
+ * @author noear 2024/12/24 created
+ */
+public interface GoodMapper extends BaseMapper {
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/InnerMapper.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/InnerMapper.java
new file mode 100644
index 00000000..3f477854
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/InnerMapper.java
@@ -0,0 +1,10 @@
+package com.mybatisflex.loveqq.test.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.mybatisflex.loveqq.test.entity.Inner;
+
+/**
+ * @author noear 2024/12/24 created
+ */
+public interface InnerMapper extends BaseMapper {
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/MyAccountMapper.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/MyAccountMapper.java
new file mode 100644
index 00000000..bef52c32
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/MyAccountMapper.java
@@ -0,0 +1,40 @@
+/*
+ * 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.loveqq.test.mapper;
+
+import com.mybatisflex.loveqq.test.model.Account;
+import com.mybatisflex.loveqq.test.model.AccountDto;
+import com.mybatisflex.loveqq.test.model.AccountView;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.Map;
+
+public interface MyAccountMapper extends AccountMapper {
+
+
+ AccountDto selectByName(@Param("name") String name);
+
+ AccountView selectViewObject();
+
+ @Select("select * from tb_account where id = #{id} and id =#{id}")
+ Account selectById(@Param("id") Object id);
+
+ @Select("select * from tb_account where id = #{id}")
+ Map selectMapById(@Param("id") Object id);
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/OuterMapper.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/OuterMapper.java
new file mode 100644
index 00000000..ebc23c32
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/OuterMapper.java
@@ -0,0 +1,10 @@
+package com.mybatisflex.loveqq.test.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.mybatisflex.loveqq.test.entity.Outer;
+
+/**
+ * @author noear 2024/12/24 created
+ */
+public interface OuterMapper extends BaseMapper {
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/PatientMapper.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/PatientMapper.java
new file mode 100644
index 00000000..dc2fd7e9
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/PatientMapper.java
@@ -0,0 +1,10 @@
+package com.mybatisflex.loveqq.test.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.mybatisflex.loveqq.test.model.Patient;
+
+/**
+ * @author noear 2024/12/24 created
+ */
+public interface PatientMapper extends BaseMapper {
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/SysUserMapper.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/SysUserMapper.java
new file mode 100644
index 00000000..13acf7b4
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/SysUserMapper.java
@@ -0,0 +1,10 @@
+package com.mybatisflex.loveqq.test.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.mybatisflex.loveqq.test.alias.SysUser;
+
+/**
+ * @author noear 2024/12/24 created
+ */
+public interface SysUserMapper extends BaseMapper {
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/TbClassMapper.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/TbClassMapper.java
new file mode 100644
index 00000000..809de86e
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/TbClassMapper.java
@@ -0,0 +1,7 @@
+package com.mybatisflex.loveqq.test.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.mybatisflex.loveqq.test.model.TbClass;
+
+public interface TbClassMapper extends BaseMapper {
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/UnmappedUserMapper.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/UnmappedUserMapper.java
new file mode 100644
index 00000000..8634189c
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/UnmappedUserMapper.java
@@ -0,0 +1,10 @@
+package com.mybatisflex.loveqq.test.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.mybatisflex.loveqq.test.model.UnmappedUser;
+
+/**
+ * @author noear 2024/12/24 created
+ */
+public interface UnmappedUserMapper extends BaseMapper {
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/UserMapper.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/UserMapper.java
new file mode 100644
index 00000000..95a71afd
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/mapper/UserMapper.java
@@ -0,0 +1,10 @@
+package com.mybatisflex.loveqq.test.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.mybatisflex.loveqq.test.model.User;
+
+/**
+ * @author noear 2024/12/24 created
+ */
+public interface UserMapper extends BaseMapper {
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Account.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Account.java
new file mode 100644
index 00000000..ccd2a571
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Account.java
@@ -0,0 +1,122 @@
+/*
+ * 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.loveqq.test.model;
+
+import com.mybatisflex.annotation.Column;
+import com.mybatisflex.annotation.Table;
+
+import java.util.Date;
+
+/**
+ * 账户信息。
+ */
+@Table(value = "tb_account", onSet = AccountOnSetListener.class)
+public class Account extends BaseEntity {
+
+ /*@Id(keyType = KeyType.Auto)
+ private Long id;*/
+ //private String userName;
+ /**
+ * 年龄。
+ */
+ private Integer age;
+
+ /**
+ * 生日。
+ */
+ private Date birthday;
+
+ /**
+ * 逻辑删除。
+ */
+ @Column(isLogicDelete = true)
+ private Boolean isDelete;
+
+ @Column(ignore = true)
+ private String anotherColumn;
+
+// private Gender gender;
+//
+// public Gender getGender() {
+// return gender;
+// }
+//
+// public void setGender(Gender gender) {
+// this.gender = gender;
+// }
+
+ /*public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }*/
+
+ /*public String getUserName() {
+ return userName;
+ }
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }*/
+
+ public Integer getAge() {
+ return age;
+ }
+
+ public void setAge(Integer age) {
+ this.age = age;
+ }
+
+ public Date getBirthday() {
+ return birthday;
+ }
+
+ public void setBirthday(Date birthday) {
+ this.birthday = birthday;
+ }
+
+ public Boolean getDelete() {
+ return isDelete;
+ }
+
+ public void setDelete(Boolean delete) {
+ isDelete = delete;
+ }
+
+ public String getAnotherColumn() {
+ return anotherColumn;
+ }
+
+ public void setAnotherColumn(String anotherColumn) {
+ this.anotherColumn = anotherColumn;
+ }
+
+ @Override
+ public String toString() {
+ return "Account{" +
+ "id=" + id +
+ ", userName='" + userName + '\'' +
+ ", age=" + age +
+ ", birthday=" + birthday +
+ ", isDelete=" + isDelete +
+ ", anotherColumn=" + anotherColumn +
+// ", roles=" + roles +
+ '}';
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/AccountDto.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/AccountDto.java
new file mode 100644
index 00000000..8944c919
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/AccountDto.java
@@ -0,0 +1,37 @@
+package com.mybatisflex.loveqq.test.model;
+
+import java.util.List;
+import java.util.Map;
+
+public class AccountDto {
+
+ private Long id;
+
+ private String name;
+
+ private List> other;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public List> getOther() {
+ return other;
+ }
+
+ public void setOther(List> other) {
+ this.other = other;
+ }
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/AccountMissingListenerTestModel.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/AccountMissingListenerTestModel.java
new file mode 100644
index 00000000..398fea48
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/AccountMissingListenerTestModel.java
@@ -0,0 +1,89 @@
+package com.mybatisflex.loveqq.test.model;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.KeyType;
+import com.mybatisflex.annotation.Table;
+import com.mybatisflex.loveqq.test.listener.missingListenerFix.AccountAgeInsertListenerFlag;
+import com.mybatisflex.loveqq.test.listener.missingListenerFix.AccountTableAnnoInsertListener;
+import com.mybatisflex.loveqq.test.listener.missingListenerFix.BaseLogicDelete;
+
+import java.util.Objects;
+
+/**
+ * 缺失的监听器测试
+ *
+ * @author Ice 2023/10/23
+ * @version 1.0
+ */
+@Table(value = "tb_account", onInsert = AccountTableAnnoInsertListener.class)
+public class AccountMissingListenerTestModel extends BaseLogicDelete implements AccountAgeInsertListenerFlag {
+
+ /**
+ * 主键。
+ */
+ @Id(keyType = KeyType.Auto)
+ private Long id;
+
+ private String userName;
+
+ private Integer age;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ public Integer getAge() {
+ return age;
+ }
+
+ public void setAge(Integer age) {
+ this.age = age;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ AccountMissingListenerTestModel that = (AccountMissingListenerTestModel) o;
+ return Objects.equals(id, that.id) && Objects.equals(userName, that.userName) && Objects.equals(age, that.age) && Objects.equals(getDelete(), that.getDelete());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, userName, age, getDelete());
+ }
+
+ @Override
+ public String toString() {
+ return "AccountMissingListenerTestModel{" +
+ "id=" + id +
+ ", userName='" + userName + '\'' +
+ ", age=" + age +
+ ", isDelete=" + getDelete() +
+ '}';
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/AccountOnSetListener.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/AccountOnSetListener.java
new file mode 100644
index 00000000..e2993dbc
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/AccountOnSetListener.java
@@ -0,0 +1,29 @@
+/*
+ * 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.loveqq.test.model;
+
+import com.mybatisflex.annotation.SetListener;
+
+public class AccountOnSetListener implements SetListener {
+
+ @Override
+ public Object onSet(Object entity, String property, Object value) {
+ System.out.println(">>>>>>> property: " + property + " value:" + value);
+ return value;
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/AccountVO.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/AccountVO.java
new file mode 100644
index 00000000..87c2e7f7
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/AccountVO.java
@@ -0,0 +1,70 @@
+/*
+ * 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.loveqq.test.model;
+
+import java.util.Date;
+
+public class AccountVO extends IdEntity {
+
+ private Integer age;
+ private Date birthday;
+
+ private Gender gender;
+ private RoleVO2 role;
+
+ public RoleVO2 getRole() {
+ return role;
+ }
+
+ public void setRole(RoleVO2 role) {
+ this.role = role;
+ }
+
+ public Gender getGender() {
+ return gender;
+ }
+
+ public void setGender(Gender gender) {
+ this.gender = gender;
+ }
+
+ public Integer getAge() {
+ return age;
+ }
+
+ public void setAge(Integer age) {
+ this.age = age;
+ }
+
+ public Date getBirthday() {
+ return birthday;
+ }
+
+ public void setBirthday(Date birthday) {
+ this.birthday = birthday;
+ }
+
+ @Override
+ public String toString() {
+ return "Account{" +
+ "id=" + id +
+ ", age=" + age +
+ ", birthday=" + birthday +
+ ", roleName=" + role +
+ '}';
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/AccountVO2.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/AccountVO2.java
new file mode 100644
index 00000000..9b4e3f1f
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/AccountVO2.java
@@ -0,0 +1,76 @@
+/*
+ * 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.loveqq.test.model;
+
+import com.mybatisflex.annotation.ColumnAlias;
+
+/**
+ * @author 王帅
+ * @since 2023-06-30
+ */
+public class AccountVO2 extends IdEntity {
+
+ private Integer age;
+
+ @ColumnAlias("account_name")
+ private String userName;
+
+
+ private User user;
+
+
+ @Override
+ @ColumnAlias("account_id")
+ public Long getId() {
+ return super.getId();
+ }
+
+ public Integer getAge() {
+ return age;
+ }
+
+ public void setAge(Integer age) {
+ this.age = age;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ public User getUser() {
+ return user;
+ }
+
+ public void setUser(User user) {
+ this.user = user;
+ }
+
+ @Override
+ public String toString() {
+ return "AccountVO2{" +
+ "id=" + id +
+ ", age=" + age +
+ ", userName='" + userName + '\'' +
+ ", user='" + user + '\'' +
+ '}';
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/AccountView.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/AccountView.java
new file mode 100644
index 00000000..f9dfdb33
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/AccountView.java
@@ -0,0 +1,54 @@
+package com.mybatisflex.loveqq.test.model;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 复杂查询接收类
+ * 演示问题 。
+ *
+ * @author 王帅
+ * @since 2024-10-03
+ */
+@SuppressWarnings({"LombokGetterMayBeUsed", "LombokSetterMayBeUsed"})
+public class AccountView {
+
+ private Long id;
+ private String userName;
+ private Date birthday;
+
+ private List accountList;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ public Date getBirthday() {
+ return birthday;
+ }
+
+ public void setBirthday(Date birthday) {
+ this.birthday = birthday;
+ }
+
+ public List getAccountList() {
+ return accountList;
+ }
+
+ public void setAccountList(List accountList) {
+ this.accountList = accountList;
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Article.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Article.java
new file mode 100644
index 00000000..45387da8
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Article.java
@@ -0,0 +1,71 @@
+/*
+ * 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.loveqq.test.model;
+
+import com.mybatisflex.annotation.Column;
+import com.mybatisflex.annotation.Table;
+
+@Table(value = "tb_article")
+public class Article extends IdEntity {
+
+ private static final long serialVersionUID = 1L;
+
+
+ private Long accountId;
+
+ private String title;
+
+ private String content;
+
+ @Column(isLogicDelete = true)
+ private Boolean isDelete;
+
+ public Long getAccountId() {
+ return accountId;
+ }
+
+ public void setAccountId(Long accountId) {
+ this.accountId = accountId;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getContent() {
+ return content;
+ }
+
+ public void setContent(String content) {
+ this.content = content;
+ }
+
+ @Override
+ public String toString() {
+ return "Article{" +
+ "id=" + id +
+ ", accountId=" + accountId +
+ ", title='" + title + '\'' +
+ ", content='" + content + '\'' +
+ '}';
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/BaseEntity.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/BaseEntity.java
new file mode 100644
index 00000000..d7ef41a1
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/BaseEntity.java
@@ -0,0 +1,57 @@
+/*
+ * 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.loveqq.test.model;
+
+import com.mybatisflex.annotation.Column;
+
+import java.util.List;
+
+/**
+ * @author 王帅
+ * @since 2.0
+ */
+public class BaseEntity extends IdEntity {
+
+ /**
+ * 用户名。
+ */
+ protected T userName;
+
+ /**
+ * 用户角色。
+ */
+ @Column(ignore = true)
+ protected List roles;
+
+ public List getRoles() {
+ return roles;
+ }
+
+ public void setRoles(List roles) {
+ this.roles = roles;
+ }
+
+
+ public T getUserName() {
+ return userName;
+ }
+
+ public void setUserName(T userName) {
+ this.userName = userName;
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Disease.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Disease.java
new file mode 100644
index 00000000..8882f4b4
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Disease.java
@@ -0,0 +1,45 @@
+package com.mybatisflex.loveqq.test.model;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.KeyType;
+import com.mybatisflex.annotation.Table;
+
+import java.io.Serializable;
+
+/**
+ * 疾病
+ *
+ * @author Ice 2023/09/26
+ * @version 1.0
+ */
+@Table(value = "tb_disease")
+public class Disease implements Serializable {
+ private static final long serialVersionUID = -3195530228167432902L;
+
+ /**
+ * ID
+ */
+ @Id(keyType = KeyType.Generator, value = "uuid")
+ private String diseaseId;
+
+ /**
+ * 疾病名称
+ */
+ private String name;
+
+ public String getDiseaseId() {
+ return diseaseId;
+ }
+
+ public void setDiseaseId(String diseaseId) {
+ this.diseaseId = diseaseId;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/FieldMapping.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/FieldMapping.java
new file mode 100644
index 00000000..9a81414f
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/FieldMapping.java
@@ -0,0 +1,66 @@
+package com.mybatisflex.loveqq.test.model;
+
+
+import com.mybatisflex.annotation.Column;
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.KeyType;
+import com.mybatisflex.annotation.Table;
+import com.mybatisflex.core.activerecord.Model;
+import com.mybatisflex.core.keygen.KeyGenerators;
+
+import java.util.Date;
+import java.util.Objects;
+
+
+@Table("field_mapping")
+public class FieldMapping extends Model {
+ @Id(keyType = KeyType.Generator,value = KeyGenerators.snowFlakeId)
+ private String id;
+ @Column(ignore = true)
+ private Date innerDate;
+
+ public String getId() {
+ return id;
+ }
+
+ public static FieldMapping create() {
+ return new FieldMapping();
+ }
+
+ public FieldMapping setId(String id) {
+ this.id = id;
+ return this;
+ }
+
+ public Date getInnerDate() {
+ return innerDate;
+ }
+
+ public FieldMapping setInnerDate(Date innerDate) {
+ this.innerDate = innerDate;
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ FieldMapping that = (FieldMapping) o;
+ return Objects.equals(id, that.id) && Objects.equals(innerDate, that.innerDate);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, innerDate);
+ }
+
+ @Override
+ public String toString() {
+ return "FieldMapping{" +
+ "id='" + id + '\'' +
+ ", innerDate=" + innerDate +
+ '}';
+ }
+
+}
+
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/FieldMappingInner.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/FieldMappingInner.java
new file mode 100644
index 00000000..395bec52
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/FieldMappingInner.java
@@ -0,0 +1,71 @@
+package com.mybatisflex.loveqq.test.model;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.KeyType;
+import com.mybatisflex.annotation.Table;
+import com.mybatisflex.core.activerecord.Model;
+import com.mybatisflex.core.keygen.KeyGenerators;
+
+import java.util.Date;
+import java.util.Objects;
+
+@Table("field_mapping_inner")
+public class FieldMappingInner extends Model {
+ @Id(keyType = KeyType.Generator,value = KeyGenerators.snowFlakeId)
+ private String id;
+ private String outId;
+ private Date createDate;
+
+ public static FieldMappingInner create() {
+ return new FieldMappingInner();
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public FieldMappingInner setId(String id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getOutId() {
+ return outId;
+ }
+
+ public FieldMappingInner setOutId(String outId) {
+ this.outId = outId;
+ return this;
+ }
+
+ public Date getCreateDate() {
+ return createDate;
+ }
+
+ public FieldMappingInner setCreateDate(Date createDate) {
+ this.createDate = createDate;
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ FieldMappingInner that = (FieldMappingInner) o;
+ return Objects.equals(id, that.id) && Objects.equals(outId, that.outId) && Objects.equals(createDate, that.createDate);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, outId, createDate);
+ }
+
+ @Override
+ public String toString() {
+ return "FieldMappingInner{" +
+ "id='" + id + '\'' +
+ ", outId='" + outId + '\'' +
+ ", createDate=" + createDate +
+ '}';
+ }
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Gender.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Gender.java
new file mode 100644
index 00000000..2a12bc67
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Gender.java
@@ -0,0 +1,41 @@
+/*
+ * 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.loveqq.test.model;
+
+import com.mybatisflex.annotation.EnumValue;
+
+/**
+ * @author 王帅
+ * @since 2023-06-14
+ */
+public enum Gender {
+
+ // MALE, FEMALE;
+ MALE(1), FEMALE(0);
+
+ public int getCode() {
+ return code;
+ }
+
+ @EnumValue
+ private final int code;
+
+ Gender(int code) {
+ this.code = code;
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Good.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Good.java
new file mode 100644
index 00000000..c6315adf
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Good.java
@@ -0,0 +1,108 @@
+/*
+ * 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.loveqq.test.model;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.KeyType;
+import com.mybatisflex.annotation.Table;
+import com.mybatisflex.core.activerecord.Model;
+
+import java.util.Objects;
+
+/**
+ * 商品。
+ *
+ * @author 王帅
+ * @since 2023-06-07
+ */
+@Table(value = "tb_good", onSet = GoodOnSetListener.class)
+public class Good extends Model {
+
+ @Id(keyType = KeyType.Auto)
+ private Integer goodId;
+ private String name;
+ private Double price;
+
+ public static Good create() {
+ return new Good();
+ }
+
+ public Integer getGoodId() {
+ return goodId;
+ }
+
+ public Good setGoodId(Integer goodId) {
+ this.goodId = goodId;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Good setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public Double getPrice() {
+ return price;
+ }
+
+ public Good setPrice(Double price) {
+ this.price = price;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "Good{" +
+ "goodId=" + goodId +
+ ", name='" + name + '\'' +
+ ", price=" + price +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ Good good = (Good) o;
+
+ if (!Objects.equals(goodId, good.goodId)) {
+ return false;
+ }
+ if (!Objects.equals(name, good.name)) {
+ return false;
+ }
+ return Objects.equals(price, good.price);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = goodId != null ? goodId.hashCode() : 0;
+ result = 31 * result + (name != null ? name.hashCode() : 0);
+ result = 31 * result + (price != null ? price.hashCode() : 0);
+ return result;
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/GoodOnSetListener.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/GoodOnSetListener.java
new file mode 100644
index 00000000..fea52c6e
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/GoodOnSetListener.java
@@ -0,0 +1,33 @@
+/*
+ * 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.loveqq.test.model;
+
+import com.mybatisflex.annotation.SetListener;
+
+/**
+ * @author 王帅
+ * @since 2023-08-11
+ */
+public class GoodOnSetListener implements SetListener {
+
+ @Override
+ public Object onSet(Object entity, String property, Object value) {
+ System.out.println("Good: " + property + " --- " + value);
+ return value;
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/IdCard.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/IdCard.java
new file mode 100644
index 00000000..fb13a153
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/IdCard.java
@@ -0,0 +1,55 @@
+/*
+ * 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.loveqq.test.model;
+
+import com.mybatisflex.annotation.Table;
+
+/**
+ * @author 王帅
+ * @since 2023-07-12
+ */
+@Table("tb_id_card")
+public class IdCard {
+
+ private Integer id;
+ private String idNumber;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getIdNumber() {
+ return idNumber;
+ }
+
+ public void setIdNumber(String idNumber) {
+ this.idNumber = idNumber;
+ }
+
+ @Override
+ public String toString() {
+ return "IdCard{" +
+ "id=" + id +
+ ", IdNumber='" + idNumber + '\'' +
+ '}';
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/IdEntity.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/IdEntity.java
new file mode 100644
index 00000000..f036ee20
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/IdEntity.java
@@ -0,0 +1,44 @@
+/*
+ * 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.loveqq.test.model;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.KeyType;
+
+import java.io.Serializable;
+
+/**
+ * @author 王帅
+ * @since 2023-06-13
+ */
+public class IdEntity implements Serializable {
+
+ /**
+ * 主键。
+ */
+ @Id(keyType = KeyType.Auto)
+ protected T id;
+
+ public T getId() {
+ return id;
+ }
+
+ public void setId(T id) {
+ this.id = id;
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Order.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Order.java
new file mode 100644
index 00000000..55624be7
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Order.java
@@ -0,0 +1,61 @@
+/*
+ * 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.loveqq.test.model;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.Table;
+
+import java.time.LocalDateTime;
+
+/**
+ * 订单。
+ *
+ * @author 王帅
+ * @since 2023-06-07
+ */
+@Table("tb_order")
+public class Order {
+
+ @Id
+ private int orderId;
+ private LocalDateTime createTime;
+
+ public int getOrderId() {
+ return orderId;
+ }
+
+ public void setOrderId(int orderId) {
+ this.orderId = orderId;
+ }
+
+ public LocalDateTime getCreateTime() {
+ return createTime;
+ }
+
+ public void setCreateTime(LocalDateTime createTime) {
+ this.createTime = createTime;
+ }
+
+ @Override
+ public String toString() {
+ return "Order{" +
+ "orderId=" + orderId +
+ ", createTime=" + createTime +
+ '}';
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/OrderGood.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/OrderGood.java
new file mode 100644
index 00000000..edd92cc7
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/OrderGood.java
@@ -0,0 +1,49 @@
+/*
+ * 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.loveqq.test.model;
+
+import com.mybatisflex.annotation.Table;
+
+/**
+ * 订单与商品连接表。
+ *
+ * @author 王帅
+ * @since 2023-06-07
+ */
+@Table("tb_order_good")
+public class OrderGood {
+
+ private Integer orderId;
+ private Integer goodId;
+
+ public Integer getOrderId() {
+ return orderId;
+ }
+
+ public void setOrderId(Integer orderId) {
+ this.orderId = orderId;
+ }
+
+ public Integer getGoodId() {
+ return goodId;
+ }
+
+ public void setGoodId(Integer goodId) {
+ this.goodId = goodId;
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/OrderInfo.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/OrderInfo.java
new file mode 100644
index 00000000..bfdcde3a
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/OrderInfo.java
@@ -0,0 +1,106 @@
+/*
+ * 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.loveqq.test.model;
+
+import com.mybatisflex.annotation.RelationManyToMany;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 订单信息。
+ *
+ * @author 王帅
+ * @since 2023-06-07
+ */
+public class OrderInfo {
+
+ private Integer orderId;
+ private LocalDateTime createTime;
+
+ @RelationManyToMany(
+ selfField = "orderId",
+ targetField = "goodId",
+ joinTable = "tb_order_good",
+ joinSelfColumn = "order_id",
+ joinTargetColumn = "good_id"
+ )
+ private List goodList;
+
+ public Integer getOrderId() {
+ return orderId;
+ }
+
+ public void setOrderId(Integer orderId) {
+ this.orderId = orderId;
+ }
+
+ public LocalDateTime getCreateTime() {
+ return createTime;
+ }
+
+ public void setCreateTime(LocalDateTime createTime) {
+ this.createTime = createTime;
+ }
+
+ public List getGoodList() {
+ return goodList;
+ }
+
+ public void setGoodList(List goodList) {
+ this.goodList = goodList;
+ }
+
+ @Override
+ public String toString() {
+ return "OrderInfo{" +
+ "orderId=" + orderId +
+ ", createTime=" + createTime +
+ ", goodList=" + goodList +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ OrderInfo orderInfo = (OrderInfo) o;
+
+ if (!Objects.equals(orderId, orderInfo.orderId)) {
+ return false;
+ }
+ if (!Objects.equals(createTime, orderInfo.createTime)) {
+ return false;
+ }
+ return Objects.equals(goodList, orderInfo.goodList);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = orderId != null ? orderId.hashCode() : 0;
+ result = 31 * result + (createTime != null ? createTime.hashCode() : 0);
+ result = 31 * result + (goodList != null ? goodList.hashCode() : 0);
+ return result;
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Patient.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Patient.java
new file mode 100644
index 00000000..421e4ef6
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Patient.java
@@ -0,0 +1,72 @@
+package com.mybatisflex.loveqq.test.model;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.Table;
+import com.mybatisflex.core.activerecord.Model;
+
+import java.io.Serializable;
+
+/**
+ * 患者信息
+ *
+ * @author Ice 2023/09/26
+ * @version 1.0
+ */
+@Table(value = "tb_patient")
+public class Patient extends Model implements Serializable {
+ private static final long serialVersionUID = 5117723684832788508L;
+
+
+ /**
+ * ID
+ */
+ @Id
+ private Integer patientId;
+
+ /**
+ * 姓名
+ */
+ private String name;
+
+ /**
+ * 所患病症(对应字符串类型) 英文逗号 分割
+ */
+ private String diseaseIds;
+
+ /**
+ * 患者标签(对应数字类型) / 分割
+ */
+ private String tagIds;
+
+ public Integer getPatientId() {
+ return patientId;
+ }
+
+ public void setPatientId(Integer patientId) {
+ this.patientId = patientId;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getDiseaseIds() {
+ return diseaseIds;
+ }
+
+ public void setDiseaseIds(String diseaseIds) {
+ this.diseaseIds = diseaseIds;
+ }
+
+ public String getTagIds() {
+ return tagIds;
+ }
+
+ public void setTagIds(String tagIds) {
+ this.tagIds = tagIds;
+ }
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/PatientVO1.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/PatientVO1.java
new file mode 100644
index 00000000..86309225
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/PatientVO1.java
@@ -0,0 +1,121 @@
+package com.mybatisflex.loveqq.test.model;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.RelationOneToMany;
+import com.mybatisflex.annotation.Table;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 患者VO
+ *
+ * @author Ice 2023/09/26
+ * @version 1.0
+ */
+@Table(value = "tb_patient")
+public class PatientVO1 implements Serializable {
+ private static final long serialVersionUID = -2298625009592638988L;
+
+ /**
+ * ID
+ */
+ @Id
+ private Integer patientId;
+
+ /**
+ * 姓名
+ */
+ private String name;
+
+ /**
+ * 所患病症(对应字符串类型) 英文逗号 分割
+ */
+ private String diseaseIds;
+
+ /**
+ * 患者标签(对应数字类型) / 分割
+ */
+ private String tagIds;
+
+ @RelationOneToMany(
+ selfField = "diseaseIds",
+ selfValueSplitBy = ",", //使用 , 进行分割
+ targetTable = "tb_disease", //只获取某个字段值需要填入目标表名
+ targetField = "diseaseId", //测试目标字段是字符串类型是否正常转换
+ valueField = "name" //测试只获取某个字段值是否正常
+ )
+ private List diseaseNameList;
+
+ @RelationOneToMany(
+ selfField = "tagIds",
+ selfValueSplitBy = "/", //使用 / 进行分割
+ targetField = "tagId" //测试目标字段是数字类型是否正常转换
+ )
+ private List tagList;
+
+ @RelationOneToMany(
+ selfField = "diseaseIds",
+ selfValueSplitBy = ",", //使用 , 进行分割
+ targetField = "diseaseId", //测试目标字段是字符串类型是否正常转换
+ mapKeyField = "diseaseId" //测试Map映射
+ )
+ private Map diseaseMap;
+
+ public Integer getPatientId() {
+ return patientId;
+ }
+
+ public void setPatientId(Integer patientId) {
+ this.patientId = patientId;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getDiseaseIds() {
+ return diseaseIds;
+ }
+
+ public void setDiseaseIds(String diseaseIds) {
+ this.diseaseIds = diseaseIds;
+ }
+
+ public String getTagIds() {
+ return tagIds;
+ }
+
+ public void setTagIds(String tagIds) {
+ this.tagIds = tagIds;
+ }
+
+ public List getDiseaseNameList() {
+ return diseaseNameList;
+ }
+
+ public void setDiseaseNameList(List diseaseNameList) {
+ this.diseaseNameList = diseaseNameList;
+ }
+
+ public List getTagList() {
+ return tagList;
+ }
+
+ public void setTagList(List tagList) {
+ this.tagList = tagList;
+ }
+
+ public Map getDiseaseMap() {
+ return diseaseMap;
+ }
+
+ public void setDiseaseMap(Map diseaseMap) {
+ this.diseaseMap = diseaseMap;
+ }
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Role.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Role.java
new file mode 100644
index 00000000..94199608
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Role.java
@@ -0,0 +1,121 @@
+/*
+ * 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.loveqq.test.model;
+
+import com.mybatisflex.annotation.Column;
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.Table;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 角色。
+ *
+ * @author 王帅
+ * @since 2023-06-07
+ */
+@Table("tb_role")
+public class Role implements Comparable {
+
+ @Id
+ private Integer roleId;
+ private String roleKey;
+ private String roleName;
+
+ @Column(ignore = true)
+ private List userVOS;
+
+ public Integer getRoleId() {
+ return roleId;
+ }
+
+ public void setRoleId(Integer roleId) {
+ this.roleId = roleId;
+ }
+
+ public String getRoleKey() {
+ return roleKey;
+ }
+
+ public void setRoleKey(String roleKey) {
+ this.roleKey = roleKey;
+ }
+
+ public String getRoleName() {
+ return roleName;
+ }
+
+ public void setRoleName(String roleName) {
+ this.roleName = roleName;
+ }
+
+ public List getUserVOS() {
+ return userVOS;
+ }
+
+ public void setUserVOS(List userVOS) {
+ this.userVOS = userVOS;
+ }
+
+ @Override
+ public String toString() {
+ return "Role{" +
+ "roleId=" + roleId +
+ ", roleKey='" + roleKey + '\'' +
+ ", roleName='" + roleName + '\'' +
+ '}';
+ }
+
+ @Override
+ public int compareTo(Role o) {
+ return Integer.compare(this.roleId, o.roleId);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ Role role = (Role) o;
+
+ if (!Objects.equals(roleId, role.roleId)) {
+ return false;
+ }
+ if (!Objects.equals(roleKey, role.roleKey)) {
+ return false;
+ }
+ if (!Objects.equals(roleName, role.roleName)) {
+ return false;
+ }
+ return Objects.equals(userVOS, role.userVOS);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = roleId != null ? roleId.hashCode() : 0;
+ result = 31 * result + (roleKey != null ? roleKey.hashCode() : 0);
+ result = 31 * result + (roleName != null ? roleName.hashCode() : 0);
+ result = 31 * result + (userVOS != null ? userVOS.hashCode() : 0);
+ return result;
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/RoleKey.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/RoleKey.java
new file mode 100644
index 00000000..c898a278
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/RoleKey.java
@@ -0,0 +1,40 @@
+/*
+ * 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.loveqq.test.model;
+
+/**
+ * @author 王帅
+ * @since 2023-06-15
+ */
+public class RoleKey {
+
+ private T roleKey;
+
+ public T getRoleKey() {
+ return roleKey;
+ }
+
+ public void setRoleKey(T roleKey) {
+ this.roleKey = roleKey;
+ }
+
+ @Override
+ public String toString() {
+ return roleKey.toString();
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/RoleVO1.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/RoleVO1.java
new file mode 100644
index 00000000..6e02805a
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/RoleVO1.java
@@ -0,0 +1,74 @@
+/*
+ * 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.loveqq.test.model;
+
+/**
+ * 角色。
+ *
+ * @author 王帅
+ * @since 2023-06-13
+ */
+public class RoleVO1 {
+
+ private Integer roleId;
+ private String roleKey;
+ private String roleName;
+ private UserVO1 user;
+
+ public Integer getRoleId() {
+ return roleId;
+ }
+
+ public void setRoleId(Integer roleId) {
+ this.roleId = roleId;
+ }
+
+ public String getRoleKey() {
+ return roleKey;
+ }
+
+ public void setRoleKey(String roleKey) {
+ this.roleKey = roleKey;
+ }
+
+ public String getRoleName() {
+ return roleName;
+ }
+
+ public void setRoleName(String roleName) {
+ this.roleName = roleName;
+ }
+
+ public UserVO1 getUser() {
+ return user;
+ }
+
+ public void setUser(UserVO1 user) {
+ this.user = user;
+ }
+
+ @Override
+ public String toString() {
+ return "Role{" +
+ "roleId=" + roleId +
+ ", roleKey='" + roleKey + '\'' +
+ ", roleName='" + roleName + '\'' +
+ ", userVO1='" + user + '\'' +
+ '}';
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/RoleVO2.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/RoleVO2.java
new file mode 100644
index 00000000..b4d12c37
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/RoleVO2.java
@@ -0,0 +1,52 @@
+/*
+ * 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.loveqq.test.model;
+
+/**
+ * @author 王帅
+ * @since 2023-06-15
+ */
+public class RoleVO2 {
+
+ private T roleName;
+ private RoleKey roleKey;
+
+ public T getRoleName() {
+ return roleName;
+ }
+
+ public void setRoleName(T roleName) {
+ this.roleName = roleName;
+ }
+
+ public RoleKey getRoleKey() {
+ return roleKey;
+ }
+
+ public void setRoleKey(RoleKey roleKey) {
+ this.roleKey = roleKey;
+ }
+
+ @Override
+ public String toString() {
+ return "RoleName{" +
+ "roleName=" + roleName +
+ ", roleKey=" + roleKey +
+ '}';
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/RoleVO3.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/RoleVO3.java
new file mode 100644
index 00000000..3a1f67f0
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/RoleVO3.java
@@ -0,0 +1,62 @@
+/*
+ * 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.loveqq.test.model;
+
+/**
+ * @author 王帅
+ * @since 2023-06-15
+ */
+public class RoleVO3 {
+
+ private Integer roleId;
+ private String roleKey;
+ private String userName;
+
+ public Integer getRoleId() {
+ return roleId;
+ }
+
+ public void setRoleId(Integer roleId) {
+ this.roleId = roleId;
+ }
+
+ public String getRoleKey() {
+ return roleKey;
+ }
+
+ public void setRoleKey(String roleKey) {
+ this.roleKey = roleKey;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ @Override
+ public String toString() {
+ return "RoleVO3{" +
+ "roleId=" + roleId +
+ ", roleKey='" + roleKey + '\'' +
+ ", roleName='" + userName + '\'' +
+ '}';
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Tag.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Tag.java
new file mode 100644
index 00000000..ba16d2cc
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/Tag.java
@@ -0,0 +1,44 @@
+package com.mybatisflex.loveqq.test.model;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.Table;
+
+import java.io.Serializable;
+
+/**
+ * 标签
+ *
+ * @author Ice 2023/09/26
+ * @version 1.0
+ */
+@Table(value = "tb_tag")
+public class Tag implements Serializable {
+ private static final long serialVersionUID = 5600670055904157386L;
+
+ /**
+ * ID
+ */
+ @Id
+ private Integer tagId;
+
+ /**
+ * 标签名称
+ */
+ private String name;
+
+ public Integer getTagId() {
+ return tagId;
+ }
+
+ public void setTagId(Integer tagId) {
+ this.tagId = tagId;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/TbClass.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/TbClass.java
new file mode 100644
index 00000000..23c850ad
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/TbClass.java
@@ -0,0 +1,47 @@
+package com.mybatisflex.loveqq.test.model;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.Table;
+
+@Table(value = "tb_class")
+public class TbClass {
+ @Id
+ private Long id;
+
+ private Long user_id;
+
+ private String className;
+
+ public Long getId() {
+ return id;
+ }
+
+ public Long getUser_id() {
+ return user_id;
+ }
+
+ public String getClassName() {
+ return className;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public void setUser_id(Long user_id) {
+ this.user_id = user_id;
+ }
+
+ public void setClassName(String className) {
+ this.className = className;
+ }
+
+ @Override
+ public String toString() {
+ return "TbClass{" +
+ "id=" + id +
+ ", user_id=" + user_id +
+ ", className='" + className + '\'' +
+ '}';
+ }
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UnmappedBaseEntity.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UnmappedBaseEntity.java
new file mode 100644
index 00000000..5608f80f
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UnmappedBaseEntity.java
@@ -0,0 +1,35 @@
+/*
+ * 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.loveqq.test.model;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Map;
+
+/**
+ * UnMappedBaseEntity
+ *
+ * @author wy
+ * @version 1.0
+ * @date 2024/9/12 11:36
+ **/
+@Getter
+@Setter
+public class UnmappedBaseEntity {
+
+ protected Map unmappedMap;
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UnmappedUser.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UnmappedUser.java
new file mode 100644
index 00000000..89b8b2f6
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UnmappedUser.java
@@ -0,0 +1,53 @@
+/*
+ * 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.loveqq.test.model;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.KeyType;
+import com.mybatisflex.annotation.Table;
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * UnMapped
+ *
+ * @author wy
+ * @version 1.0
+ * @date 2024/9/12 11:28
+ **/
+@Getter
+@Setter
+@Table("tb_unmapped_user")
+public class UnmappedUser extends UnmappedBaseEntity {
+
+ @Id(keyType = KeyType.Auto)
+ private Long id;
+
+ private Integer age;
+
+ private String name;
+
+ @Override
+ public String toString() {
+ return "UnmappedUser{" +
+ "id='" + id + '\'' +
+ ", name='" + name + '\'' +
+ ", age=" + age +
+ (CollectionUtils.isEmpty(unmappedMap) ? "" : ", unmappedMap=" + unmappedMap.toString())
+ + '}';
+ }
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/User.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/User.java
new file mode 100644
index 00000000..3e3ef1f8
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/User.java
@@ -0,0 +1,96 @@
+/*
+ * 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.loveqq.test.model;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.RelationManyToMany;
+import com.mybatisflex.annotation.Table;
+import com.mybatisflex.core.activerecord.Model;
+
+import java.util.List;
+
+/**
+ * 用户。
+ *
+ * @author 王帅
+ * @since 2023-06-07
+ */
+
+@Table("tb_user")
+public class User extends Model {
+
+ @Id
+ private Integer userId;
+ private String userName;
+ private String password;
+
+ @RelationManyToMany(
+ selfField = "userId",
+ targetField = "roleId",
+ joinTable = "tb_user_role",
+ joinSelfColumn = "user_id",
+ joinTargetColumn = "role_id"
+ )
+ private List roleList;
+
+ public static User create() {
+ return new User();
+ }
+
+ public Integer getUserId() {
+ return userId;
+ }
+
+ public void setUserId(Integer userId) {
+ this.userId = userId;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public List getRoleList() {
+ return roleList;
+ }
+
+ public void setRoleList(List roleList) {
+ this.roleList = roleList;
+ }
+
+ @Override
+ public String toString() {
+ return "User{" +
+ "userId=" + userId +
+ ", userName='" + userName + '\'' +
+ ", password='" + password + '\'' +
+ ", roleList=" + roleList +
+ '}';
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserInfo.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserInfo.java
new file mode 100644
index 00000000..b8e9d321
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserInfo.java
@@ -0,0 +1,115 @@
+/*
+ * 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.loveqq.test.model;
+
+import com.mybatisflex.annotation.RelationManyToMany;
+
+import java.util.List;
+
+/**
+ * 用户信息。
+ *
+ * @author 王帅
+ * @since 2023-06-07
+ */
+public class UserInfo {
+
+ private Integer userId;
+ private String userName;
+ private String password;
+ private String idNumber;
+
+ @RelationManyToMany(
+ selfField = "userId",
+ targetField = "roleId",
+ joinTable = "tb_user_role",
+ joinSelfColumn = "user_id",
+ joinTargetColumn = "role_id"
+ )
+ private List roleList;
+
+ @RelationManyToMany(
+ selfField = "userId",
+ targetField = "orderId",
+ targetTable = "tb_order",
+ joinTable = "tb_user_order",
+ joinSelfColumn = "user_id",
+ joinTargetColumn = "order_id"
+ )
+ private List orderInfoList;
+
+ public Integer getUserId() {
+ return userId;
+ }
+
+ public void setUserId(Integer userId) {
+ this.userId = userId;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getIdNumber() {
+ return idNumber;
+ }
+
+ public void setIdNumber(String idNumber) {
+ this.idNumber = idNumber;
+ }
+
+ public List getRoleList() {
+ return roleList;
+ }
+
+ public void setRoleList(List roleList) {
+ this.roleList = roleList;
+ }
+
+ public List getOrderInfoList() {
+ return orderInfoList;
+ }
+
+ public void setOrderInfoList(List orderInfoList) {
+ this.orderInfoList = orderInfoList;
+ }
+
+ @Override
+ public String toString() {
+ return "UserInfo{" +
+ "userId=" + userId +
+ ", userName='" + userName + '\'' +
+ ", password='" + password + '\'' +
+ ", idNumber='" + idNumber + '\'' +
+ ", roleList=" + roleList +
+ ", orderInfoList=" + orderInfoList +
+ '}';
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserOrder.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserOrder.java
new file mode 100644
index 00000000..f570898b
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserOrder.java
@@ -0,0 +1,49 @@
+/*
+ * 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.loveqq.test.model;
+
+import com.mybatisflex.annotation.Table;
+
+/**
+ * 用户订单映射。
+ *
+ * @author 王帅
+ * @since 2023-06-07
+ */
+@Table("tb_user_order")
+public class UserOrder {
+
+ private Integer userId;
+ private Integer orderId;
+
+ public Integer getUserId() {
+ return userId;
+ }
+
+ public void setUserId(Integer userId) {
+ this.userId = userId;
+ }
+
+ public Integer getOrderId() {
+ return orderId;
+ }
+
+ public void setOrderId(Integer orderId) {
+ this.orderId = orderId;
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserRole.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserRole.java
new file mode 100644
index 00000000..5f2b0e78
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserRole.java
@@ -0,0 +1,49 @@
+/*
+ * 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.loveqq.test.model;
+
+import com.mybatisflex.annotation.Table;
+
+/**
+ * 用户与角色连接表。
+ *
+ * @author 王帅
+ * @since 2023-06-07
+ */
+@Table("tb_user_role")
+public class UserRole {
+
+ private Integer userId;
+ private Integer roleId;
+
+ public Integer getUserId() {
+ return userId;
+ }
+
+ public void setUserId(Integer userId) {
+ this.userId = userId;
+ }
+
+ public Integer getRoleId() {
+ return roleId;
+ }
+
+ public void setRoleId(Integer roleId) {
+ this.roleId = roleId;
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserVO.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserVO.java
new file mode 100644
index 00000000..61106bab
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserVO.java
@@ -0,0 +1,70 @@
+/*
+ * 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.loveqq.test.model;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 用户 VO 对象。
+ *
+ * @author 王帅
+ * @since 2023-06-07
+ */
+
+public class UserVO {
+
+ private String userId;
+ private String userName;
+ // private TreeSet roleList;
+// private Role[] roleList;
+ private HashMap roleList;
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ public Map getRoleList() {
+ return roleList;
+ }
+
+ public void setRoleList(HashMap roleList) {
+ this.roleList = roleList;
+ }
+
+ @Override
+ public String toString() {
+ return "UserVO{" +
+ "userId='" + userId + '\'' +
+ ", userName='" + userName + '\'' +
+ ", roleList=" + roleList +
+ '}';
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserVO1.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserVO1.java
new file mode 100644
index 00000000..a571acc3
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserVO1.java
@@ -0,0 +1,62 @@
+/*
+ * 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.loveqq.test.model;
+
+/**
+ * @author 王帅
+ * @since 2023-06-07
+ */
+public class UserVO1 {
+
+ private String userId;
+ private String userName;
+ private RoleVO1 role;
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ public RoleVO1 getRole() {
+ return role;
+ }
+
+ public void setRole(RoleVO1 role) {
+ this.role = role;
+ }
+
+ @Override
+ public String toString() {
+ return "UserVO1{" +
+ "userId='" + userId + '\'' +
+ ", userName='" + userName + '\'' +
+ ", roleVO1=" + role +
+ '}';
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserVO2.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserVO2.java
new file mode 100644
index 00000000..db46c857
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserVO2.java
@@ -0,0 +1,75 @@
+/*
+ * 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.loveqq.test.model;
+
+import java.util.List;
+
+/**
+ * @author 王帅
+ * @since 2023-06-07
+ */
+
+public class UserVO2 {
+
+ private String userId;
+ private String userName;
+ private List roles;
+ private List roleIds;
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ public List getRoles() {
+ return roles;
+ }
+
+ public void setRoles(List roles) {
+ this.roles = roles;
+ }
+
+ public List getRoleIds() {
+ return roleIds;
+ }
+
+ public void setRoleIds(List roleIds) {
+ this.roleIds = roleIds;
+ }
+
+ @Override
+ public String toString() {
+ return "UserVO2{" +
+ "userId='" + userId + '\'' +
+ ", userName='" + userName + '\'' +
+ ", roles=" + roles +
+ ", roleIds=" + roleIds +
+ '}';
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserVO3.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserVO3.java
new file mode 100644
index 00000000..fc076fd4
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserVO3.java
@@ -0,0 +1,68 @@
+/*
+ * 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.loveqq.test.model;
+
+import com.mybatisflex.annotation.Id;
+
+import java.util.List;
+
+/**
+ * @author 王帅
+ * @since 2023-06-07
+ */
+
+public class UserVO3 {
+
+ @Id
+ private String userId;
+ private String userName;
+ private List roleVO3;
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ public List getRoleVO3() {
+ return roleVO3;
+ }
+
+ public void setRoleVO3(List roleVO3) {
+ this.roleVO3 = roleVO3;
+ }
+
+ @Override
+ public String toString() {
+ return "UserVO3{" +
+ "userId='" + userId + '\'' +
+ ", userName='" + userName + '\'' +
+ ", roleVO3=" + roleVO3 +
+ '}';
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserVO4.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserVO4.java
new file mode 100644
index 00000000..d365cbc0
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserVO4.java
@@ -0,0 +1,56 @@
+/*
+ * 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.loveqq.test.model;
+
+/**
+ * 用户 VO 对象。
+ *
+ * @author 王帅
+ * @since 2023-06-30
+ */
+
+public class UserVO4 {
+
+ private String userId;
+
+ private String userName;
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ @Override
+ public String toString() {
+ return "UserVO{" +
+ "userId='" + userId + '\'' +
+ ", userName='" + userName + '\'' +
+ '}';
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserVO5.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserVO5.java
new file mode 100644
index 00000000..d9ebada9
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/model/UserVO5.java
@@ -0,0 +1,108 @@
+package com.mybatisflex.loveqq.test.model;//package com.mybatisflex.test.model;
+//
+//import com.mybatisflex.annotation.*;
+//
+//import java.io.Serializable;
+//import java.util.List;
+//
+///**
+// * 字段绑定测试
+// * @author Ice 2023/09/16
+// * @version 1.0
+// */
+//@Table("tb_user")
+//public class UserVO5 implements Serializable {
+// private static final long serialVersionUID = 474700189859144273L;
+//
+// @Id
+// private Integer userId;
+// private String userName;
+// private String password;
+//
+// @RelationOneToOne(
+// selfField = "userId",
+// targetTable = "tb_id_card",
+// targetField = "id",
+// valueField = "idNumber"
+// )
+// private String idNumberCustomFieldName;
+//
+// @RelationOneToMany(
+// selfField = "userId",
+// targetTable = "tb_user_order",
+// targetField = "userId",
+// valueField = "orderId"
+// )
+// private List orderIdList;
+//
+// @RelationManyToMany(
+// selfField = "userId",
+// targetTable = "tb_role",
+// targetField = "roleId",
+// valueField = "roleName",
+// joinTable = "tb_user_role",
+// joinSelfColumn = "user_id",
+// joinTargetColumn = "role_id"
+// )
+// private List roleNameList;
+//
+// public Integer getUserId() {
+// return userId;
+// }
+//
+// public void setUserId(Integer userId) {
+// this.userId = userId;
+// }
+//
+// public String getUserName() {
+// return userName;
+// }
+//
+// public void setUserName(String userName) {
+// this.userName = userName;
+// }
+//
+// public String getPassword() {
+// return password;
+// }
+//
+// public void setPassword(String password) {
+// this.password = password;
+// }
+//
+// public String getIdNumberCustomFieldName() {
+// return idNumberCustomFieldName;
+// }
+//
+// public void setIdNumberCustomFieldName(String idNumberCustomFieldName) {
+// this.idNumberCustomFieldName = idNumberCustomFieldName;
+// }
+//
+// public List getOrderIdList() {
+// return orderIdList;
+// }
+//
+// public void setOrderIdList(List orderIdList) {
+// this.orderIdList = orderIdList;
+// }
+//
+// public List getRoleNameList() {
+// return roleNameList;
+// }
+//
+// public void setRoleNameList(List roleNameList) {
+// this.roleNameList = roleNameList;
+// }
+//
+// @Override
+// public String toString() {
+// return "UserVO5{" +
+// "userId=" + userId +
+// ", userName='" + userName + '\'' +
+// ", password='" + password + '\'' +
+// ", idNumberCustomFieldName='" + idNumberCustomFieldName + '\'' +
+// ", orderIdList=" + orderIdList +
+// ", roleNameList=" + roleNameList +
+// '}';
+// }
+//}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/service/AccountService.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/service/AccountService.java
new file mode 100644
index 00000000..17c2d4db
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/service/AccountService.java
@@ -0,0 +1,54 @@
+/*
+ * 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.loveqq.test.service;
+
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Service;
+import com.mybatisflex.loveqq.test.mapper.AccountMapper;
+import com.mybatisflex.loveqq.test.model.Account;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.concurrent.TimeUnit;
+
+@Service
+public class AccountService {
+
+
+ @Resource
+ AccountMapper accountMapper;
+
+
+ @Transactional
+ public void update2() {
+ int x = 1 / 0;
+ Account account = new Account();
+ account.setId(2L);
+ account.setUserName("haha");
+ accountMapper.update(account);
+ }
+
+ @Transactional(rollbackFor = Exception.class, timeout = 3)
+ public void transactionTimeTest() throws InterruptedException {
+ Account account = new Account();
+ account.setId(100L);
+ account.setUserName("aliothmoon");
+ accountMapper.insert(account);
+ TimeUnit.SECONDS.sleep(5);
+ accountMapper.selectOneById(account.getId());
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/service/ArticleService.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/service/ArticleService.java
new file mode 100644
index 00000000..8c32d7fb
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/service/ArticleService.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2022-2024, 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.loveqq.test.service;
+
+import com.mybatisflex.core.service.IService;
+import com.mybatisflex.loveqq.test.model.Article;
+
+/**
+ * @author 王帅
+ * @since 2023-07-22
+ */
+public interface ArticleService extends IService {
+
+ void changeDataSource();
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/service/impl/ArticleServiceImpl.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/service/impl/ArticleServiceImpl.java
new file mode 100644
index 00000000..6126c8ec
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/service/impl/ArticleServiceImpl.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2022-2024, 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.loveqq.test.service.impl;
+
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Service;
+import com.mybatisflex.annotation.UseDataSource;
+import com.mybatisflex.core.datasource.DataSourceKey;
+import com.mybatisflex.loveqq.framework.boot.autoconfig.service.ServiceImpl;
+import com.mybatisflex.loveqq.test.mapper.ArticleMapper;
+import com.mybatisflex.loveqq.test.model.Article;
+import com.mybatisflex.loveqq.test.service.ArticleService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author 王帅
+ * @since 2023-07-22
+ */
+@Service
+public class ArticleServiceImpl extends ServiceImpl implements ArticleService {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ArticleServiceImpl.class);
+
+ @Override
+ @UseDataSource("annotation ds")
+ public void changeDataSource() {
+ LOGGER.info("start1: {}", DataSourceKey.get());
+ DataSourceKey.use("ds outer", () -> {
+ LOGGER.info("start2: {}", DataSourceKey.get());
+ DataSourceKey.use("ds inner", () -> {
+ LOGGER.info("start3: {}", DataSourceKey.get());
+ LOGGER.info("end3: {}", DataSourceKey.get());
+ });
+ LOGGER.info("end2: {}", DataSourceKey.get());
+ });
+ LOGGER.info("end1: {}", DataSourceKey.get());
+ }
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/unmapped/MyUnMappedColumnHandler.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/unmapped/MyUnMappedColumnHandler.java
new file mode 100644
index 00000000..a523ba74
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/java/com/mybatisflex/loveqq/test/unmapped/MyUnMappedColumnHandler.java
@@ -0,0 +1,46 @@
+/*
+ * 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.loveqq.test.unmapped;
+
+import com.mybatisflex.core.mybatis.UnMappedColumnHandler;
+import com.mybatisflex.loveqq.test.model.UnmappedBaseEntity;
+import org.apache.ibatis.reflection.MetaObject;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * MyUnMappedColumnHandler
+ *
+ * @author wy
+ * @version 1.0
+ * @date 2024/9/12 11:34
+ **/
+public class MyUnMappedColumnHandler implements UnMappedColumnHandler {
+ @Override
+ public void handleUnMappedColumn(MetaObject metaObject, String unmappedColumnName, Object value) {
+ if (metaObject.getOriginalObject() instanceof UnmappedBaseEntity){
+ Object object = metaObject.getValue("unmappedMap");
+ if(object == null){
+ Map map = new HashMap<>();
+ map.put(unmappedColumnName, value);
+ metaObject.setValue("unmappedMap", map);
+ }else {
+ ((Map)object).put(unmappedColumnName, value);
+ }
+ }
+ }
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/resources/application.yml b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/resources/application.yml
new file mode 100644
index 00000000..053a5f9d
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/resources/application.yml
@@ -0,0 +1,48 @@
+# DataSource Config
+#spring:
+# # h2:
+# # console:
+# # enabled: true
+# datasource:
+# driver-class-name: com.mysql.cj.jdbc.Driver
+# url: jdbc:mysql://localhost:3306/flex_test
+# username: root
+# password: 12345678
+ # driver-class-name:
+ # datasource:
+ # driver-class-name: org.h2.Driver
+ # username: root
+ # password: test
+# sql:
+# init:
+# schema-locations: classpath:schema.sql
+# data-locations: classpath:data.sql
+#mybatis-flex:
+# admin-config:
+# enable: true
+# endpoint: http://localhost/admin
+# secret-key: secretKey
+# mapper-locations:
+# - classpath*:/mapper/*.xml
+# global-config:
+# print-banner: false
+# key-config:
+# key-type: generator
+# value: uuid
+# configuration:
+# use-generated-keys: true
+# datasource:
+# data-center:
+# url: jdbc:mysql://localhost:3306/flex_test
+# username: root
+# password: 12345678
+mybatis-flex:
+ datasource:
+ ds1:
+ url: jdbc:mysql://localhost:3306/flex_test
+ username: root
+ password: 12345678
+ ds2:
+ url: jdbc:mysql://localhost:3306/flex_test
+ username: root
+ password: 12345678
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/resources/mapper/accountMapper.xml b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/resources/mapper/accountMapper.xml
new file mode 100644
index 00000000..66546a11
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/main/resources/mapper/accountMapper.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ select * from `tb_account` ${qwSql} limit ${pageOffset}, ${pageSize}
+
+
+
+ select count(*) from `tb_account` ${qwSql}
+
+
+
+
+
+
+
+
+
+
+ select id, user_name, birthday from tb_account where id = 1
+
+
+
+ select * from tb_account where id = #{id}
+
+
+
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/LoveqqExtension.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/LoveqqExtension.java
new file mode 100644
index 00000000..ba1ed3b3
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/LoveqqExtension.java
@@ -0,0 +1,28 @@
+package com.mybatisflex.loveqq.test;
+
+import com.kfyty.loveqq.framework.boot.K;
+import com.kfyty.loveqq.framework.core.autoconfig.ApplicationContext;
+import com.kfyty.loveqq.framework.core.autoconfig.beans.AutowiredCapableSupport;
+import com.kfyty.loveqq.framework.core.utils.BeanUtil;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public class LoveqqExtension implements BeforeAllCallback, BeforeEachCallback {
+ private static ApplicationContext context;
+ private static AutowiredCapableSupport autowiredCapable;
+
+ @Override
+ public void beforeAll(ExtensionContext extensionContext) throws Exception {
+ if (context == null) {
+ context = K.start(SampleApplication.class);
+ autowiredCapable = context.getBean(AutowiredCapableSupport.BEAN_NAME);
+ }
+ }
+
+ @Override
+ public void beforeEach(ExtensionContext extensionContext) throws Exception {
+ Object instance = extensionContext.getRequiredTestInstance();
+ autowiredCapable.autowiredBean(BeanUtil.getBeanName(instance.getClass()), instance);
+ }
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/common/CloneTest.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/common/CloneTest.java
new file mode 100644
index 00000000..876b74ca
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/common/CloneTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.loveqq.test.common;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONWriter;
+import com.mybatisflex.core.query.CPI;
+import com.mybatisflex.core.query.QueryWrapper;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.function.Supplier;
+
+import static com.mybatisflex.core.query.QueryMethods.count;
+import static com.mybatisflex.core.query.QueryMethods.distinct;
+import static com.mybatisflex.loveqq.test.model.table.AccountTableDef.ACCOUNT;
+import static com.mybatisflex.loveqq.test.model.table.RoleTableDef.ROLE;
+import static com.mybatisflex.loveqq.test.model.table.UserRoleTableDef.USER_ROLE;
+import static com.mybatisflex.loveqq.test.model.table.UserTableDef.USER;
+
+/**
+ * @author 王帅
+ * @since 2023-06-09
+ */
+class CloneTest {
+
+ @Test
+ void test03() {
+ String jsonString = JSON.toJSONString(newQueryWrapper(), JSONWriter.Feature.FieldBased, JSONWriter.Feature.ReferenceDetection);
+ System.out.println(jsonString);
+ }
+
+ @Test
+ void test() {
+ QueryWrapper queryWrapper = newQueryWrapper();
+ Class queryWrapperClass = QueryWrapper.class;
+ int count = 10000;
+
+ /*
+ * new: 41ms
+ * clone: 65ms
+ * fastjson: 620ms
+ * serial: 2003ms
+ */
+ calcTime(count, "new", this::newQueryWrapper);
+ calcTime(count, "clone", queryWrapper::clone);
+ calcTime(count, "fastjson", () -> SerialUtil.cloneObject(queryWrapper, queryWrapperClass));
+ calcTime(count, "serial", () -> SerialUtil.cloneObject(queryWrapper));
+ }
+
+ @Test
+ void test02() {
+ QueryWrapper queryWrapper = newQueryWrapper();
+ QueryWrapper queryWrapper1 = queryWrapper.clone();
+ QueryWrapper queryWrapper2 = SerialUtil.cloneObject(queryWrapper);
+// QueryWrapper queryWrapper3 = SerialUtil.cloneObject(queryWrapper, QueryWrapper.class);
+ System.err.println(SerialUtil.toJSONString(queryWrapper));
+ System.out.println(queryWrapper.toSQL());
+ System.out.println(queryWrapper1.toSQL());
+ System.out.println(queryWrapper2.toSQL());
+// System.out.println(queryWrapper3.toSQL());
+
+ Assertions.assertEquals(queryWrapper.toSQL(), queryWrapper1.toSQL());
+ Assertions.assertEquals(queryWrapper.toSQL(), queryWrapper2.toSQL());
+// Assertions.assertEquals(queryWrapper.toSQL(), queryWrapper3.toSQL());
+ }
+
+ private void calcTime(int count, String type, Supplier supplier) {
+ long start = System.currentTimeMillis();
+ for (int i = 0; i < count; i++) {
+ supplier.get();
+ }
+ long end = System.currentTimeMillis();
+ System.out.println(type + ": " + (end - start) + "ms");
+ }
+
+ private QueryWrapper newQueryWrapper() {
+ return QueryWrapper.create()
+ .select(count(distinct(USER.USER_ID))/*case_()
+ .when(USER.USER_ID.eq(3)).then("x3")
+ .when(USER.USER_ID.eq(5)).then("x4")
+ .end(),
+ distinct(USER.USER_ID.add(4)),
+ USER.USER_NAME,
+ ROLE.ALL_COLUMNS*/)
+ .from(USER.as("u"))
+ .leftJoin(USER_ROLE).as("ur").on(USER_ROLE.USER_ID.eq(USER.USER_ID))
+ .leftJoin(ROLE).as("r").on(USER_ROLE.ROLE_ID.eq(ROLE.ROLE_ID))
+ .where(USER.USER_ID.eq(3))
+ .and(ROLE.ROLE_NAME.in(Arrays.asList(1, 2, 3)))
+ .or(ROLE.ROLE_ID.ge(USER.USER_ID))
+ .groupBy(ROLE.ROLE_NAME)
+ .having(ROLE.ROLE_ID.ge(7))
+ .orderBy(ROLE.ROLE_NAME.asc());
+ }
+
+ @Test
+ void test04() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .from(ACCOUNT)
+ .select(ACCOUNT.DEFAULT_COLUMNS)
+ .where(ACCOUNT.ID.eq(1));
+
+ QueryWrapper clone = queryWrapper.clone();
+
+ CPI.setSelectColumns(clone, null);
+
+ clone.select(ACCOUNT.ID, ACCOUNT.USER_NAME);
+
+ String sql1 = queryWrapper.toSQL();
+ String sql2 = clone.toSQL();
+
+ System.out.println(sql1);
+ System.out.println(sql2);
+
+ Assertions.assertNotEquals(sql1, sql2);
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/common/FieldTest.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/common/FieldTest.java
new file mode 100644
index 00000000..83d2ffdc
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/common/FieldTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.loveqq.test.common;
+
+import com.mybatisflex.loveqq.test.model.Account;
+import com.mybatisflex.loveqq.test.model.BaseEntity;
+import com.mybatisflex.loveqq.test.model.UserVO;
+import org.apache.ibatis.reflection.Reflector;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+/**
+ * @author 王帅
+ * @since 2023-06-13
+ */
+class FieldTest {
+
+ @Test
+ void test() {
+ String genericString = BaseEntity.class.toGenericString();
+ System.out.println(genericString);
+ }
+
+ @Test
+ void test02() {
+ Class accountClass = Account.class;
+ Method[] declaredMethods = accountClass.getMethods();
+ Arrays.stream(declaredMethods)
+ .filter(e -> e.getName().startsWith("get"))
+ .forEach(System.out::println);
+ }
+
+ @Test
+ void test03() {
+ Reflector reflector = new Reflector(Account.class);
+ Class> id = reflector.getGetterType("id");
+ Class> userName = reflector.getGetterType("userName");
+ Class> age = reflector.getGetterType("age");
+ System.out.println(id);
+ System.out.println(userName);
+ System.out.println(age);
+ }
+
+ @Test
+ void test04() {
+ Reflector reflector = new Reflector(UserVO.class);
+ Class> roleList = reflector.getGetterType("roleList");
+ System.out.println(roleList);
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/common/ListenerTest.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/common/ListenerTest.java
new file mode 100644
index 00000000..4e8cf70e
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/common/ListenerTest.java
@@ -0,0 +1,89 @@
+package com.mybatisflex.loveqq.test.common;
+
+import com.mybatisflex.annotation.InsertListener;
+import com.mybatisflex.core.BaseMapper;
+import com.mybatisflex.core.FlexGlobalConfig;
+import com.mybatisflex.core.mybatis.Mappers;
+import com.mybatisflex.core.table.TableInfo;
+import com.mybatisflex.core.table.TableInfoFactory;
+import com.mybatisflex.core.util.CollectionUtil;
+import com.mybatisflex.core.util.MapUtil;
+import com.mybatisflex.loveqq.test.listener.missingListenerFix.AccountAgeInsertListener;
+import com.mybatisflex.loveqq.test.listener.missingListenerFix.AccountAgeInsertListenerFlag;
+import com.mybatisflex.loveqq.test.listener.missingListenerFix.AccountTableAnnoInsertListener;
+import com.mybatisflex.loveqq.test.listener.missingListenerFix.LogicDeleteInsertListener;
+import com.mybatisflex.loveqq.test.listener.missingListenerFix.LogicDeleteInsertListenerFlag;
+import com.mybatisflex.loveqq.test.model.AccountMissingListenerTestModel;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+/**
+ * 监听器测试
+ *
+ * @author Ice 2023/10/23
+ * @version 1.0
+ */
+class ListenerTest {
+
+ @Test
+ void missingListenerTest() {
+
+ AccountMissingListenerTestModel accountMissingListenerTestModel = new AccountMissingListenerTestModel();
+
+ //加入配置
+ FlexGlobalConfig config = FlexGlobalConfig.getDefaultConfig();
+ config.registerInsertListener(new LogicDeleteInsertListener(), LogicDeleteInsertListenerFlag.class);
+ config.registerInsertListener(new AccountAgeInsertListener(), AccountAgeInsertListenerFlag.class);
+
+ //获取TableInfo
+ TableInfo tableInfo = TableInfoFactory.ofEntityClass(AccountMissingListenerTestModel.class);
+
+ //执行测试 ===> Listener列表比对
+ Map, List> tempOnInsertListenerMap = new ConcurrentHashMap<>();//替代原本的缓存Map
+
+ List insertListeners = MapUtil.computeIfAbsent(tempOnInsertListenerMap, AccountMissingListenerTestModel.class, aClass -> {
+ List globalListeners = FlexGlobalConfig.getDefaultConfig()
+ .getSupportedInsertListener(AccountMissingListenerTestModel.class);
+ List allListeners = CollectionUtil.merge(tableInfo.getOnInsertListeners(), globalListeners);
+ Collections.sort(allListeners);
+ return allListeners;
+ });
+
+ List> resolvedInsertListeners = insertListeners.stream().map(InsertListener::getClass).collect(Collectors.toList());
+ List> expectedInsertListeners = CollectionUtil.newArrayList(LogicDeleteInsertListener.class, AccountAgeInsertListener.class, AccountTableAnnoInsertListener.class);
+
+ Assertions.assertTrue(
+ () -> {
+ for (Class> clazz : expectedInsertListeners) {
+ if (!resolvedInsertListeners.contains(clazz)) {
+ return false;
+ }
+ }
+ return true;
+ },
+ String.format("InsertListener与预期结果不一致\n预期Listener列表:%s\n实际Listener列表:%s", expectedInsertListeners, resolvedInsertListeners)
+ );
+
+ //执行测试 ===> 插入结果比对
+ BaseMapper baseMapper = Mappers.ofEntityClass(accountMissingListenerTestModel.getClass());
+ baseMapper.insert(accountMissingListenerTestModel);
+
+ //实际执行结果
+ AccountMissingListenerTestModel dbData = (AccountMissingListenerTestModel) baseMapper.selectOneById(accountMissingListenerTestModel.getId());
+
+ //预期数据
+ AccountMissingListenerTestModel expectedData = new AccountMissingListenerTestModel();
+ expectedData.setId(dbData.getId());
+ expectedData.setUserName("测试缺失的监听器-userName");
+ expectedData.setAge(18);
+ expectedData.setDelete(false);
+
+ Assertions.assertEquals(expectedData, dbData);
+ }
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/common/MapperUtilTest.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/common/MapperUtilTest.java
new file mode 100644
index 00000000..bf5afb53
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/common/MapperUtilTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.loveqq.test.common;
+
+import com.mybatisflex.core.query.QueryWrapper;
+import com.mybatisflex.core.util.MapperUtil;
+import org.junit.jupiter.api.Test;
+
+import static com.mybatisflex.loveqq.test.model.table.RoleTableDef.ROLE;
+import static com.mybatisflex.loveqq.test.model.table.UserRoleTableDef.USER_ROLE;
+import static com.mybatisflex.loveqq.test.model.table.UserTableDef.USER;
+
+/**
+ * @author 王帅
+ * @since 2023-06-09
+ */
+class MapperUtilTest {
+
+ @Test
+ void testOptimizeCountQueryWrapper() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(USER.USER_ID, USER.USER_NAME, ROLE.ALL_COLUMNS)
+ .from(USER.as("u"))
+ .leftJoin(USER_ROLE).as("ur").on(USER_ROLE.USER_ID.eq(USER.USER_ID))
+ .leftJoin(ROLE).as("r").on(USER_ROLE.ROLE_ID.eq(ROLE.ROLE_ID))
+ .where(USER.USER_ID.eq(3))
+ .and(USER_ROLE.ROLE_ID.eq(6))
+ .groupBy(ROLE.ROLE_ID);
+ System.out.println(queryWrapper.toSQL());
+ System.out.println(MapperUtil.rawCountQueryWrapper(queryWrapper).toSQL());
+ System.out.println(MapperUtil.optimizeCountQueryWrapper(queryWrapper).toSQL());
+ }
+
+ /**
+ * 测试 (sql1) union (sql2)
+ */
+ @Test
+ void testOptimizeCountQueryWrapperOfUnion1() {
+ //简单union
+ //SELECT `user_id`, `user_name` FROM `tb_user` WHERE `user_id` = 1 order by user_id desc
+ QueryWrapper union1 = QueryWrapper.create().select(USER.USER_ID, USER.USER_NAME).from(USER).where(USER.USER_ID.eq(1)).orderBy(USER.USER_ID.desc());
+ //SELECT `user_id`, `user_name` FROM `tb_user` WHERE `user_name` LIKE '%test%'
+ QueryWrapper union2 = QueryWrapper.create().select(USER.USER_ID, USER.USER_NAME).from(USER).where(USER.USER_NAME.like("test"));
+
+ QueryWrapper query1 = union1.union(union2);
+
+ String sql = MapperUtil.optimizeCountQueryWrapper(query1).toSQL();
+ //SELECT COUNT(*) AS `total` FROM ((SELECT `user_id`, `user_name` FROM `tb_user` WHERE `user_id` = 1) UNION (SELECT `user_id`, `user_name` FROM `tb_user` WHERE `user_name` LIKE '%test%')) AS `t`
+ System.out.println(sql);
+ }
+
+ /**
+ * 测试 (sql1 ) union (sql2 with group by)
+ */
+ @Test
+ void testOptimizeCountQueryWrapperOfUnion2() {
+ //with group by union
+ //SELECT `user_id`, `user_name` FROM `tb_user` WHERE `user_id` = 1 order by user_id desc
+ QueryWrapper union1 = QueryWrapper.create().select(USER.USER_ID, USER.USER_NAME).from(USER).where(USER.USER_ID.eq(1)).orderBy(USER.USER_ID.desc());
+ //SELECT `user_id`, `user_name` FROM `tb_user` WHERE `user_name` LIKE '%test%' group by user_id, user_name
+ QueryWrapper union2 = QueryWrapper.create().select(USER.USER_ID, USER.USER_NAME).from(USER).where(USER.USER_NAME.like("test")).groupBy(USER.USER_ID, USER.USER_NAME);
+
+ QueryWrapper query1 = union1.union(union2);
+
+ //SELECT COUNT(*) AS `total` FROM ((SELECT `user_id`, `user_name` FROM `tb_user` WHERE `user_id` = 1) UNION (SELECT `user_id`, `user_name` FROM `tb_user` WHERE `user_name` LIKE '%test%' GROUP BY `user_id`, `user_name`)) AS `t`
+ String sql = MapperUtil.optimizeCountQueryWrapper(query1).toSQL();
+ System.out.println(sql);
+ }
+
+ /**
+ * 测试 (sql1) union (sql2 union sql3)
+ */
+ @Test
+ void testOptimizeCountQueryWrapperOfUnion3() {
+ //with sub query union
+ //SELECT `user_id`, `user_name` FROM `tb_user` WHERE `user_id` = 1 order by user_id desc
+ QueryWrapper union1 = QueryWrapper.create().select(USER.USER_ID, USER.USER_NAME).from(USER).where(USER.USER_ID.eq(1)).orderBy(USER.USER_ID.desc());
+ //SELECT `user_id`, `user_name` FROM `tb_user` WHERE `user_name` LIKE '%test%' group by user_id, user_name
+ QueryWrapper union2 = QueryWrapper.create().select(USER.USER_ID, USER.USER_NAME).from(USER).where(USER.USER_NAME.like("test")).orderBy(USER.USER_NAME.desc());
+
+ QueryWrapper union3 = QueryWrapper.create().select(USER.USER_ID, USER.USER_NAME).from(USER).where(USER.PASSWORD.isNull()).orderBy(USER.USER_NAME.desc());
+
+
+ QueryWrapper query1 = union1.union(union2.union(union3));
+
+ //SELECT COUNT(*) AS `total` FROM ((SELECT `user_id`, `user_name` FROM `tb_user` WHERE `user_id` = 1) UNION ((SELECT `user_id`, `user_name` FROM `tb_user` WHERE `user_name` LIKE '%test%') UNION (SELECT `user_id`, `user_name` FROM `tb_user` WHERE `password` IS NULL ))) AS `t`
+ String sql = MapperUtil.optimizeCountQueryWrapper(query1).toSQL();
+
+ System.out.println(sql);
+ }
+
+ @Test
+ void testOptimizeCountQueryWrapperOfUnion4() {
+ //with sub query union
+ //SELECT `user_id`, `user_name` FROM `tb_user` WHERE `user_id` = 1 order by user_id desc
+ QueryWrapper union1 = QueryWrapper.create().select(USER.USER_ID, USER.USER_NAME).from(USER).where(USER.USER_ID.eq(1)).orderBy(USER.USER_ID.desc());
+ //SELECT `user_id`, `user_name` FROM `tb_user` WHERE `user_name` LIKE '%test%' group by user_id, user_name
+ QueryWrapper union2 = QueryWrapper.create().select(USER.USER_ID, USER.USER_NAME).from(USER).where(USER.USER_NAME.like("test")).orderBy(USER.USER_NAME.desc());
+
+ QueryWrapper union3 = QueryWrapper.create().from(union2).as("a");
+
+
+ QueryWrapper query1 = union1.union(union3);
+
+ //SELECT COUNT(*) AS `total` FROM ((SELECT `user_id`, `user_name` FROM `tb_user` WHERE `user_id` = 1) UNION ((SELECT `user_id`, `user_name` FROM `tb_user` WHERE `user_name` LIKE '%test%') UNION (SELECT `user_id`, `user_name` FROM `tb_user` WHERE `password` IS NULL ))) AS `t`
+ String sql = MapperUtil.optimizeCountQueryWrapper(query1).toSQL();
+
+ System.out.println(sql);
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/common/QueryWrapperTest.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/common/QueryWrapperTest.java
new file mode 100644
index 00000000..effecd5e
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/common/QueryWrapperTest.java
@@ -0,0 +1,202 @@
+/*
+ * 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.loveqq.test.common;
+
+import com.github.vertical_blank.sqlformatter.SqlFormatter;
+import com.mybatisflex.core.query.CPI;
+import com.mybatisflex.core.query.QueryColumnBehavior;
+import com.mybatisflex.core.query.QueryCondition;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.mybatisflex.core.query.RawQueryTable;
+import com.mybatisflex.loveqq.test.model.table.RoleTableDef;
+import com.mybatisflex.loveqq.test.model.table.UserTableDef;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static com.mybatisflex.core.query.QueryMethods.case_;
+import static com.mybatisflex.core.query.QueryMethods.column;
+import static com.mybatisflex.core.query.QueryMethods.count;
+import static com.mybatisflex.core.query.QueryMethods.distinct;
+import static com.mybatisflex.core.query.QueryMethods.select;
+import static com.mybatisflex.loveqq.test.model.table.RoleTableDef.ROLE;
+import static com.mybatisflex.loveqq.test.model.table.UserRoleTableDef.USER_ROLE;
+import static com.mybatisflex.loveqq.test.model.table.UserTableDef.USER;
+
+/**
+ * @author 王帅
+ * @since 2023-06-12
+ */
+class QueryWrapperTest {
+
+ @Test
+ void test01() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(USER.USER_ID, ROLE.ALL_COLUMNS)
+ .hint("hint")
+ .from(USER.as("u"))
+ .leftJoin(USER_ROLE).as("ur").on(USER.USER_ID.eq(USER_ROLE.USER_ID))
+ .leftJoin(ROLE).as("r").on(ROLE.ROLE_ID.eq(USER_ROLE.ROLE_ID))
+ .where(USER.USER_ID.eq(3))
+ .and(USER.USER_ID.eq(ROLE.ROLE_ID))
+ .or(USER.USER_ID.in(4, 5, 6))
+ .groupBy(ROLE.ROLE_KEY)
+ .having(ROLE.ROLE_ID.eq(USER_ROLE.ROLE_ID))
+ .orderBy(ROLE.ROLE_NAME.asc());
+
+ Assertions.assertEquals("SELECT /*+ hint */ `u`.`user_id`, `r`.* " +
+ "FROM `tb_user` AS `u` " +
+ "LEFT JOIN `tb_user_role` AS `ur` ON `u`.`user_id` = `ur`.`user_id` " +
+ "LEFT JOIN `tb_role` AS `r` ON `r`.`role_id` = `ur`.`role_id` " +
+ "WHERE `u`.`user_id` = 3 AND `u`.`user_id` = `r`.`role_id` OR `u`.`user_id` IN (4, 5, 6) " +
+ "GROUP BY `r`.`role_key` " +
+ "HAVING `r`.`role_id` = `ur`.`role_id` " +
+ "ORDER BY `r`.`role_name` ASC"
+ , queryWrapper.toSQL());
+
+ System.out.println(queryWrapper.toSQL());
+
+
+ System.out.println(queryWrapper.toSQL());
+ }
+
+ @Test
+ void test02() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(count(distinct(USER.USER_ID)), case_()
+ .when(USER.USER_ID.eq(3)).then("x3")
+ .when(USER.USER_ID.eq(5)).then("x4")
+ .end(),
+ distinct(USER.USER_ID.add(4)),
+ USER.USER_NAME,
+ ROLE.ALL_COLUMNS)
+ .from(USER.as("u"))
+ .leftJoin(USER_ROLE).as("ur").on(USER_ROLE.USER_ID.eq(USER.USER_ID))
+ .leftJoin(ROLE).as("r").on(USER_ROLE.ROLE_ID.eq(ROLE.ROLE_ID))
+ .where(USER.USER_ID.eq(3))
+ .and(ROLE.ROLE_NAME.in(Arrays.asList(1, 2, 3)))
+ .or(ROLE.ROLE_ID.ge(USER.USER_ID))
+ .groupBy(ROLE.ROLE_NAME)
+ .having(ROLE.ROLE_ID.ge(7))
+ .orderBy(ROLE.ROLE_NAME.asc());
+
+ Assertions.assertEquals("SELECT COUNT(DISTINCT `u`.`user_id`), " +
+ "CASE WHEN `u`.`user_id` = 3 THEN 'x3' WHEN `u`.`user_id` = 5 " +
+ "THEN 'x4' END, DISTINCT `u`.`user_id` + 4, `u`.`user_name`, `r`.* " +
+ "FROM `tb_user` AS `u` " +
+ "LEFT JOIN `tb_user_role` AS `ur` ON `ur`.`user_id` = `u`.`user_id` " +
+ "LEFT JOIN `tb_role` AS `r` ON `ur`.`role_id` = `r`.`role_id` " +
+ "WHERE `u`.`user_id` = 3 AND `r`.`role_name` IN (1, 2, 3) OR `r`.`role_id` >= `u`.`user_id` " +
+ "GROUP BY `r`.`role_name` " +
+ "HAVING `r`.`role_id` >= 7 " +
+ "ORDER BY `r`.`role_name` ASC"
+ , queryWrapper.toSQL());
+
+ System.out.println(queryWrapper.toSQL());
+ }
+
+ @Test
+ void test03() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select()
+ .from(USER.as("u"))
+ .leftJoin(USER_ROLE).as("ur").on(USER.USER_ID.eq(USER_ROLE.USER_ID))
+ .where(QueryCondition.createEmpty())
+ .and(USER.USER_ID.eq(1).or(USER.USER_ID.in(
+ QueryWrapper.create().select(USER_ROLE.USER_ID).from(USER_ROLE)))
+ )
+ .and(USER_ROLE.USER_ID.eq(1));
+ System.out.println(queryWrapper.toSQL());
+ QueryCondition whereQueryCondition = CPI.getWhereQueryCondition(queryWrapper);
+ boolean contained = CPI.containsTable(whereQueryCondition, "tb_user_role");
+
+ Assertions.assertTrue(contained);
+ }
+
+ @Test
+ void test04() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select("a.*")
+ .from(new RawQueryTable("(select * from app)").as("a"));
+
+ Assertions.assertEquals("SELECT a.* FROM (select * from app) AS `a`"
+ , queryWrapper.toSQL());
+
+ System.out.println(queryWrapper.toSQL());
+ }
+
+ @Test
+ void test05() {
+ RoleTableDef r = ROLE.as("r");
+ UserTableDef u = USER.as("u");
+
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(USER.USER_NAME)
+ .from(USER)
+ .leftJoin(u).on(u.USER_ID.eq(USER.USER_ID))
+ .where(USER.USER_ID.eq(1))
+ // 子查询里面用了父查询里面的表
+ .and(column(select(r.ROLE_ID).from(r).where(u.USER_ID.eq(r.ROLE_ID))).le(2));
+
+ String sql = SqlFormatter.format(queryWrapper.toSQL());
+ System.out.println(sql);
+
+ Assertions.assertEquals("SELECT\n" +
+ " ` tb_user `.` user_name `\n" +
+ "FROM\n" +
+ " ` tb_user `\n" +
+ " LEFT JOIN ` tb_user ` AS ` u ` ON ` u `.` user_id ` = ` tb_user `.` user_id `\n" +
+ "WHERE\n" +
+ " ` tb_user `.` user_id ` = 1\n" +
+ " AND (\n" +
+ " SELECT\n" +
+ " ` r `.` role_id `\n" +
+ " FROM\n" +
+ " ` tb_role ` AS ` r `\n" +
+ " WHERE\n" +
+ " ` u `.` user_id ` = ` r `.` role_id `\n" +
+ " ) <= 2", sql);
+ }
+
+ @Test
+ void test06() {
+ List ids = Collections.emptyList();
+
+ QueryColumnBehavior.setIgnoreFunction(QueryColumnBehavior.IGNORE_EMPTY);
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .from(USER)
+ .where(USER.USER_ID.in(ids, v -> !v.isEmpty()))
+ .and(USER.USER_ID.eq(null, true))
+ .and(USER.USER_NAME.eq("", true));
+ QueryColumnBehavior.setIgnoreFunction(QueryColumnBehavior.IGNORE_NULL);
+
+ String sql = SqlFormatter.format(queryWrapper.toSQL());
+ System.out.println(sql);
+
+ Assertions.assertEquals("SELECT\n" +
+ " *\n" +
+ "FROM\n" +
+ " ` tb_user `\n" +
+ "WHERE\n" +
+ " ` user_id ` = null\n" +
+ " AND ` user_name ` = ''", sql);
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/common/ReflectTest.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/common/ReflectTest.java
new file mode 100644
index 00000000..3b505834
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/common/ReflectTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.loveqq.test.common;
+
+import com.mybatisflex.core.util.ClassUtil;
+import com.mybatisflex.loveqq.test.model.Account;
+import org.apache.ibatis.reflection.TypeParameterResolver;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Type;
+import java.util.List;
+
+/**
+ * @author 王帅
+ * @since 2023-06-14
+ */
+class ReflectTest {
+
+ @Test
+ void test() {
+ List allFields = ClassUtil.getAllFields(Account.class);
+ for (Field field : allFields) {
+ Type type = TypeParameterResolver.resolveFieldType(field, Account.class);
+ System.out.println("field: " + field + "----->Type:" + type);
+ }
+
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/common/SerialUtil.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/common/SerialUtil.java
new file mode 100644
index 00000000..e19519a1
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/common/SerialUtil.java
@@ -0,0 +1,105 @@
+/*
+ * 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.loveqq.test.common;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.mybatisflex.core.query.QueryCondition;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * 序列化工具类。
+ *
+ * @author 王帅
+ * @since 2023-06-10
+ */
+public class SerialUtil {
+
+ public static byte[] writeObject(Object obj) {
+ try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(bos)) {
+ oos.writeObject(obj);
+ oos.flush();
+ return bos.toByteArray();
+ } catch (IOException e) {
+ return new byte[0];
+ }
+ }
+
+ public static Object readObject(byte[] bytes) {
+ try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
+ ObjectInputStream ois = new ObjectInputStream(bis)) {
+ return ois.readObject();
+ } catch (IOException | ClassNotFoundException e) {
+ return null;
+ }
+ }
+
+ public static T cloneObject(Object obj) {
+ //noinspection unchecked
+ return (T) readObject(writeObject(obj));
+ }
+
+ public static String toJSONString(Object obj) {
+ return JSON.toJSONString(obj, JSONWriter.Feature.FieldBased,
+ JSONWriter.Feature.WriteClassName,
+ JSONWriter.Feature.NotWriteRootClassName,
+ JSONWriter.Feature.ReferenceDetection);
+ }
+
+ public static T parseObject(String str, Class tClass) {
+ return JSON.parseObject(str, tClass, JSONReader.Feature.FieldBased,
+ JSONReader.Feature.SupportClassForName);
+ }
+
+ public static T cloneObject(Object obj, Class tClass) {
+ return parseObject(toJSONString(obj), tClass);
+ }
+
+
+ /**
+ * 使用jackson对QueryWrapper进行序列化反序列化操作,需要注意QueryCondition的protected属性以及prev和next的递归问题。
+ *
+ * @return Jackson序列化映射
+ */
+ public static ObjectMapper jacksonMapper(){
+ ObjectMapper mapper = new ObjectMapper();
+ // 为了将QueryWrapper里的protected属性可见
+ mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.PROTECTED_AND_PUBLIC);
+ // 避免QueryCondition里的prev和next在序列化时出现递归调用错误
+ mapper.addMixIn(QueryCondition.class,QueryConditionMixIn.class);
+ return mapper;
+ }
+
+ /**
+ * 因无法修改QueryCondition而添加的映射属性包装
+ */
+ class QueryConditionMixIn{
+ @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
+ protected QueryCondition prev;
+ }
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/AccountMapperTest.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/AccountMapperTest.java
new file mode 100644
index 00000000..72331295
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/AccountMapperTest.java
@@ -0,0 +1,232 @@
+/*
+ * 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.loveqq.test.mapper;
+
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Autowired;
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Component;
+import com.mybatisflex.core.logicdelete.LogicDeleteManager;
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.query.QueryMethods;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.mybatisflex.core.row.Db;
+import com.mybatisflex.core.update.UpdateWrapper;
+import com.mybatisflex.core.util.UpdateEntity;
+import com.mybatisflex.loveqq.test.LoveqqExtension;
+import com.mybatisflex.loveqq.test.model.Account;
+import com.mybatisflex.loveqq.test.model.AccountVO;
+import com.mybatisflex.loveqq.test.model.AccountVO2;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.util.Date;
+
+import static com.mybatisflex.core.query.QueryMethods.column;
+import static com.mybatisflex.core.query.QueryMethods.concat;
+import static com.mybatisflex.core.query.QueryMethods.distinct;
+import static com.mybatisflex.loveqq.test.model.table.AccountTableDef.ACCOUNT;
+import static com.mybatisflex.loveqq.test.model.table.RoleTableDef.ROLE;
+import static com.mybatisflex.loveqq.test.model.table.UserRoleTableDef.USER_ROLE;
+import static com.mybatisflex.loveqq.test.model.table.UserTableDef.USER;
+
+/**
+ * @author 王帅
+ * @since 2023-06-13
+ */
+@Component
+@ExtendWith(LoveqqExtension.class)
+@SuppressWarnings("all")
+class AccountMapperTest {
+
+ @Autowired
+ private AccountMapper accountMapper;
+
+ @Autowired
+ private MyAccountMapper myAccountMapper;
+
+ @Test
+ void testAppendCondition() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .where(ACCOUNT.ID.ge(0));
+ Page page = Page.of(1, 10);
+ myAccountMapper.xmlPaginate("selectByName", page, queryWrapper);
+ Assertions.assertTrue(page.getRecords().size() > 0);
+ }
+
+ @Test
+ void testInsertRaw() {
+ Account account = UpdateEntity.of(Account.class);
+ account.setUserName("I'm a joker.");
+ account.setBirthday(new Date());
+ UpdateWrapper wrapper = (UpdateWrapper) account;
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(ACCOUNT.AGE)
+ .from(ACCOUNT)
+ .where(ACCOUNT.ID.eq(1));
+ wrapper.set(ACCOUNT.AGE, queryWrapper);
+ wrapper.set(ACCOUNT.BIRTHDAY, QueryMethods.now());
+ Assertions.assertThrows(Throwable.class, () -> accountMapper.insert(account));
+ }
+
+ @Test
+ void testCount() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select()
+ .from(ACCOUNT)
+ .groupBy(ACCOUNT.AGE);
+
+ long count = accountMapper.selectCountByQuery(queryWrapper);
+
+// Assertions.assertEquals(2, count);
+
+ queryWrapper = QueryWrapper.create()
+ .select(distinct(ACCOUNT.AGE))
+ .from(ACCOUNT);
+
+ count = accountMapper.selectCountByQuery(queryWrapper);
+
+// Assertions.assertEquals(2, count);
+ }
+
+ /**
+ * 测试db执行的情况下, sql日志打印情况
+ */
+ @Test
+ void testDbSqlLogger() {
+ QueryWrapper wrapper = QueryWrapper.create()
+ .select(ACCOUNT.ALL_COLUMNS)
+ .from(ACCOUNT);
+
+ Db.selectOneByQuery(wrapper);
+ }
+
+ @Test
+ void testInsert() {
+ Account account = new Account();
+ account.setBirthday(new Date());
+ account.setUserName("张三");
+ account.setAge(18);
+ accountMapper.insert(account);
+ }
+
+ @Test
+ void testUpdate() {
+ Account account = new Account();
+ account.setId(1L);
+ account.setAge(58);
+ accountMapper.update(account);
+ }
+
+ @Test
+ void testDelete() {
+ accountMapper.deleteById(1L);
+ }
+
+ @Test
+ void testSelect() {
+ accountMapper.selectListByQuery(QueryWrapper.create()).forEach(System.err::println);
+ }
+
+ @Test
+ void testGenericEntity() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(ACCOUNT.ALL_COLUMNS, ROLE.ALL_COLUMNS)
+ .from(ACCOUNT)
+ .leftJoin(USER_ROLE).on(USER_ROLE.USER_ID.eq(ACCOUNT.ID))
+ .leftJoin(ROLE).on(USER_ROLE.ROLE_ID.eq(ROLE.ROLE_ID));
+ accountMapper.selectListByQueryAs(queryWrapper, AccountVO.class).forEach(System.err::println);
+ }
+
+ @Test
+ void testEnum() {
+ Account account = new Account();
+ account.setId(1L);
+ account.setAge(18);
+ int result = accountMapper.update(account);
+ System.out.println(result);
+ }
+
+ @Test
+ void testSelectListWithNullQuery() {
+ Assertions.assertThrows(Exception.class, () -> accountMapper.selectListByQuery(null));
+ Assertions.assertThrows(Exception.class, () -> Db.selectListByQuery("tb_account", null));
+ }
+
+ @Test
+ void testUpdateAll() {
+ Account account = new Account();
+ account.setAge(10);
+ Assertions.assertThrows(Exception.class, () ->
+ LogicDeleteManager.execWithoutLogicDelete(() -> accountMapper.updateByQuery(account, QueryWrapper.create())));
+ }
+
+ @Test
+ void testDeleteAll() {
+ Assertions.assertThrows(Exception.class, () ->
+ LogicDeleteManager.execWithoutLogicDelete(() -> accountMapper.deleteByQuery(QueryWrapper.create())));
+ }
+
+ @Test
+ void testAs() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(ACCOUNT.ID,
+ ACCOUNT.AGE,
+ USER.USER_ID,
+ USER.USER_NAME)
+ .from(ACCOUNT.as("a"), USER.as("u"))
+ .where(ACCOUNT.ID.eq(1))
+ .limit(1);
+ AccountVO2 account = accountMapper.selectOneByQueryAs(queryWrapper, AccountVO2.class);
+ System.out.println(account);
+ }
+
+ @Test
+ void testAs0() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(ACCOUNT.ID.as("account_id"),
+ ACCOUNT.AGE,
+ concat(column("'account name: '"), ACCOUNT.USER_NAME).as("user_name"),
+ USER.USER_ID,
+ concat(column("'user name: '"), USER.USER_NAME).as("1_account_name"))
+ .from(ACCOUNT.as("a"), USER.as("u"))
+ .where(ACCOUNT.ID.eq(1))
+ .limit(1);
+ AccountVO2 account = accountMapper.selectOneByQueryAs(queryWrapper, AccountVO2.class);
+ System.out.println(account);
+ }
+
+ @Test
+ void testAs1() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(ACCOUNT.ID.as(AccountVO2::getId),
+ ACCOUNT.AGE,
+ concat(column("'account name: '"), ACCOUNT.USER_NAME).as(AccountVO2::getUserName),
+ USER.USER_ID,
+ concat(column("'user name: '"), USER.USER_NAME).as("1_account_name"))
+ .from(ACCOUNT.as("a"), USER.as("u"))
+ .where(ACCOUNT.ID.eq(1))
+ .limit(1);
+ AccountVO2 account = accountMapper.selectOneByQueryAs(queryWrapper, AccountVO2.class);
+ System.out.println(account);
+ }
+
+ @Test
+ void testIgnoreColumn() {
+ accountMapper.selectListByQuery(QueryWrapper.create());
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/ActiveRecordTest.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/ActiveRecordTest.java
new file mode 100644
index 00000000..ebf65212
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/ActiveRecordTest.java
@@ -0,0 +1,208 @@
+/*
+ * 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.loveqq.test.mapper;
+
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Component;
+import com.mybatisflex.core.mybatis.Mappers;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.mybatisflex.loveqq.test.LoveqqExtension;
+import com.mybatisflex.loveqq.test.model.Good;
+import com.mybatisflex.loveqq.test.model.User;
+import com.mybatisflex.loveqq.test.model.table.RoleTableDef;
+import com.mybatisflex.loveqq.test.model.table.UserRoleTableDef;
+import com.mybatisflex.loveqq.test.model.table.UserTableDef;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import static com.mybatisflex.loveqq.test.model.table.GoodTableDef.GOOD;
+import static com.mybatisflex.loveqq.test.model.table.RoleTableDef.ROLE;
+import static com.mybatisflex.loveqq.test.model.table.UserRoleTableDef.USER_ROLE;
+import static com.mybatisflex.loveqq.test.model.table.UserTableDef.USER;
+
+/**
+ * @author 王帅
+ * @since 2023-07-23
+ */
+@Component
+@ExtendWith(LoveqqExtension.class)
+class ActiveRecordTest {
+
+ @Test
+ void testMapper() {
+ Good good = Good.create();
+
+ good.setPrice(28.0);
+
+ GoodMapper goodMapper = (GoodMapper) Mappers.ofEntityClass(Good.class);
+
+ goodMapper.selectListByQuery(QueryWrapper.create(good));
+ }
+
+ @Test
+ void testInsert() {
+ boolean saved = Good.create()
+ .setPrice(28.0)
+ .setName("摆渡人")
+ .save();
+
+ Assertions.assertTrue(saved);
+ }
+
+ @Test
+ void testInsertCallback() {
+ Integer goodId = Good.create()
+ .setPrice(28.0)
+ .setName("摆渡人")
+ .saveOpt()
+ .orElseThrow(RuntimeException::new)
+ .getGoodId();
+
+ System.out.println(goodId);
+ }
+
+ @Test
+ void testUpdate() {
+ Good.create()
+ .setGoodId(11)
+ .setPrice(38.0)
+ .updateById();
+ }
+
+ @Test
+ void testDelete() {
+ boolean removed = Good.create()
+ .setGoodId(1)
+ .removeById();
+
+ System.out.println(removed);
+ }
+
+ @Test
+ void testSelectById() {
+ Good good = Good.create()
+ .setGoodId(11)
+ .oneById();
+
+ System.out.println(good);
+ }
+
+ @Test
+ void testSelectOne() {
+ Good good1 = Good.create()
+ .setName("摆渡人")
+ .one();
+
+ Good good2 = Good.create()
+ .where(GOOD.NAME.eq("摆渡人"))
+ .one();
+
+ Good good3 = Good.create()
+ .where(Good::getName).eq("摆渡人")
+ .one();
+
+ System.out.println(good1);
+ System.out.println(good2);
+ System.out.println(good3);
+ }
+
+ @Test
+ void testSelectList() {
+ Good.create()
+ .where(GOOD.PRICE.ge(28.0))
+ .list()
+ .forEach(System.out::println);
+ }
+
+ @Test
+ void testRelation() {
+ User user1 = User.create()
+ .as("u")
+ .select(USER.DEFAULT_COLUMNS, ROLE.DEFAULT_COLUMNS)
+ .leftJoin(USER_ROLE.as("ur")).on(USER_ROLE.USER_ID.eq(USER.USER_ID))
+ .leftJoin(ROLE.as("r")).on(USER_ROLE.ROLE_ID.eq(ROLE.ROLE_ID))
+ .where(USER.USER_ID.eq(2))
+ .list()
+ .get(0);
+
+ User user2 = User.create()
+ .where(USER.USER_ID.eq(2))
+ .withRelations()
+ .one();
+
+ Assertions.assertEquals(user1.toString(), user2.toString());
+ }
+
+ @Test
+ void testRelationsQuery() {
+ User.create()
+ .where(USER.USER_ID.ge(1))
+ .withRelations() // 使用 Relations Query 的方式进行关联查询。
+ .maxDepth(3) // 设置父子关系查询中,默认的递归查询深度。
+ .ignoreRelations("orderList") // 忽略查询部分 Relations 注解标记的属性。
+ .extraConditionParam("id", 100) // 添加额外的 Relations 查询条件。
+ .list()
+ .forEach(System.out::println);
+ }
+
+ @Test
+ void testFieldsQuery() {
+ User.create()
+ .where(USER.USER_ID.ge(1))
+ .withFields() // 使用 Fields Query 的方式进行关联查询。
+ .fieldMapping(User::getRoleList, user -> // 设置属性对应的 QueryWrapper 查询。
+ QueryWrapper.create()
+ .select()
+ .from(ROLE)
+ .where(ROLE.ROLE_ID.in(
+ QueryWrapper.create()
+ .select(USER_ROLE.ROLE_ID)
+ .from(USER_ROLE)
+ .where(USER_ROLE.USER_ID.eq(user.getUserId()))
+ )))
+ .list()
+ .forEach(System.out::println);
+ }
+
+ @Test
+ void testToSql() {
+ String sql1 = User.create()
+ .as("u")
+ .select(USER.ALL_COLUMNS, ROLE.ALL_COLUMNS)
+ .leftJoin(USER_ROLE.as("ur")).on(USER_ROLE.USER_ID.eq(USER.USER_ID))
+ .leftJoin(ROLE.as("r")).on(USER_ROLE.ROLE_ID.eq(ROLE.ROLE_ID))
+ .where(USER.USER_ID.eq(2))
+ .toQueryWrapper()
+ .toSQL();
+
+ UserTableDef u = USER.as("u");
+ RoleTableDef r = ROLE.as("r");
+ UserRoleTableDef ur = USER_ROLE.as("ur");
+
+ String sql2 = User.create()
+ .as(u.getAlias())
+ .select(u.ALL_COLUMNS, r.ALL_COLUMNS)
+ .leftJoin(ur).on(ur.USER_ID.eq(u.USER_ID))
+ .leftJoin(r).on(ur.ROLE_ID.eq(r.ROLE_ID))
+ .where(u.USER_ID.eq(2))
+ .toQueryWrapper()
+ .toSQL();
+
+ Assertions.assertEquals(sql1, sql2);
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/AliasTest.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/AliasTest.java
new file mode 100644
index 00000000..ecf2033e
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/AliasTest.java
@@ -0,0 +1,180 @@
+/*
+ * 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.loveqq.test.mapper;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Autowired;
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Component;
+import com.mybatisflex.core.FlexGlobalConfig;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.mybatisflex.loveqq.test.LoveqqExtension;
+import com.mybatisflex.loveqq.test.alias.SysUser;
+import com.mybatisflex.loveqq.test.alias.UserVO;
+import org.apache.ibatis.mapping.ResultMap;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static com.mybatisflex.core.query.QueryMethods.column;
+import static com.mybatisflex.core.query.QueryMethods.select;
+import static com.mybatisflex.loveqq.test.alias.table.SysDeptTableDef.SYS_DEPT;
+import static com.mybatisflex.loveqq.test.alias.table.SysRoleTableDef.SYS_ROLE;
+import static com.mybatisflex.loveqq.test.alias.table.SysUserTableDef.SYS_USER;
+
+/**
+ * 别名测试。
+ *
+ * @author 王帅
+ * @since 2023-11-16
+ */
+@Component
+@ExtendWith(LoveqqExtension.class)
+class AliasTest {
+
+ @Autowired
+ SysUserMapper userMapper;
+
+ @Autowired
+ ObjectMapper objectMapper;
+
+ void printList(QueryWrapper queryWrapper) {
+ List users = userMapper.selectListByQuery(queryWrapper);
+ Assertions.assertDoesNotThrow(() ->
+ System.out.println(objectMapper.writerWithDefaultPrettyPrinter()
+ .writeValueAsString(users)));
+ }
+
+ @Test
+ void test01() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(SYS_USER.DEFAULT_COLUMNS)
+ .select(SYS_ROLE.DEFAULT_COLUMNS)
+ .from(SYS_USER.as("u"))
+ .leftJoin(SYS_ROLE.as("r")).on(SYS_USER.ID.eq(1));
+
+ printList(queryWrapper);
+ }
+
+ @Test
+ void test02() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(SYS_USER.DEFAULT_COLUMNS)
+ .select(SYS_ROLE.DEFAULT_COLUMNS)
+ .select(SYS_DEPT.DEFAULT_COLUMNS)
+ .from(SYS_USER.as("u"))
+ .leftJoin(SYS_ROLE.as("r")).on(SYS_USER.ID.eq(1))
+ .leftJoin(SYS_DEPT.as("d")).on(SYS_USER.ID.eq(1));
+
+ printList(queryWrapper);
+ }
+
+ @Test
+ void test03() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ // 调整 SELECT 顺序
+ .select(SYS_ROLE.DEFAULT_COLUMNS)
+ .select(SYS_DEPT.DEFAULT_COLUMNS)
+ .select(SYS_USER.DEFAULT_COLUMNS)
+ .from(SYS_USER.as("u"))
+ .leftJoin(SYS_ROLE.as("r")).on(SYS_USER.ID.eq(1))
+ .leftJoin(SYS_DEPT.as("d")).on(SYS_USER.ID.eq(1));
+
+ printList(queryWrapper);
+ }
+
+ @Test
+ void test04() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(column("`u`.`create_by` AS `sys_user$create_by`"))
+ .select(column("`u`.`update_by` AS `sys_user$update_by`"))
+ .from(SYS_USER.as("u"));
+
+ printList(queryWrapper);
+ }
+
+ @Test
+ void test05() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ // 不支持的情况
+ .select(column("`u`.`create_by`"))
+ .select(column("`u`.`update_by`"))
+ .select(column("`d`.`create_by`"))
+ .select(column("`d`.`update_by`"))
+ .from(select(column("*")).from(SYS_USER)).as("u")
+ .from(SYS_DEPT.as("d"));
+
+ printList(queryWrapper);
+ }
+
+ @Test
+ void test06() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ // SELECT 里没有重名列 例如:id
+ .select(SYS_USER.ID, SYS_USER.USER_NAME, SYS_USER.AGE, SYS_USER.BIRTHDAY)
+ .select(SYS_ROLE.CREATE_BY.as("sys_role$create_by"))
+ .from(SYS_USER.as("u"))
+ .leftJoin(SYS_ROLE.as("r")).on(SYS_USER.ID.eq(1));
+
+ Object[] objects = FlexGlobalConfig.getDefaultConfig()
+ .getConfiguration()
+ .getResultMaps()
+ .toArray();
+
+ Object[] resultMaps = Arrays.stream(objects)
+ .filter(e -> e instanceof ResultMap)
+ .map(e -> (ResultMap) e)
+ .filter(e -> e.getId().contains("Sys"))
+ .filter(e -> !e.getId().contains("select"))
+ .toArray();
+
+ System.out.println(resultMaps.length);
+
+ printList(queryWrapper);
+ }
+
+ @Test
+ void test07() throws JsonProcessingException {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(SYS_USER.DEFAULT_COLUMNS)
+ .select(SYS_ROLE.DEFAULT_COLUMNS)
+ .select(SYS_DEPT.DEFAULT_COLUMNS)
+ .from(SYS_USER.as("u"))
+ .leftJoin(SYS_ROLE.as("r")).on(SYS_USER.ID.eq(1))
+ .leftJoin(SYS_DEPT.as("d")).on(SYS_USER.ID.eq(1));
+
+ ObjectWriter objectWriter = objectMapper.writerWithDefaultPrettyPrinter();
+
+ String userList1 = objectWriter.writeValueAsString(userMapper.selectListByQuery(queryWrapper));
+ String userList2 = objectWriter.writeValueAsString(userMapper.selectListByQueryAs(queryWrapper, UserVO.class));
+
+ Assertions.assertEquals(userList1, userList2);
+ }
+
+ @Test
+ void test08() throws JsonProcessingException {
+ SysUser user = userMapper.selectOneById(1);
+ ObjectWriter objectWriter = objectMapper.writerWithDefaultPrettyPrinter();
+ System.out.println(objectWriter.writeValueAsString(user));
+ Assertions.assertTrue(true);
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/CombinedMapperTest.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/CombinedMapperTest.java
new file mode 100644
index 00000000..148ac31f
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/CombinedMapperTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.loveqq.test.mapper;
+
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Autowired;
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Component;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.mybatisflex.loveqq.test.LoveqqExtension;
+import com.mybatisflex.loveqq.test.model.Account;
+import com.mybatisflex.loveqq.test.model.Article;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.util.List;
+
+@Component
+@ExtendWith(LoveqqExtension.class)
+public class CombinedMapperTest {
+
+ @Autowired
+ private AccountMapper accountMapper;
+
+ @Autowired
+ private ArticleMapper articleMapper;
+
+ @Test
+ void testQuery() {
+
+ for (int i = 0; i < 10; i++) {
+ List accounts = accountMapper.selectListByQuery(QueryWrapper.create());
+ List articles = articleMapper.selectListByQuery(QueryWrapper.create());
+ }
+
+
+ System.out.println(">>>>>>finished!!!");
+ }
+
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/MyAccountMapperTest.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/MyAccountMapperTest.java
new file mode 100644
index 00000000..0444a74c
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/MyAccountMapperTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.loveqq.test.mapper;
+
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Autowired;
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Component;
+import com.mybatisflex.loveqq.test.LoveqqExtension;
+import com.mybatisflex.loveqq.test.model.Account;
+import com.mybatisflex.loveqq.test.model.AccountView;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * @author 庄佳彬
+ * @since 2023/4/24 19:37
+ */
+@Component
+@ExtendWith(LoveqqExtension.class)
+class MyAccountMapperTest {
+
+ @Autowired
+ private MyAccountMapper mapper;
+
+ @Test
+ void complexSelect() {
+ AccountView accountView = Assertions.assertDoesNotThrow(() -> mapper.selectViewObject());
+ System.out.println(accountView);
+ }
+
+ @Test
+ void insertBatch() {
+ List accounts = new ArrayList<>();
+ for (int i = 0; i < 10; i++) {
+ Account account = new Account();
+ account.setBirthday(new Date());
+ account.setAge(i % 60);
+ account.setUserName(String.valueOf(i));
+ accounts.add(account);
+ }
+ //删除初始化数据
+ mapper.deleteById(1);
+ mapper.deleteById(2);
+ try {
+ mapper.insertBatch(accounts);
+ } catch (Exception e) {
+ System.out.println("异常");
+ }
+ int i = mapper.insertBatch(accounts, 1000);
+ assertEquals(10, i);
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/OuterMapperTest.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/OuterMapperTest.java
new file mode 100644
index 00000000..bc36bcd9
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/OuterMapperTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.loveqq.test.mapper;
+
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Autowired;
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Component;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.mybatisflex.loveqq.test.LoveqqExtension;
+import com.mybatisflex.loveqq.test.entity.Outer;
+import com.mybatisflex.loveqq.test.entity.table.InnerTableDef;
+import com.mybatisflex.loveqq.test.entity.table.OuterTableDef;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import static com.mybatisflex.loveqq.test.entity.table.InnerTableDef.INNER;
+import static com.mybatisflex.loveqq.test.entity.table.OuterTableDef.OUTER;
+
+/**
+ * @author 王帅
+ * @since 2023-07-01
+ */
+@Component
+@ExtendWith(LoveqqExtension.class)
+class OuterMapperTest {
+
+ @Autowired
+ private OuterMapper outerMapper;
+
+ @Autowired
+ private InnerMapper innerMapper;
+
+ @Test
+ void testInsert() {
+ Outer outer = new Outer();
+ outer.setName("outer 01");
+ int result = outerMapper.insertSelective(outer);
+ Assertions.assertEquals(result,1);
+ }
+
+ @Test
+ void testSelect() {
+ OuterTableDef outer = OUTER.as("o");
+ InnerTableDef inner = INNER.as("i");
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(outer.ID,
+ outer.NAME,
+ inner.ID,
+ inner.TYPE)
+ .from(outer)
+ .leftJoin(inner).on(inner.ID.eq(2))
+ .limit(1);
+ Outer outer1 = outerMapper.selectOneByQuery(queryWrapper);
+ System.out.println(outer1);
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/PatientMapperTest.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/PatientMapperTest.java
new file mode 100644
index 00000000..5f2a703f
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/PatientMapperTest.java
@@ -0,0 +1,30 @@
+package com.mybatisflex.loveqq.test.mapper;
+
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Component;
+import com.mybatisflex.loveqq.test.LoveqqExtension;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import javax.annotation.Resource;
+
+/**
+ * 患者相关测试
+ *
+ * @author Ice 2023/09/26
+ * @version 1.0
+ */
+@Component
+@ExtendWith(LoveqqExtension.class)
+@SuppressWarnings("all")
+public class PatientMapperTest {
+
+ @Resource
+ private PatientMapper patientMapper;
+
+ @Test
+ public void testRelationOneToManySplitBy() {
+// QueryWrapper wrapper = QueryWrapper.create().orderBy(PatientVO1::getPatientId, false).limit(1);
+// PatientVO1 patientVO1 = patientMapper.selectOneWithRelationsByQueryAs(wrapper, PatientVO1.class);
+// System.out.println(JSON.toJSONString(patientVO1));
+ }
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/QueryChainTest.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/QueryChainTest.java
new file mode 100644
index 00000000..ce76ca4b
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/QueryChainTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.loveqq.test.mapper;
+
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Autowired;
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Component;
+import com.mybatisflex.core.field.QueryBuilder;
+import com.mybatisflex.core.query.QueryChain;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.mybatisflex.core.update.UpdateChain;
+import com.mybatisflex.loveqq.test.LoveqqExtension;
+import com.mybatisflex.loveqq.test.model.Gender;
+import com.mybatisflex.loveqq.test.model.User;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import static com.mybatisflex.loveqq.test.model.table.RoleTableDef.ROLE;
+import static com.mybatisflex.loveqq.test.model.table.UserRoleTableDef.USER_ROLE;
+import static com.mybatisflex.loveqq.test.model.table.UserTableDef.USER;
+
+/**
+ * @author 王帅
+ * @since 2023-08-08
+ */
+@Component
+@ExtendWith(LoveqqExtension.class)
+class QueryChainTest {
+
+ @Autowired
+ UserMapper userMapper;
+
+ @Test
+ void testFields() {
+ QueryBuilder builder = user ->
+ QueryWrapper.create()
+ .select()
+ .from(ROLE)
+ .where(ROLE.ROLE_ID.in(
+ QueryWrapper.create()
+ .select(USER_ROLE.ROLE_ID)
+ .from(USER_ROLE)
+ .where(USER_ROLE.USER_ID.eq(user.getUserId()))
+ ));
+
+ User user1 = QueryChain.of(userMapper)
+ .where(USER.USER_ID.eq(1))
+ .withFields()
+ .fieldMapping(User::getRoleList, builder)
+ .one();
+
+ User user2 = User.create()
+ .where(USER.USER_ID.eq(1))
+ .withFields()
+ .fieldMapping(User::getRoleList, builder)
+ .one();
+
+ Assertions.assertEquals(user1.toString(), user2.toString());
+ }
+
+ @Test
+ void testRelations() {
+ User user1 = QueryChain.of(userMapper)
+ .where(USER.USER_ID.eq(2))
+ .withRelations()
+ .one();
+
+ User user2 = User.create()
+ .where(USER.USER_ID.eq(2))
+ .withRelations()
+ .one();
+
+ Assertions.assertEquals(user1.toString(), user2.toString());
+ }
+
+ @Test
+ void testToSql() {
+ String sql = UpdateChain.create(userMapper)
+ .set(USER.USER_NAME, "张三")
+ .set(User::getUserId, Gender.MALE)
+ .where(USER.USER_ID.eq(1))
+ .toSQL();
+
+ System.out.println(sql);
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/UnmappedUserMapperTest.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/UnmappedUserMapperTest.java
new file mode 100644
index 00000000..39032f89
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/UnmappedUserMapperTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.loveqq.test.mapper;
+
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Autowired;
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Component;
+import com.mybatisflex.core.query.QueryColumn;
+import com.mybatisflex.core.query.QueryMethods;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.mybatisflex.loveqq.test.LoveqqExtension;
+import com.mybatisflex.loveqq.test.model.UnmappedUser;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.util.List;
+
+import static com.mybatisflex.loveqq.test.model.table.UnmappedUserTableDef.UNMAPPED_USER;
+
+/**
+ * UnMappedUserMapperTest
+ *
+ * @author wy
+ * @version 1.0
+ * @date 2024/9/12 11:39
+ **/
+@Component
+@ExtendWith(LoveqqExtension.class)
+@SuppressWarnings("all")
+public class UnmappedUserMapperTest {
+
+ @Autowired
+ private UnmappedUserMapper unmappedUserMapper;
+
+ /**
+ * 额外字段查询,数据库中有,但是实体类中没有
+ * 应用:前端具有一定查询数据库能力时,不改后端代码情况下,返回新增字段数据
+ */
+ @Test
+ void testExtraColumn() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(UNMAPPED_USER.ID, UNMAPPED_USER.AGE, UNMAPPED_USER.NAME,
+ // 额外字段查询
+ new QueryColumn("code"))
+ .where(UNMAPPED_USER.ID.in(1, 2, 3));
+ List unmappedUserList = unmappedUserMapper.selectListByQuery(queryWrapper);
+ System.out.println(unmappedUserList);
+ }
+
+ /**
+ * 同名字段
+ * 多数据源下同表同名字段不同含义或者需要同时展示
+ */
+ @Test
+ void testSameColumn() {
+ // 可能多数据源下会存在同表同名字段不同值
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(UNMAPPED_USER.ID, UNMAPPED_USER.AGE, UNMAPPED_USER.NAME,
+ // 同名字段重置
+ UNMAPPED_USER.NAME.as("ext_name"))
+ .where(UNMAPPED_USER.ID.in(1, 2, 3));
+ List unmappedUserList = unmappedUserMapper.selectListByQuery(queryWrapper);
+ System.out.println(unmappedUserList);
+ }
+
+ /**
+ * 计算或者处理的字段
+ * sql中进行处理的字段,不直接映射到实体类上的域
+ */
+ @Test
+ void testCalColumn() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(UNMAPPED_USER.ID, UNMAPPED_USER.NAME, UNMAPPED_USER.AGE,
+ // 字段计算结果
+ QueryMethods.case_()
+ .when(UNMAPPED_USER.AGE.ge(18)).then("adult")
+ .when(UNMAPPED_USER.AGE.le(14)).then("child")
+ .else_("juvenile")
+ .end()
+ .as("age_group"))
+ .where(UNMAPPED_USER.ID.in(1, 2, 3));
+ List unmappedUserList = unmappedUserMapper.selectListByQuery(queryWrapper);
+ System.out.println(unmappedUserList);
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/UserMapperTest.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/UserMapperTest.java
new file mode 100644
index 00000000..0cda4dcd
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/mapper/UserMapperTest.java
@@ -0,0 +1,315 @@
+/*
+ * 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.loveqq.test.mapper;
+
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Autowired;
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Component;
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.mybatisflex.loveqq.test.LoveqqExtension;
+import com.mybatisflex.loveqq.test.model.OrderInfo;
+import com.mybatisflex.loveqq.test.model.User;
+import com.mybatisflex.loveqq.test.model.UserInfo;
+import com.mybatisflex.loveqq.test.model.UserVO;
+import com.mybatisflex.loveqq.test.model.UserVO1;
+import com.mybatisflex.loveqq.test.model.UserVO2;
+import com.mybatisflex.loveqq.test.model.UserVO3;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.util.List;
+
+import static com.mybatisflex.core.query.QueryMethods.select;
+import static com.mybatisflex.loveqq.test.model.table.GoodTableDef.GOOD;
+import static com.mybatisflex.loveqq.test.model.table.IdCardTableDef.ID_CARD;
+import static com.mybatisflex.loveqq.test.model.table.OrderGoodTableDef.ORDER_GOOD;
+import static com.mybatisflex.loveqq.test.model.table.OrderTableDef.ORDER;
+import static com.mybatisflex.loveqq.test.model.table.RoleTableDef.ROLE;
+import static com.mybatisflex.loveqq.test.model.table.UserOrderTableDef.USER_ORDER;
+import static com.mybatisflex.loveqq.test.model.table.UserRoleTableDef.USER_ROLE;
+import static com.mybatisflex.loveqq.test.model.table.UserTableDef.USER;
+
+/**
+ * @author 王帅
+ * @since 2023-06-07
+ */
+@Component
+@ExtendWith(LoveqqExtension.class)
+@SuppressWarnings("all")
+class UserMapperTest {
+
+ @Autowired
+ private UserMapper userMapper;
+
+ @Test
+ void testSelectOne() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(USER.USER_ID, USER.USER_NAME, ROLE.ALL_COLUMNS)
+ .from(USER.as("u"))
+ .leftJoin(USER_ROLE).as("ur").on(USER_ROLE.USER_ID.eq(USER.USER_ID))
+ .leftJoin(ROLE).as("r").on(USER_ROLE.ROLE_ID.eq(ROLE.ROLE_ID))
+ .where(USER.USER_ID.eq(3));
+ System.out.println(queryWrapper.toSQL());
+ // UserVO userVO = userMapper.selectOneByQueryAs(queryWrapper, UserVO.class);
+ // UserVO1 userVO = userMapper.selectOneByQueryAs(queryWrapper, UserVO1.class);
+ // UserVO2 userVO = userMapper.selectOneByQueryAs(queryWrapper, UserVO2.class);
+ UserVO3 userVO = userMapper.selectOneByQueryAs(queryWrapper, UserVO3.class);
+ System.err.println(userVO);
+ }
+
+ @Test
+ void testFieldQuery() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(USER.USER_ID, USER.USER_NAME)
+ .from(USER.as("u"))
+ .where(USER.USER_ID.eq(3));
+ System.out.println(queryWrapper.toSQL());
+ List userVOs = userMapper.selectListByQueryAs(queryWrapper, UserVO.class,
+ fieldQueryBuilder -> fieldQueryBuilder
+ .field(UserVO::getRoleList)
+ .prevent(true)
+ .queryWrapper(user -> QueryWrapper.create()
+ .select()
+ .from(ROLE)
+ .where(ROLE.ROLE_ID.in(
+ select(USER_ROLE.ROLE_ID)
+ .from(USER_ROLE)
+ .where(USER_ROLE.USER_ID.eq(user.getUserId())
+ )
+ )
+ )
+ )
+ );
+ System.err.println(userVOs);
+ }
+
+ @Test
+ void testSelectList() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(USER.USER_ID, USER.USER_NAME, ROLE.ALL_COLUMNS)
+ .from(USER.as("u"))
+ .leftJoin(USER_ROLE).as("ur").on(USER_ROLE.USER_ID.eq(USER.USER_ID))
+ .leftJoin(ROLE).as("r").on(USER_ROLE.ROLE_ID.eq(ROLE.ROLE_ID))
+ .where(USER.USER_ID.eq(3));
+ System.out.println(queryWrapper.toSQL());
+ List userVOS = userMapper.selectListByQueryAs(queryWrapper, UserVO.class);
+ userVOS.forEach(System.err::println);
+ }
+
+ @Test
+ void testSelectListNoJoin() {
+ List users = userMapper.selectListByQueryAs(QueryWrapper.create(), User.class);
+ users.forEach(System.err::println);
+ List userVOS = userMapper.selectListByQueryAs(QueryWrapper.create(), UserVO.class);
+ userVOS.forEach(System.err::println);
+ }
+
+ @Test
+ void testComplexSelectList() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(USER.ALL_COLUMNS, ID_CARD.ID_NUMBER, ROLE.ALL_COLUMNS, ORDER.ALL_COLUMNS, GOOD.ALL_COLUMNS)
+ .from(USER.as("u"))
+ .leftJoin(ID_CARD).as("i").on(USER.USER_ID.eq(ID_CARD.ID))
+ .leftJoin(USER_ROLE).as("ur").on(USER.USER_ID.eq(USER_ROLE.USER_ID))
+ .leftJoin(ROLE).as("r").on(ROLE.ROLE_ID.eq(USER_ROLE.ROLE_ID))
+ .leftJoin(USER_ORDER).as("uo").on(USER.USER_ID.eq(USER_ORDER.USER_ID))
+ .leftJoin(ORDER).as("o").on(ORDER.ORDER_ID.eq(USER_ORDER.ORDER_ID))
+ .leftJoin(ORDER_GOOD).as("og").on(ORDER.ORDER_ID.eq(ORDER_GOOD.ORDER_ID))
+ .leftJoin(GOOD).as("g").on(GOOD.GOOD_ID.eq(ORDER_GOOD.GOOD_ID));
+ System.err.println(queryWrapper.toSQL());
+ List userInfos = userMapper.selectListByQueryAs(queryWrapper, UserInfo.class);
+ userInfos.forEach(System.err::println);
+ }
+
+ @Test
+ void testComplexSelectListRelations() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(USER.ALL_COLUMNS, ID_CARD.ID_NUMBER)
+ .from(USER.as("u"))
+ .leftJoin(ID_CARD).as("i").on(USER.USER_ID.eq(ID_CARD.ID))
+ .where(USER.USER_ID.eq(2));
+// List userInfos = userMapper.selectListWithRelationsByQueryAs(queryWrapper, UserInfo.class);
+// userInfos.forEach(System.err::println);
+ UserInfo userInfo = userMapper.selectOneWithRelationsByQueryAs(queryWrapper, UserInfo.class);
+ System.out.println(userInfo);
+ }
+
+ @Test
+ void testComplexSelectListFields() {
+ List userInfos = userMapper.selectListByQueryAs(QueryWrapper.create(), UserInfo.class,
+ c -> c.field(UserInfo::getIdNumber).queryWrapper(userInfo ->
+ QueryWrapper.create()
+ .select(ID_CARD.ID_NUMBER)
+ .from(ID_CARD)
+ .where(ID_CARD.ID.eq(userInfo.getUserId()))
+ ),
+ c -> c.field(UserInfo::getRoleList).prevent().queryWrapper(userInfo ->
+ QueryWrapper.create()
+ .select()
+ .from(ROLE.as("r"))
+ .leftJoin(USER_ROLE).as("ur").on(USER_ROLE.ROLE_ID.eq(ROLE.ROLE_ID))
+ .where(USER_ROLE.USER_ID.eq(userInfo.getUserId()))
+ ),
+ c -> c.field(UserInfo::getOrderInfoList).queryWrapper(userInfo ->
+ /*QueryWrapper.create()
+ .select()
+ .from(ORDER.as("o"))
+ .leftJoin(USER_ORDER).as("uo").on(USER_ORDER.ORDER_ID.eq(ORDER.ORDER_ID))
+ .where(USER_ORDER.USER_ID.eq(userInfo.getUserId()))*/
+ null
+ ),
+ c -> c.nestedField(OrderInfo::getGoodList).prevent().queryWrapper(orderInfo ->
+ QueryWrapper.create()
+ .select()
+ .from(GOOD.as("g"))
+ .leftJoin(ORDER_GOOD).as("og").on(ORDER_GOOD.GOOD_ID.eq(GOOD.GOOD_ID))
+ .where(ORDER_GOOD.ORDER_ID.eq(orderInfo.getOrderId()))
+ )
+ );
+ userInfos.forEach(System.err::println);
+ }
+
+ @Test
+ void testEquals() {
+ QueryWrapper queryWrapper1 = QueryWrapper.create()
+ .select(USER.ALL_COLUMNS, ID_CARD.ID_NUMBER, ROLE.ALL_COLUMNS, ORDER.ALL_COLUMNS, GOOD.ALL_COLUMNS)
+ .from(USER.as("u"))
+ .leftJoin(ID_CARD).as("i").on(USER.USER_ID.eq(ID_CARD.ID))
+ .leftJoin(USER_ROLE).as("ur").on(USER.USER_ID.eq(USER_ROLE.USER_ID))
+ .leftJoin(ROLE).as("r").on(ROLE.ROLE_ID.eq(USER_ROLE.ROLE_ID))
+ .leftJoin(USER_ORDER).as("uo").on(USER.USER_ID.eq(USER_ORDER.USER_ID))
+ .leftJoin(ORDER).as("o").on(ORDER.ORDER_ID.eq(USER_ORDER.ORDER_ID))
+ .leftJoin(ORDER_GOOD).as("og").on(ORDER.ORDER_ID.eq(ORDER_GOOD.ORDER_ID))
+ .leftJoin(GOOD).as("g").on(GOOD.GOOD_ID.eq(ORDER_GOOD.GOOD_ID))
+ .orderBy(USER.USER_ID.asc(), ROLE.ROLE_ID.asc(), ORDER.ORDER_ID.asc(), GOOD.GOOD_ID.asc());
+ List userInfos1 = userMapper.selectListByQueryAs(queryWrapper1, UserInfo.class);
+
+ QueryWrapper queryWrapper2 = QueryWrapper.create()
+ .select(USER.ALL_COLUMNS, ID_CARD.ID_NUMBER)
+ .from(USER.as("u"))
+ .leftJoin(ID_CARD).as("i").on(USER.USER_ID.eq(ID_CARD.ID));
+ List userInfos2 = userMapper.selectListWithRelationsByQueryAs(queryWrapper2, UserInfo.class);
+
+ List userInfos3 = userMapper.selectListByQueryAs(QueryWrapper.create(), UserInfo.class,
+ c -> c.field(UserInfo::getIdNumber).queryWrapper(userInfo ->
+ QueryWrapper.create()
+ .select(ID_CARD.ID_NUMBER)
+ .from(ID_CARD)
+ .where(ID_CARD.ID.eq(userInfo.getUserId()))
+ ),
+ c -> c.field(UserInfo::getRoleList).prevent().queryWrapper(userInfo ->
+ QueryWrapper.create()
+ .select()
+ .from(ROLE.as("r"))
+ .leftJoin(USER_ROLE).as("ur").on(USER_ROLE.ROLE_ID.eq(ROLE.ROLE_ID))
+ .where(USER_ROLE.USER_ID.eq(userInfo.getUserId()))
+ .orderBy(ROLE.ROLE_ID.asc())
+ ),
+ c -> c.field(UserInfo::getOrderInfoList).queryWrapper(userInfo ->
+ QueryWrapper.create()
+ .select()
+ .from(ORDER.as("o"))
+ .leftJoin(USER_ORDER).as("uo").on(USER_ORDER.ORDER_ID.eq(ORDER.ORDER_ID))
+ .where(USER_ORDER.USER_ID.eq(userInfo.getUserId()))
+ .orderBy(ORDER.ORDER_ID.asc())
+ ),
+ c -> c.nestedField(OrderInfo::getGoodList).prevent().queryWrapper(orderInfo ->
+ QueryWrapper.create()
+ .select()
+ .from(GOOD.as("g"))
+ .leftJoin(ORDER_GOOD).as("og").on(ORDER_GOOD.GOOD_ID.eq(GOOD.GOOD_ID))
+ .where(ORDER_GOOD.ORDER_ID.eq(orderInfo.getOrderId()))
+ .orderBy(GOOD.GOOD_ID.asc())
+ )
+ );
+
+ Assertions.assertEquals(userInfos1.toString(), userInfos2.toString());
+ Assertions.assertEquals(userInfos1.toString(), userInfos3.toString());
+ }
+
+ @Test
+ void testCircularReference() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(USER.USER_ID, USER.USER_NAME, ROLE.ALL_COLUMNS)
+ .from(USER.as("u"))
+ .leftJoin(USER_ROLE).as("ur").on(USER_ROLE.USER_ID.eq(USER.USER_ID))
+ .leftJoin(ROLE).as("r").on(USER_ROLE.ROLE_ID.eq(ROLE.ROLE_ID))
+ .where(USER.USER_ID.eq(1));
+ List userVO1s = userMapper.selectListByQueryAs(queryWrapper, UserVO1.class);
+ System.err.println(userVO1s);
+ }
+
+ @Test
+ void testPage() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(USER.USER_ID, USER.USER_NAME, ROLE.ALL_COLUMNS)
+ .from(USER.as("u"))
+ .leftJoin(USER_ROLE).as("ur").on(USER_ROLE.USER_ID.eq(USER.USER_ID))
+ .leftJoin(ROLE).as("r").on(USER_ROLE.ROLE_ID.eq(ROLE.ROLE_ID));
+ System.err.println(queryWrapper.toSQL());
+ Page page = new Page<>();
+ page.setOptimizeCountQuery(false);
+ int pageNumber = 0;
+ do {
+ page = userMapper.paginateAs(page, queryWrapper, UserVO.class);
+ System.err.println(page);
+ page.setPageNumber(page.getPageNumber() + 1);
+ } while (page.hasNext());
+ }
+
+ @Test
+ void testListString() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(USER.USER_ID,
+ USER.USER_NAME,
+ ROLE.ROLE_NAME.as("roles"),
+ ROLE.ROLE_ID.as("role_ids"))
+ .from(USER.as("u"))
+ .leftJoin(USER_ROLE).as("ur").on(USER_ROLE.USER_ID.eq(USER.USER_ID))
+ .leftJoin(ROLE).as("r").on(USER_ROLE.ROLE_ID.eq(ROLE.ROLE_ID))
+ .where(USER.USER_ID.eq(2));
+ UserVO2 user = userMapper.selectOneByQueryAs(queryWrapper, UserVO2.class);
+ System.err.println(user);
+ user = userMapper.selectOneByQueryAs(queryWrapper, UserVO2.class);
+ System.err.println(user);
+ }
+
+
+ @Test
+ void testQueryWrapper() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(USER.USER_ID,
+ USER.USER_NAME,
+ ROLE.ROLE_NAME.as("roles"),
+ ROLE.ROLE_ID.as("role_ids"))
+ .from(USER.as("u"))
+ .leftJoin(USER_ROLE).as("ur").on(USER_ROLE.USER_ID.eq(USER.USER_ID))
+ .leftJoin(ROLE).as("r").on(USER_ROLE.ROLE_ID.eq(ROLE.ROLE_ID))
+ .where(USER.USER_ID.eq(2));
+
+ String sql = queryWrapper.toSQL();
+ System.out.println(sql);
+ }
+
+// @Test
+// public void testFieldBindRelations() {
+// List userVO5List = userMapper.selectListWithRelationsByQueryAs(QueryWrapper.create(), UserVO5.class);
+// System.out.println(userVO5List);
+// }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/service/ArticleServiceTest.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/service/ArticleServiceTest.java
new file mode 100644
index 00000000..4e58fc38
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/service/ArticleServiceTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2022-2024, 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.loveqq.test.service;
+
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Autowired;
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Component;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.mybatisflex.loveqq.test.LoveqqExtension;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.transaction.annotation.Transactional;
+
+import static com.mybatisflex.core.query.QueryMethods.case_;
+import static com.mybatisflex.core.query.QueryMethods.length;
+import static com.mybatisflex.loveqq.test.model.table.AccountTableDef.ACCOUNT;
+import static com.mybatisflex.loveqq.test.model.table.ArticleTableDef.ARTICLE;
+
+/**
+ * @author 王帅
+ * @since 2023-07-22
+ */
+@Component
+@ExtendWith(LoveqqExtension.class)
+class ArticleServiceTest {
+
+ @Autowired
+ ArticleService articleService;
+
+ @Test
+ void testChangeDataSource() {
+ articleService.changeDataSource();
+ Assertions.assertTrue(true);
+ }
+
+ @Test
+ void testChain() {
+ Assertions.assertDoesNotThrow(() ->
+ articleService.queryChain()
+ .select(ARTICLE.DEFAULT_COLUMNS)
+ .from(ARTICLE)
+ .where(ARTICLE.ID.ge(100))
+ .objList()
+ .forEach(System.out::println)
+ );
+ }
+
+ @Test
+ void testExists() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(ARTICLE.DEFAULT_COLUMNS)
+ .from(ARTICLE)
+ .where(ARTICLE.ACCOUNT_ID.eq(1))
+ .orderBy(ARTICLE.ACCOUNT_ID.desc());
+ boolean exists = articleService.exists(queryWrapper);
+ Assertions.assertTrue(exists);
+ }
+
+ @Test
+ @Transactional
+ void testSubUpdate() {
+ QueryWrapper queryWrapper = QueryWrapper.create()
+ .select(ACCOUNT.AGE)
+ .from(ACCOUNT)
+ .where(ACCOUNT.ID.eq(1));
+
+ boolean updated = articleService.updateChain()
+ .set(ARTICLE.CONTENT, "hhhh")
+ .set(ARTICLE.ACCOUNT_ID, queryWrapper)
+ .set(ARTICLE.IS_DELETE, ARTICLE.IS_DELETE.eq(0))
+ .set(ARTICLE.TITLE, case_(length(ARTICLE.TITLE)).when(1).then("title1").else_("大标题").end())
+ .update();
+
+ Assertions.assertTrue(updated);
+ }
+
+}
diff --git a/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/service/FieldMappingTest.java b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/service/FieldMappingTest.java
new file mode 100644
index 00000000..bb34a044
--- /dev/null
+++ b/mybatis-flex-test/mybatis-flex-loveqq-test/src/test/java/com/mybatisflex/loveqq/test/service/FieldMappingTest.java
@@ -0,0 +1,35 @@
+package com.mybatisflex.loveqq.test.service;
+
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Autowired;
+import com.kfyty.loveqq.framework.core.autoconfig.annotation.Component;
+import com.mybatisflex.loveqq.test.LoveqqExtension;
+import com.mybatisflex.loveqq.test.mapper.FieldMappingInnerMapper;
+import com.mybatisflex.loveqq.test.mapper.FieldMappingMapper;
+import com.mybatisflex.loveqq.test.model.FieldMapping;
+import com.mybatisflex.loveqq.test.model.FieldMappingInner;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.util.Date;
+
+@Component
+@ExtendWith(LoveqqExtension.class)
+class FieldMappingTest {
+ @Autowired
+ FieldMappingMapper fieldMappingMapper;
+
+ @Autowired
+ FieldMappingInnerMapper fieldMappingInnerMapper;
+
+ @Test
+ void testFieldMapping() {
+ String fieldId = FieldMapping.create().saveOpt().get().getId();
+ FieldMappingInner.create().setCreateDate(new Date()).setOutId(fieldId).save();
+ FieldMapping.create()
+ .withFields()
+ .fieldMapping(FieldMapping::getInnerDate, con ->
+ FieldMappingInner.create().select(FieldMappingInner::getCreateDate).where(FieldMappingInner::getOutId).eq(con.getId()).toQueryWrapper()
+ )
+ .list().forEach(System.out::println);
+ }
+}
diff --git a/mybatis-flex-test/pom.xml b/mybatis-flex-test/pom.xml
index ac876c75..bebd8e10 100644
--- a/mybatis-flex-test/pom.xml
+++ b/mybatis-flex-test/pom.xml
@@ -20,6 +20,7 @@
mybatis-flex-spring-boot-test
mybatis-flex-spring-cloud-test
+ mybatis-flex-loveqq-test
diff --git a/pom.xml b/pom.xml
index d5e91b29..987dd8d9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -55,6 +55,7 @@
mybatis-flex-spring-boot-starter
mybatis-flex-spring-boot3-starter
mybatis-flex-solon-plugin
+ mybatis-flex-loveqq-starter
mybatis-flex-test
mybatis-flex-processor
@@ -77,7 +78,9 @@
5.3.27
2.7.11
3.0.1
+ 1.1.0-java8
+ 1.18.30
4.13.2
1.19.3
1.5.0