diff --git a/mybatis-flex-spring-boot-starter/src/main/java/com/mybatisflex/spring/boot/FlexTransactionAutoConfiguration.java b/mybatis-flex-spring-boot-starter/src/main/java/com/mybatisflex/spring/boot/FlexTransactionAutoConfiguration.java
index 7586b103..532e65e5 100644
--- a/mybatis-flex-spring-boot-starter/src/main/java/com/mybatisflex/spring/boot/FlexTransactionAutoConfiguration.java
+++ b/mybatis-flex-spring-boot-starter/src/main/java/com/mybatisflex/spring/boot/FlexTransactionAutoConfiguration.java
@@ -39,7 +39,13 @@ import org.springframework.transaction.annotation.TransactionManagementConfigure
*
* @author michael
*/
-@ConditionalOnClass(Db.class)
+@ConditionalOnClass(
+ value = Db.class,
+ name = {
+ "org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration",
+ "org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration",
+ }
+)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@ConditionalOnMissingBean(TransactionManager.class)
@Configuration(proxyBeanMethods = false)
diff --git a/mybatis-flex-spring-boot-starter/src/main/java/com/mybatisflex/spring/boot/MultiDataSourceAutoConfiguration.java b/mybatis-flex-spring-boot-starter/src/main/java/com/mybatisflex/spring/boot/MultiDataSourceAutoConfiguration.java
index 975875d1..7c2578c7 100644
--- a/mybatis-flex-spring-boot-starter/src/main/java/com/mybatisflex/spring/boot/MultiDataSourceAutoConfiguration.java
+++ b/mybatis-flex-spring-boot-starter/src/main/java/com/mybatisflex/spring/boot/MultiDataSourceAutoConfiguration.java
@@ -54,7 +54,13 @@ import java.util.Optional;
@ConditionalOnMybatisFlexDatasource()
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(MybatisFlexProperties.class)
-@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
+@ConditionalOnClass(
+ value = {SqlSessionFactory.class, SqlSessionFactoryBean.class},
+ name = {
+ "org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration",
+ "org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration",
+ }
+)
@AutoConfigureBefore(value = {DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class}
, name = {"com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure",
"com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure"})
diff --git a/mybatis-flex-spring-boot-starter/src/main/java/com/mybatisflex/spring/boot/MybatisFlexAutoConfiguration.java b/mybatis-flex-spring-boot-starter/src/main/java/com/mybatisflex/spring/boot/MybatisFlexAutoConfiguration.java
index c05c4bfe..6828e552 100644
--- a/mybatis-flex-spring-boot-starter/src/main/java/com/mybatisflex/spring/boot/MybatisFlexAutoConfiguration.java
+++ b/mybatis-flex-spring-boot-starter/src/main/java/com/mybatisflex/spring/boot/MybatisFlexAutoConfiguration.java
@@ -96,7 +96,12 @@ import java.util.stream.Stream;
* @author 王帅
*/
@Configuration(proxyBeanMethods = false)
-@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
+@ConditionalOnClass(
+ value = {SqlSessionFactory.class, SqlSessionFactoryBean.class},
+ name = {
+ "org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration",
+ }
+)
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisFlexProperties.class)
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
diff --git a/mybatis-flex-spring-boot4-starter/pom.xml b/mybatis-flex-spring-boot4-starter/pom.xml
new file mode 100644
index 00000000..4659fd1f
--- /dev/null
+++ b/mybatis-flex-spring-boot4-starter/pom.xml
@@ -0,0 +1,133 @@
+
+
+ 4.0.0
+
+ com.mybatis-flex
+ parent
+ ${revision}
+ ../pom.xml
+
+
+ mybatis-flex-spring-boot4-starter
+ mybatis-flex-spring-boot4-starter
+ jar
+
+
+ UTF-8
+
+
+
+
+ com.mybatis-flex
+ mybatis-flex-spring-boot-starter
+ ${project.version}
+
+
+ org.mybatis
+ mybatis-spring
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ ${spring-boot4.version}
+ true
+
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure-processor
+ ${spring-boot4.version}
+ true
+
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure
+ ${spring-boot4.version}
+ true
+
+
+
+ org.springframework.boot
+ spring-boot-jdbc
+ ${spring-boot4.version}
+ true
+
+
+
+ org.mybatis.scripting
+ mybatis-freemarker
+ true
+
+
+
+ org.mybatis.scripting
+ mybatis-velocity
+ true
+
+
+
+ org.mybatis.scripting
+ mybatis-thymeleaf
+ true
+
+
+
+ com.zaxxer
+ HikariCP
+ 4.0.3
+ compile
+ true
+
+
+
+ com.alibaba
+ druid
+ 1.2.18
+ compile
+ true
+
+
+
+ com.github.chris2018998
+ beecp
+ 3.4.1
+ compile
+ true
+
+
+
+ org.apache.commons
+ commons-dbcp2
+ 2.9.0
+ compile
+ true
+
+
+
+ io.seata
+ seata-rm-datasource
+ 1.7.0
+ compile
+ true
+
+
+
+ com.mybatis-flex
+ mybatis-flex-core
+ ${project.version}
+
+
+
+ org.mybatis
+ mybatis-spring
+ 3.0.5
+
+
+
+
diff --git a/mybatis-flex-spring-boot4-starter/src/main/java/com/mybatisflex/spring/boot/v4/FlexTransactionAutoConfiguration.java b/mybatis-flex-spring-boot4-starter/src/main/java/com/mybatisflex/spring/boot/v4/FlexTransactionAutoConfiguration.java
new file mode 100644
index 00000000..124399be
--- /dev/null
+++ b/mybatis-flex-spring-boot4-starter/src/main/java/com/mybatisflex/spring/boot/v4/FlexTransactionAutoConfiguration.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.spring.boot.v4;
+
+import com.mybatisflex.core.row.Db;
+import com.mybatisflex.spring.FlexTransactionManager;
+import org.jspecify.annotations.NonNull;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.AutoConfigureOrder;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration;
+import org.springframework.boot.transaction.autoconfigure.TransactionAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Role;
+import org.springframework.core.Ordered;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionManager;
+import org.springframework.transaction.annotation.TransactionManagementConfigurer;
+
+/**
+ * MyBatis-Flex 事务自动配置。
+ *
+ * @author michael
+ */
+@ConditionalOnClass(
+ value = Db.class,
+ name = {
+ "org.springframework.boot.transaction.autoconfigure.TransactionAutoConfiguration",
+ "org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration",
+ }
+)
+@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+@ConditionalOnMissingBean(TransactionManager.class)
+@Configuration(proxyBeanMethods = false)
+@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
+@AutoConfigureAfter({MybatisFlexAutoConfiguration.class})
+@AutoConfigureBefore(value = {TransactionAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class})
+public class FlexTransactionAutoConfiguration implements TransactionManagementConfigurer {
+
+ /**
+ * 这里使用 final 修饰属性是因为:
+ *
+ * 1、调用 {@link #annotationDrivenTransactionManager} 方法会返回 TransactionManager 对象
+ * 2、{@code @Bean} 注入又会返回 TransactionManager 对象
+ *
+ * 需要保证两个对象的一致性。
+ */
+ private final FlexTransactionManager flexTransactionManager = new FlexTransactionManager();
+
+ @NonNull
+ @Override
+ @Bean(name = "transactionManager")
+ public PlatformTransactionManager annotationDrivenTransactionManager() {
+ return flexTransactionManager;
+ }
+
+}
diff --git a/mybatis-flex-spring-boot4-starter/src/main/java/com/mybatisflex/spring/boot/v4/MultiDataSourceAutoConfiguration.java b/mybatis-flex-spring-boot4-starter/src/main/java/com/mybatisflex/spring/boot/v4/MultiDataSourceAutoConfiguration.java
new file mode 100644
index 00000000..b9f4f060
--- /dev/null
+++ b/mybatis-flex-spring-boot4-starter/src/main/java/com/mybatisflex/spring/boot/v4/MultiDataSourceAutoConfiguration.java
@@ -0,0 +1,159 @@
+/*
+ * 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.spring.boot.v4;
+
+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.dialect.DbType;
+import com.mybatisflex.core.dialect.DbTypeUtil;
+import com.mybatisflex.core.exception.FlexExceptions;
+import com.mybatisflex.core.util.MapUtil;
+import com.mybatisflex.spring.boot.ConditionalOnMybatisFlexDatasource;
+import com.mybatisflex.spring.boot.v4.MybatisFlexProperties.SeataConfig;
+import com.mybatisflex.spring.datasource.DataSourceAdvice;
+import io.seata.rm.datasource.DataSourceProxy;
+import io.seata.rm.datasource.xa.DataSourceProxyXA;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.mybatis.spring.SqlSessionFactoryBean;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
+import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Role;
+
+import javax.sql.DataSource;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * MyBatis-Flex 多数据源的配置支持。
+ *
+ * @author michael
+ * @author 王帅
+ */
+@ConditionalOnMybatisFlexDatasource()
+@Configuration(proxyBeanMethods = false)
+@EnableConfigurationProperties(MybatisFlexProperties.class)
+@ConditionalOnClass(
+ value = {SqlSessionFactory.class, SqlSessionFactoryBean.class},
+ name = {
+ "org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration",
+ "org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration",
+ }
+)
+@AutoConfigureBefore(value = {DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class}
+ , name = {"com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure",
+ "com.alibaba.druid.spring.boot4.autoconfigure.DruidDataSourceAutoConfigure"})
+public class MultiDataSourceAutoConfiguration {
+
+ private final String master;
+
+ private final Map> dataSourceProperties;
+
+ private final SeataConfig seataConfig;
+
+ // 数据源解密器
+ protected final DataSourceDecipher dataSourceDecipher;
+
+
+ public MultiDataSourceAutoConfiguration(MybatisFlexProperties properties
+ , ObjectProvider dataSourceDecipherProvider
+ ) {
+ dataSourceProperties = properties.getDatasource();
+ dataSourceDecipher = dataSourceDecipherProvider.getIfAvailable();
+ seataConfig = properties.getSeataConfig();
+ master = properties.getDefaultDatasourceKey();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public DataSource dataSource() {
+
+ FlexDataSource flexDataSource = null;
+
+ if (dataSourceProperties != null && !dataSourceProperties.isEmpty()) {
+
+ if (dataSourceDecipher != null) {
+ DataSourceManager.setDecipher(dataSourceDecipher);
+ }
+
+ if (master != null) {
+ Map map = dataSourceProperties.remove(master);
+ if (map != null) {
+ // 这里创建master时,flexDataSource一定是null
+ flexDataSource = addDataSource(MapUtil.entry(master, map), null);
+ } else {
+ throw FlexExceptions.wrap("没有找到默认数据源 \"%s\" 对应的配置,请检查您的多数据源配置。", master);
+ }
+ }
+
+ for (Map.Entry> entry : dataSourceProperties.entrySet()) {
+ flexDataSource = addDataSource(entry, flexDataSource);
+ }
+ }
+
+ return flexDataSource;
+ }
+
+ private FlexDataSource addDataSource(Map.Entry> entry, FlexDataSource flexDataSource) {
+ DataSource dataSource = new DataSourceBuilder(entry.getValue()).build();
+ DataSourceManager.decryptDataSource(dataSource);
+
+ // 数据库类型
+ DbType dbType = null;
+ if (seataConfig != null && seataConfig.isEnable()) {
+ if (seataConfig.getSeataMode() == MybatisFlexProperties.SeataMode.XA) {
+ DataSourceProxyXA sourceProxyXa = new DataSourceProxyXA(dataSource);
+ dbType = DbType.findByName(sourceProxyXa.getDbType());
+ dataSource = sourceProxyXa;
+ } else {
+ DataSourceProxy dataSourceProxy = new DataSourceProxy(dataSource);
+ dbType = DbType.findByName(dataSourceProxy.getDbType());
+ dataSource = dataSourceProxy;
+ }
+ }
+
+ // 如果没有构建成功dbType,需要自解析
+ final DataSource lambdaInnerDataSource = dataSource;
+ dbType = Optional.ofNullable(dbType).orElseGet(() -> DbTypeUtil.getDbType(lambdaInnerDataSource));
+ if (flexDataSource == null) {
+ flexDataSource = new FlexDataSource(entry.getKey(), dataSource, dbType, false);
+ } else {
+ flexDataSource.addDataSource(entry.getKey(), dataSource, dbType, false);
+ }
+ return flexDataSource;
+ }
+
+
+ /**
+ * {@link com.mybatisflex.annotation.UseDataSource} 注解切换数据源切面。
+ */
+ @Bean
+ @ConditionalOnMissingBean
+ @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+ public DataSourceAdvice dataSourceAdvice() {
+ return new DataSourceAdvice();
+ }
+
+}
diff --git a/mybatis-flex-spring-boot4-starter/src/main/java/com/mybatisflex/spring/boot/v4/MybatisFlexAutoConfiguration.java b/mybatis-flex-spring-boot4-starter/src/main/java/com/mybatisflex/spring/boot/v4/MybatisFlexAutoConfiguration.java
new file mode 100644
index 00000000..3d3adbd8
--- /dev/null
+++ b/mybatis-flex-spring-boot4-starter/src/main/java/com/mybatisflex/spring/boot/v4/MybatisFlexAutoConfiguration.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright 2015-2022 the original author or authors.
+ *
+ * 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
+ *
+ * https://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.spring.boot.v4;
+
+import com.mybatisflex.core.FlexGlobalConfig;
+import com.mybatisflex.core.datasource.DataSourceDecipher;
+import com.mybatisflex.core.datasource.DataSourceManager;
+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.spring.FlexSqlSessionFactoryBean;
+import com.mybatisflex.spring.boot.*;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.mapping.DatabaseIdProvider;
+import org.apache.ibatis.plugin.Interceptor;
+import org.apache.ibatis.scripting.LanguageDriver;
+import org.apache.ibatis.session.ExecutorType;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.apache.ibatis.type.TypeHandler;
+import org.mybatis.spring.SqlSessionFactoryBean;
+import org.mybatis.spring.SqlSessionTemplate;
+import org.mybatis.spring.mapper.MapperFactoryBean;
+import org.mybatis.spring.mapper.MapperScannerConfigurer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeanWrapper;
+import org.springframework.beans.BeanWrapperImpl;
+import org.springframework.beans.factory.*;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
+import org.springframework.context.EnvironmentAware;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
+import org.springframework.core.env.Environment;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+
+import javax.sql.DataSource;
+import java.beans.PropertyDescriptor;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+
+/**
+ * Mybatis-Flex 的核心配置。
+ *
+ * 参考
+ * MybatisAutoConfiguration.java
+ *
+ * 为 Mybatis-Flex 开启自动配置功能,主要修改以下几个方面:
+ *
+ * 1、替换配置为 mybatis-flex 的配置前缀
+ * 2、修改 SqlSessionFactory 为 FlexSqlSessionFactoryBean
+ * 3、修改 Configuration 为 FlexConfiguration
+ *
+ * @author Eddú Meléndez
+ * @author Josh Long
+ * @author Kazuki Shimizu
+ * @author Eduardo Macarrón
+ * @author michael
+ * @author 王帅
+ */
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnClass(
+ value = {SqlSessionFactory.class, SqlSessionFactoryBean.class},
+ name = {
+ "org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration",
+ }
+)
+@ConditionalOnSingleCandidate(DataSource.class)
+@EnableConfigurationProperties(MybatisFlexProperties.class)
+@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
+public class MybatisFlexAutoConfiguration implements InitializingBean {
+
+ protected static final Logger logger = LoggerFactory.getLogger(MybatisFlexAutoConfiguration.class);
+
+ protected final MybatisFlexProperties properties;
+
+ protected final Interceptor[] interceptors;
+
+ protected final TypeHandler[] typeHandlers;
+
+ protected final LanguageDriver[] languageDrivers;
+
+ protected final ResourceLoader resourceLoader;
+
+ protected final DatabaseIdProvider databaseIdProvider;
+
+ protected final List configurationCustomizers;
+
+ protected final List sqlSessionFactoryBeanCustomizers;
+
+ //数据源解密器
+ protected final DataSourceDecipher dataSourceDecipher;
+
+ //动态表名
+ protected final DynamicTableProcessor dynamicTableProcessor;
+
+ //动态 schema 处理器
+ protected final DynamicSchemaProcessor dynamicSchemaProcessor;
+
+ //多租户
+ protected final TenantFactory tenantFactory;
+
+ //自定义逻辑删除处理器
+ protected final LogicDeleteProcessor logicDeleteProcessor;
+
+ //初始化监听
+ protected final List mybatisFlexCustomizers;
+
+
+ public MybatisFlexAutoConfiguration(MybatisFlexProperties properties, ObjectProvider interceptorsProvider,
+ ObjectProvider typeHandlersProvider, ObjectProvider languageDriversProvider,
+ ResourceLoader resourceLoader, ObjectProvider databaseIdProvider,
+ ObjectProvider> configurationCustomizersProvider,
+ ObjectProvider> sqlSessionFactoryBeanCustomizers,
+ ObjectProvider dataSourceDecipherProvider,
+ ObjectProvider dynamicTableProcessorProvider,
+ ObjectProvider dynamicSchemaProcessorProvider,
+ ObjectProvider tenantFactoryProvider,
+ ObjectProvider logicDeleteProcessorProvider,
+ ObjectProvider mybatisFlexCustomizerProviders
+ ) {
+ this.properties = properties;
+ this.interceptors = interceptorsProvider.getIfAvailable();
+ this.typeHandlers = typeHandlersProvider.getIfAvailable();
+ this.languageDrivers = languageDriversProvider.getIfAvailable();
+ this.resourceLoader = resourceLoader;
+ this.databaseIdProvider = databaseIdProvider.getIfAvailable();
+ this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
+ this.sqlSessionFactoryBeanCustomizers = sqlSessionFactoryBeanCustomizers.getIfAvailable();
+
+ //数据源解密器
+ this.dataSourceDecipher = dataSourceDecipherProvider.getIfAvailable();
+
+ //动态表名
+ this.dynamicTableProcessor = dynamicTableProcessorProvider.getIfAvailable();
+
+ //动态 schema 处理器
+ this.dynamicSchemaProcessor = dynamicSchemaProcessorProvider.getIfAvailable();
+
+ //多租户
+ this.tenantFactory = tenantFactoryProvider.getIfAvailable();
+
+ //逻辑删除处理器
+ this.logicDeleteProcessor = logicDeleteProcessorProvider.getIfAvailable();
+
+ //初始化监听器
+ this.mybatisFlexCustomizers = mybatisFlexCustomizerProviders.orderedStream().collect(Collectors.toList());
+ }
+
+ @Override
+ public void afterPropertiesSet() {
+ // 检测 MyBatis 原生配置文件是否存在
+ checkConfigFileExists();
+
+ // 添加 MyBatis-Flex 全局配置
+ if (properties.getGlobalConfig() != null) {
+ properties.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()));
+ }
+ }
+
+ private void checkConfigFileExists() {
+ if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
+ Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
+ Assert.state(resource.exists(),
+ "Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)");
+ }
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
+
+ SqlSessionFactoryBean factory = new FlexSqlSessionFactoryBean();
+ factory.setDataSource(dataSource);
+ if (properties.getConfiguration() == null || properties.getConfiguration().getVfsImpl() == null) {
+ factory.setVfs(SpringBootVFS.class);
+ }
+ if (StringUtils.hasText(this.properties.getConfigLocation())) {
+ factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
+ }
+ applyConfiguration(factory);
+ if (this.properties.getConfigurationProperties() != null) {
+ factory.setConfigurationProperties(this.properties.getConfigurationProperties());
+ }
+ if (!ObjectUtils.isEmpty(this.interceptors)) {
+ factory.setPlugins(this.interceptors);
+ }
+ if (this.databaseIdProvider != null) {
+ factory.setDatabaseIdProvider(this.databaseIdProvider);
+ }
+ if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
+ factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
+ }
+ if (this.properties.getTypeAliasesSuperType() != null) {
+ factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
+ }
+ if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
+ factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
+ }
+ if (!ObjectUtils.isEmpty(this.typeHandlers)) {
+ factory.setTypeHandlers(this.typeHandlers);
+ }
+ Resource[] mapperLocations = this.properties.resolveMapperLocations();
+ if (!ObjectUtils.isEmpty(mapperLocations)) {
+ factory.setMapperLocations(mapperLocations);
+ }
+ Set factoryPropertyNames = Stream
+ .of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName)
+ .collect(Collectors.toSet());
+ Class extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
+ if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
+ // Need to mybatis-spring 2.0.2+
+ factory.setScriptingLanguageDrivers(this.languageDrivers);
+ if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
+ defaultLanguageDriver = this.languageDrivers[0].getClass();
+ }
+ }
+ if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
+ // Need to mybatis-spring 2.0.2+
+ factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
+ }
+ applySqlSessionFactoryBeanCustomizers(factory);
+ return factory.getObject();
+ }
+
+ protected void applyConfiguration(SqlSessionFactoryBean factory) {
+ MybatisFlexProperties.CoreConfiguration coreConfiguration = this.properties.getConfiguration();
+ FlexConfiguration configuration = null;
+ if (coreConfiguration != null || !StringUtils.hasText(this.properties.getConfigLocation())) {
+ configuration = new FlexConfiguration();
+ }
+ if (configuration != null && coreConfiguration != null) {
+ coreConfiguration.applyTo(configuration);
+ }
+ if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
+ for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
+ customizer.customize(configuration);
+ }
+ }
+ factory.setConfiguration(configuration);
+ }
+
+ protected void applySqlSessionFactoryBeanCustomizers(SqlSessionFactoryBean factory) {
+ if (!CollectionUtils.isEmpty(this.sqlSessionFactoryBeanCustomizers)) {
+ for (SqlSessionFactoryBeanCustomizer customizer : this.sqlSessionFactoryBeanCustomizers) {
+ customizer.customize(factory);
+ }
+ }
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
+ ExecutorType executorType = this.properties.getExecutorType();
+ if (executorType != null) {
+ return new SqlSessionTemplate(sqlSessionFactory, executorType);
+ } else {
+ return new SqlSessionTemplate(sqlSessionFactory);
+ }
+ }
+
+ /**
+ * This will just scan the same base package as Spring Boot does. If you want more power, you can explicitly use
+ * {@link org.mybatis.spring.annotation.MapperScan} but this will get typed mappers working correctly, out-of-the-box,
+ * similar to using Spring Data JPA repositories.
+ */
+ public static class AutoConfiguredMapperScannerRegistrar
+ implements BeanFactoryAware, EnvironmentAware, ImportBeanDefinitionRegistrar {
+
+ private BeanFactory beanFactory;
+ private Environment environment;
+
+ @Override
+ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
+
+ if (!AutoConfigurationPackages.has(this.beanFactory)) {
+ logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
+ return;
+ }
+
+ logger.debug("Searching for mappers annotated with @Mapper");
+
+ List packages = AutoConfigurationPackages.get(this.beanFactory);
+ if (logger.isDebugEnabled()) {
+ packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
+ }
+
+ BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
+ builder.addPropertyValue("processPropertyPlaceHolders", true);
+ builder.addPropertyValue("annotationClass", Mapper.class);
+ builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
+ BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
+ Set propertyNames = Stream.of(beanWrapper.getPropertyDescriptors()).map(PropertyDescriptor::getName)
+ .collect(Collectors.toSet());
+ if (propertyNames.contains("lazyInitialization")) {
+ // Need to mybatis-spring 2.0.2+
+ builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}");
+ }
+ if (propertyNames.contains("defaultScope")) {
+ // Need to mybatis-spring 2.0.6+
+ builder.addPropertyValue("defaultScope", "${mybatis.mapper-default-scope:}");
+ }
+
+ // for spring-native
+ boolean injectSqlSession = environment.getProperty("mybatis.inject-sql-session-on-mapper-scan", Boolean.class,
+ Boolean.TRUE);
+ if (injectSqlSession && this.beanFactory instanceof ListableBeanFactory) {
+ ListableBeanFactory listableBeanFactory = (ListableBeanFactory) this.beanFactory;
+ Optional sqlSessionTemplateBeanName = Optional
+ .ofNullable(getBeanNameForType(SqlSessionTemplate.class, listableBeanFactory));
+ Optional sqlSessionFactoryBeanName = Optional
+ .ofNullable(getBeanNameForType(SqlSessionFactory.class, listableBeanFactory));
+ if (sqlSessionTemplateBeanName.isPresent() || !sqlSessionFactoryBeanName.isPresent()) {
+ builder.addPropertyValue("sqlSessionTemplateBeanName",
+ sqlSessionTemplateBeanName.orElse("sqlSessionTemplate"));
+ } else {
+ builder.addPropertyValue("sqlSessionFactoryBeanName", sqlSessionFactoryBeanName.get());
+ }
+ }
+ builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+
+ registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
+ }
+
+ @Override
+ public void setBeanFactory(BeanFactory beanFactory) {
+ this.beanFactory = beanFactory;
+ }
+
+ @Override
+ public void setEnvironment(Environment environment) {
+ this.environment = environment;
+ }
+
+ private String getBeanNameForType(Class> type, ListableBeanFactory factory) {
+ String[] beanNames = factory.getBeanNamesForType(type);
+ return beanNames.length > 0 ? beanNames[0] : null;
+ }
+
+ }
+
+ /**
+ * If mapper registering configuration or mapper scanning configuration not present, this configuration allow to scan
+ * mappers based on the same component-scanning path as Spring Boot itself.
+ */
+ @org.springframework.context.annotation.Configuration(proxyBeanMethods = false)
+ @Import(AutoConfiguredMapperScannerRegistrar.class)
+ @ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
+ public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
+
+ @Override
+ public void afterPropertiesSet() {
+ logger.debug(
+ "Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
+ }
+
+ }
+
+
+}
diff --git a/mybatis-flex-spring-boot4-starter/src/main/java/com/mybatisflex/spring/boot/v4/MybatisFlexProperties.java b/mybatis-flex-spring-boot4-starter/src/main/java/com/mybatisflex/spring/boot/v4/MybatisFlexProperties.java
new file mode 100644
index 00000000..61b2a697
--- /dev/null
+++ b/mybatis-flex-spring-boot4-starter/src/main/java/com/mybatisflex/spring/boot/v4/MybatisFlexProperties.java
@@ -0,0 +1,1022 @@
+/*
+ * 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.spring.boot.v4;
+
+import com.mybatisflex.core.FlexConsts;
+import com.mybatisflex.core.FlexGlobalConfig;
+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 org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.NestedConfigurationProperty;
+import org.springframework.boot.context.properties.PropertyMapper;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Properties;
+import java.util.Set;
+import java.util.stream.Stream;
+
+/**
+ * Mybatis-Flex 的配置属性。
+ * 参考:https://github.com/mybatis/spring-boot-starter/blob/master/mybatis-spring-boot-autoconfigure/src/main/java/org/mybatis/spring/boot/autoconfigure/MybatisProperties.java
+ *
+ * @author Eddú Meléndez
+ * @author Kazuki Shimizu
+ * @author micahel
+ * @author 王帅
+ */
+@ConfigurationProperties(prefix = "mybatis-flex")
+public class MybatisFlexProperties {
+
+ private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
+
+ 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[]{"classpath*:/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 org.mybatis.spring.SqlSessionTemplate}.
+ */
+ 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;
+
+ /**
+ * A Configuration object for seata
+ */
+ private SeataConfig seataConfig;
+
+ public SeataConfig getSeataConfig() {
+ return seataConfig;
+ }
+
+ public void setSeataConfig(SeataConfig seataConfig) {
+ this.seataConfig = seataConfig;
+ }
+
+ public Map> getDatasource() {
+ return datasource;
+ }
+
+ public void setDatasource(Map> datasource) {
+ this.datasource = datasource;
+ }
+
+ public GlobalConfig getGlobalConfig() {
+ return globalConfig;
+ }
+
+ public void setGlobalConfig(GlobalConfig globalConfig) {
+ this.globalConfig = globalConfig;
+ }
+
+ public AdminConfig getAdminConfig() {
+ return adminConfig;
+ }
+
+ public void setAdminConfig(AdminConfig adminConfig) {
+ this.adminConfig = adminConfig;
+ }
+
+ public String getDefaultDatasourceKey() {
+ return defaultDatasourceKey;
+ }
+
+ public void setDefaultDatasourceKey(String defaultDatasourceKey) {
+ this.defaultDatasourceKey = defaultDatasourceKey;
+ }
+
+ /**
+ * @since 1.1.0
+ */
+ public String getConfigLocation() {
+ return this.configLocation;
+ }
+
+ /**
+ * @since 1.1.0
+ */
+ public void setConfigLocation(String configLocation) {
+ this.configLocation = configLocation;
+ }
+
+ public String[] getMapperLocations() {
+ return this.mapperLocations;
+ }
+
+ public void setMapperLocations(String[] mapperLocations) {
+ this.mapperLocations = mapperLocations;
+ }
+
+ public String getTypeHandlersPackage() {
+ return this.typeHandlersPackage;
+ }
+
+ public void setTypeHandlersPackage(String typeHandlersPackage) {
+ this.typeHandlersPackage = typeHandlersPackage;
+ }
+
+ public String getTypeAliasesPackage() {
+ return this.typeAliasesPackage;
+ }
+
+ public void setTypeAliasesPackage(String typeAliasesPackage) {
+ this.typeAliasesPackage = typeAliasesPackage;
+ }
+
+ /**
+ * @since 1.3.3
+ */
+ public Class> getTypeAliasesSuperType() {
+ return typeAliasesSuperType;
+ }
+
+ /**
+ * @since 1.3.3
+ */
+ public void setTypeAliasesSuperType(Class> typeAliasesSuperType) {
+ this.typeAliasesSuperType = typeAliasesSuperType;
+ }
+
+ public boolean isCheckConfigLocation() {
+ return this.checkConfigLocation;
+ }
+
+ public void setCheckConfigLocation(boolean checkConfigLocation) {
+ this.checkConfigLocation = checkConfigLocation;
+ }
+
+ public ExecutorType getExecutorType() {
+ return this.executorType;
+ }
+
+ public void setExecutorType(ExecutorType executorType) {
+ this.executorType = executorType;
+ }
+
+ /**
+ * @since 2.1.0
+ */
+ public Class extends LanguageDriver> getDefaultScriptingLanguageDriver() {
+ return defaultScriptingLanguageDriver;
+ }
+
+ /**
+ * @since 2.1.0
+ */
+ public void setDefaultScriptingLanguageDriver(Class extends LanguageDriver> defaultScriptingLanguageDriver) {
+ this.defaultScriptingLanguageDriver = defaultScriptingLanguageDriver;
+ }
+
+ /**
+ * @since 1.2.0
+ */
+ public Properties getConfigurationProperties() {
+ return configurationProperties;
+ }
+
+ /**
+ * @since 1.2.0
+ */
+ public void setConfigurationProperties(Properties configurationProperties) {
+ this.configurationProperties = configurationProperties;
+ }
+
+ public CoreConfiguration getConfiguration() {
+ return configuration;
+ }
+
+ public void setConfiguration(CoreConfiguration configuration) {
+ this.configuration = configuration;
+ }
+
+ public Resource[] resolveMapperLocations() {
+ return Stream.of(Optional.ofNullable(this.mapperLocations).orElse(new String[0]))
+ .flatMap(location -> Stream.of(getResources(location))).toArray(Resource[]::new);
+ }
+
+ private Resource[] getResources(String location) {
+ try {
+ return resourceResolver.getResources(location);
+ } catch (IOException e) {
+ return new Resource[0];
+ }
+ }
+
+ /**
+ * The configuration properties for mybatis core module.
+ *
+ * @since 3.0.0
+ */
+ 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;
+
+ public Boolean getSafeRowBoundsEnabled() {
+ return safeRowBoundsEnabled;
+ }
+
+ public void setSafeRowBoundsEnabled(Boolean safeRowBoundsEnabled) {
+ this.safeRowBoundsEnabled = safeRowBoundsEnabled;
+ }
+
+ public Boolean getSafeResultHandlerEnabled() {
+ return safeResultHandlerEnabled;
+ }
+
+ public void setSafeResultHandlerEnabled(Boolean safeResultHandlerEnabled) {
+ this.safeResultHandlerEnabled = safeResultHandlerEnabled;
+ }
+
+ public Boolean getMapUnderscoreToCamelCase() {
+ return mapUnderscoreToCamelCase;
+ }
+
+ public void setMapUnderscoreToCamelCase(Boolean mapUnderscoreToCamelCase) {
+ this.mapUnderscoreToCamelCase = mapUnderscoreToCamelCase;
+ }
+
+ public Boolean getAggressiveLazyLoading() {
+ return aggressiveLazyLoading;
+ }
+
+ public void setAggressiveLazyLoading(Boolean aggressiveLazyLoading) {
+ this.aggressiveLazyLoading = aggressiveLazyLoading;
+ }
+
+ public Boolean getMultipleResultSetsEnabled() {
+ return multipleResultSetsEnabled;
+ }
+
+ public void setMultipleResultSetsEnabled(Boolean multipleResultSetsEnabled) {
+ this.multipleResultSetsEnabled = multipleResultSetsEnabled;
+ }
+
+ public Boolean getUseGeneratedKeys() {
+ return useGeneratedKeys;
+ }
+
+ public void setUseGeneratedKeys(Boolean useGeneratedKeys) {
+ this.useGeneratedKeys = useGeneratedKeys;
+ }
+
+ public Boolean getUseColumnLabel() {
+ return useColumnLabel;
+ }
+
+ public void setUseColumnLabel(Boolean useColumnLabel) {
+ this.useColumnLabel = useColumnLabel;
+ }
+
+ public Boolean getCacheEnabled() {
+ return cacheEnabled;
+ }
+
+ public void setCacheEnabled(Boolean cacheEnabled) {
+ this.cacheEnabled = cacheEnabled;
+ }
+
+ public Boolean getCallSettersOnNulls() {
+ return callSettersOnNulls;
+ }
+
+ public void setCallSettersOnNulls(Boolean callSettersOnNulls) {
+ this.callSettersOnNulls = callSettersOnNulls;
+ }
+
+ public Boolean getUseActualParamName() {
+ return useActualParamName;
+ }
+
+ public void setUseActualParamName(Boolean useActualParamName) {
+ this.useActualParamName = useActualParamName;
+ }
+
+ public Boolean getReturnInstanceForEmptyRow() {
+ return returnInstanceForEmptyRow;
+ }
+
+ public void setReturnInstanceForEmptyRow(Boolean returnInstanceForEmptyRow) {
+ this.returnInstanceForEmptyRow = returnInstanceForEmptyRow;
+ }
+
+ public Boolean getShrinkWhitespacesInSql() {
+ return shrinkWhitespacesInSql;
+ }
+
+ public void setShrinkWhitespacesInSql(Boolean shrinkWhitespacesInSql) {
+ this.shrinkWhitespacesInSql = shrinkWhitespacesInSql;
+ }
+
+ public Boolean getNullableOnForEach() {
+ return nullableOnForEach;
+ }
+
+ public void setNullableOnForEach(Boolean nullableOnForEach) {
+ this.nullableOnForEach = nullableOnForEach;
+ }
+
+ public Boolean getArgNameBasedConstructorAutoMapping() {
+ return argNameBasedConstructorAutoMapping;
+ }
+
+ public void setArgNameBasedConstructorAutoMapping(Boolean argNameBasedConstructorAutoMapping) {
+ this.argNameBasedConstructorAutoMapping = argNameBasedConstructorAutoMapping;
+ }
+
+ public String getLogPrefix() {
+ return logPrefix;
+ }
+
+ public void setLogPrefix(String logPrefix) {
+ this.logPrefix = logPrefix;
+ }
+
+ public Class extends Log> getLogImpl() {
+ return logImpl;
+ }
+
+ public void setLogImpl(Class extends Log> logImpl) {
+ this.logImpl = logImpl;
+ }
+
+ public Class extends VFS> getVfsImpl() {
+ return vfsImpl;
+ }
+
+ public void setVfsImpl(Class extends VFS> vfsImpl) {
+ this.vfsImpl = vfsImpl;
+ }
+
+ public Class> getDefaultSqlProviderType() {
+ return defaultSqlProviderType;
+ }
+
+ public void setDefaultSqlProviderType(Class> defaultSqlProviderType) {
+ this.defaultSqlProviderType = defaultSqlProviderType;
+ }
+
+ public LocalCacheScope getLocalCacheScope() {
+ return localCacheScope;
+ }
+
+ public void setLocalCacheScope(LocalCacheScope localCacheScope) {
+ this.localCacheScope = localCacheScope;
+ }
+
+ public JdbcType getJdbcTypeForNull() {
+ return jdbcTypeForNull;
+ }
+
+ public void setJdbcTypeForNull(JdbcType jdbcTypeForNull) {
+ this.jdbcTypeForNull = jdbcTypeForNull;
+ }
+
+ public Set getLazyLoadTriggerMethods() {
+ return lazyLoadTriggerMethods;
+ }
+
+ public void setLazyLoadTriggerMethods(Set lazyLoadTriggerMethods) {
+ this.lazyLoadTriggerMethods = lazyLoadTriggerMethods;
+ }
+
+ public Integer getDefaultStatementTimeout() {
+ return defaultStatementTimeout;
+ }
+
+ public void setDefaultStatementTimeout(Integer defaultStatementTimeout) {
+ this.defaultStatementTimeout = defaultStatementTimeout;
+ }
+
+ public Integer getDefaultFetchSize() {
+ return defaultFetchSize;
+ }
+
+ public void setDefaultFetchSize(Integer defaultFetchSize) {
+ this.defaultFetchSize = defaultFetchSize;
+ }
+
+ public ResultSetType getDefaultResultSetType() {
+ return defaultResultSetType;
+ }
+
+ public void setDefaultResultSetType(ResultSetType defaultResultSetType) {
+ this.defaultResultSetType = defaultResultSetType;
+ }
+
+ public ExecutorType getDefaultExecutorType() {
+ return defaultExecutorType;
+ }
+
+ public void setDefaultExecutorType(ExecutorType defaultExecutorType) {
+ this.defaultExecutorType = defaultExecutorType;
+ }
+
+ public AutoMappingBehavior getAutoMappingBehavior() {
+ return autoMappingBehavior;
+ }
+
+ public void setAutoMappingBehavior(AutoMappingBehavior autoMappingBehavior) {
+ this.autoMappingBehavior = autoMappingBehavior;
+ }
+
+ public AutoMappingUnknownColumnBehavior getAutoMappingUnknownColumnBehavior() {
+ return autoMappingUnknownColumnBehavior;
+ }
+
+ public void setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior) {
+ this.autoMappingUnknownColumnBehavior = autoMappingUnknownColumnBehavior;
+ }
+
+ public Properties getVariables() {
+ return variables;
+ }
+
+ public void setVariables(Properties variables) {
+ this.variables = variables;
+ }
+
+ public Boolean getLazyLoadingEnabled() {
+ return lazyLoadingEnabled;
+ }
+
+ public void setLazyLoadingEnabled(Boolean lazyLoadingEnabled) {
+ this.lazyLoadingEnabled = lazyLoadingEnabled;
+ }
+
+ public Class> getConfigurationFactory() {
+ return configurationFactory;
+ }
+
+ public void setConfigurationFactory(Class> configurationFactory) {
+ this.configurationFactory = configurationFactory;
+ }
+
+ public Class extends TypeHandler> getDefaultEnumTypeHandler() {
+ return defaultEnumTypeHandler;
+ }
+
+ public void setDefaultEnumTypeHandler(Class extends TypeHandler> defaultEnumTypeHandler) {
+ this.defaultEnumTypeHandler = defaultEnumTypeHandler;
+ }
+
+ void applyTo(Configuration target) {
+ PropertyMapper mapper = PropertyMapper.get();
+ mapper.from(getSafeRowBoundsEnabled()).to(target::setSafeRowBoundsEnabled);
+ mapper.from(getSafeResultHandlerEnabled()).to(target::setSafeResultHandlerEnabled);
+ mapper.from(getMapUnderscoreToCamelCase()).to(target::setMapUnderscoreToCamelCase);
+ mapper.from(getAggressiveLazyLoading()).to(target::setAggressiveLazyLoading);
+ mapper.from(getMultipleResultSetsEnabled()).to(target::setMultipleResultSetsEnabled);
+ mapper.from(getUseGeneratedKeys()).to(target::setUseGeneratedKeys);
+ mapper.from(getUseColumnLabel()).to(target::setUseColumnLabel);
+ mapper.from(getCacheEnabled()).to(target::setCacheEnabled);
+ mapper.from(getCallSettersOnNulls()).to(target::setCallSettersOnNulls);
+ mapper.from(getUseActualParamName()).to(target::setUseActualParamName);
+ mapper.from(getReturnInstanceForEmptyRow()).to(target::setReturnInstanceForEmptyRow);
+ mapper.from(getShrinkWhitespacesInSql()).to(target::setShrinkWhitespacesInSql);
+ mapper.from(getNullableOnForEach()).to(target::setNullableOnForEach);
+ mapper.from(getArgNameBasedConstructorAutoMapping()).to(target::setArgNameBasedConstructorAutoMapping);
+ mapper.from(getLazyLoadingEnabled()).to(target::setLazyLoadingEnabled);
+ mapper.from(getLogPrefix()).to(target::setLogPrefix);
+ mapper.from(getLazyLoadTriggerMethods()).to(target::setLazyLoadTriggerMethods);
+ mapper.from(getDefaultStatementTimeout()).to(target::setDefaultStatementTimeout);
+ mapper.from(getDefaultFetchSize()).to(target::setDefaultFetchSize);
+ mapper.from(getLocalCacheScope()).to(target::setLocalCacheScope);
+ mapper.from(getJdbcTypeForNull()).to(target::setJdbcTypeForNull);
+ mapper.from(getDefaultResultSetType()).to(target::setDefaultResultSetType);
+ mapper.from(getDefaultExecutorType()).to(target::setDefaultExecutorType);
+ mapper.from(getAutoMappingBehavior()).to(target::setAutoMappingBehavior);
+ mapper.from(getAutoMappingUnknownColumnBehavior()).to(target::setAutoMappingUnknownColumnBehavior);
+ mapper.from(getVariables()).to(target::setVariables);
+ mapper.from(getLogImpl()).to(target::setLogImpl);
+ mapper.from(getVfsImpl()).to(target::setVfsImpl);
+ mapper.from(getDefaultSqlProviderType()).to(target::setDefaultSqlProviderType);
+ mapper.from(getConfigurationFactory()).to(target::setConfigurationFactory);
+ mapper.from(getDefaultEnumTypeHandler()).to(target::setDefaultEnumTypeHandler);
+ }
+
+ }
+
+ /**
+ * {@link com.mybatisflex.core.FlexGlobalConfig} 配置。
+ *
+ * @author 王帅
+ * @since 2023-06-21
+ */
+ 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;
+
+ public boolean isPrintBanner() {
+ return printBanner;
+ }
+
+ public void setPrintBanner(boolean printBanner) {
+ this.printBanner = printBanner;
+ }
+
+ public FlexGlobalConfig.KeyConfig getKeyConfig() {
+ return keyConfig;
+ }
+
+ public void setKeyConfig(FlexGlobalConfig.KeyConfig keyConfig) {
+ this.keyConfig = keyConfig;
+ }
+
+ public Object getNormalValueOfLogicDelete() {
+ return normalValueOfLogicDelete;
+ }
+
+ public void setNormalValueOfLogicDelete(Object normalValueOfLogicDelete) {
+ this.normalValueOfLogicDelete = normalValueOfLogicDelete;
+ }
+
+ public Object getDeletedValueOfLogicDelete() {
+ return deletedValueOfLogicDelete;
+ }
+
+ public void setDeletedValueOfLogicDelete(Object deletedValueOfLogicDelete) {
+ this.deletedValueOfLogicDelete = deletedValueOfLogicDelete;
+ }
+
+ public int getDefaultPageSize() {
+ return defaultPageSize;
+ }
+
+ public void setDefaultPageSize(int defaultPageSize) {
+ this.defaultPageSize = defaultPageSize;
+ }
+
+ public int getDefaultRelationQueryDepth() {
+ return defaultRelationQueryDepth;
+ }
+
+ public void setDefaultRelationQueryDepth(int defaultRelationQueryDepth) {
+ this.defaultRelationQueryDepth = defaultRelationQueryDepth;
+ }
+
+ public String getLogicDeleteColumn() {
+ return logicDeleteColumn;
+ }
+
+ public void setLogicDeleteColumn(String logicDeleteColumn) {
+ this.logicDeleteColumn = logicDeleteColumn;
+ }
+
+ public String getTenantColumn() {
+ return tenantColumn;
+ }
+
+ public void setTenantColumn(String tenantColumn) {
+ this.tenantColumn = tenantColumn;
+ }
+
+ public String getVersionColumn() {
+ return versionColumn;
+ }
+
+ public void setVersionColumn(String versionColumn) {
+ this.versionColumn = versionColumn;
+ }
+
+ public boolean isIgnoreSchema() {
+ return ignoreSchema;
+ }
+
+ public void setIgnoreSchema(boolean ignoreSchema) {
+ this.ignoreSchema = ignoreSchema;
+ }
+
+ void applyTo(FlexGlobalConfig target) {
+ PropertyMapper mapper = PropertyMapper.get();
+ mapper.from(isPrintBanner()).to(target::setPrintBanner);
+ mapper.from(getKeyConfig()).to(target::setKeyConfig);
+ mapper.from(getNormalValueOfLogicDelete()).to(target::setNormalValueOfLogicDelete);
+ mapper.from(getDeletedValueOfLogicDelete()).to(target::setDeletedValueOfLogicDelete);
+ mapper.from(getDefaultPageSize()).to(target::setDefaultPageSize);
+ mapper.from(getDefaultRelationQueryDepth()).to(target::setDefaultRelationQueryDepth);
+ mapper.from(getLogicDeleteColumn()).to(target::setLogicDeleteColumn);
+ mapper.from(getVersionColumn()).to(target::setVersionColumn);
+ mapper.from(getTenantColumn()).to(target::setTenantColumn);
+ mapper.from(isIgnoreSchema()).to(target::setIgnoreSchema);
+ }
+
+ }
+
+ /**
+ * MyBatis Flex Admin 配置。
+ *
+ * @author 王帅
+ * @since 2023-07-02
+ */
+ public static class AdminConfig {
+
+ /**
+ * 启用服务。
+ */
+ private boolean enable;
+
+ /**
+ * 连接端点。
+ */
+ private String endpoint;
+
+ /**
+ * 秘密密钥。
+ */
+ private String secretKey;
+
+ public boolean isEnable() {
+ return enable;
+ }
+
+ public void setEnable(boolean enable) {
+ this.enable = enable;
+ }
+
+ public String getEndpoint() {
+ return endpoint;
+ }
+
+ public void setEndpoint(String endpoint) {
+ this.endpoint = endpoint;
+ }
+
+ public String getSecretKey() {
+ return secretKey;
+ }
+
+ public void setSecretKey(String secretKey) {
+ this.secretKey = secretKey;
+ }
+
+ }
+
+ /**
+ * Seata 配置
+ *
+ * @author life
+ */
+ public static class SeataConfig {
+
+ /**
+ * 是否开启
+ */
+ private boolean enable = false;
+
+ /**
+ * 事务模式支持,只支持XA或者AT
+ */
+ private SeataMode seataMode = SeataMode.AT;
+
+ public boolean isEnable() {
+ return enable;
+ }
+
+ public void setEnable(boolean enable) {
+ this.enable = enable;
+ }
+
+ public SeataMode getSeataMode() {
+ return seataMode;
+ }
+
+ public void setSeataMode(SeataMode seataMode) {
+ this.seataMode = seataMode;
+ }
+
+ }
+
+ /**
+ * @author life
+ */
+ public enum SeataMode {
+
+ XA,
+
+ AT
+
+ }
+
+}
diff --git a/mybatis-flex-spring-boot4-starter/src/main/java/com/mybatisflex/spring/boot/v4/package-info.java b/mybatis-flex-spring-boot4-starter/src/main/java/com/mybatisflex/spring/boot/v4/package-info.java
new file mode 100644
index 00000000..6afc7379
--- /dev/null
+++ b/mybatis-flex-spring-boot4-starter/src/main/java/com/mybatisflex/spring/boot/v4/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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 Spring Boot 支持。
+ */
+package com.mybatisflex.spring.boot.v4;
diff --git a/mybatis-flex-spring-boot4-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/mybatis-flex-spring-boot4-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json
new file mode 100644
index 00000000..cbb5a749
--- /dev/null
+++ b/mybatis-flex-spring-boot4-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json
@@ -0,0 +1,91 @@
+{
+ "properties": [
+ {
+ "defaultValue": false,
+ "name": "mybatis-flex.lazy-initialization",
+ "description": "Set whether enable lazy initialization for mapper bean.",
+ "type": "java.lang.Boolean"
+ },
+ {
+ "defaultValue": "",
+ "name": "mybatis-flex.mapper-default-scope",
+ "description": "A default scope for mapper bean that scanned by auto-configure.",
+ "type": "java.lang.String"
+ },
+ {
+ "defaultValue": true,
+ "name": "mybatis-flex.inject-sql-session-on-mapper-scan",
+ "description": "Set whether inject a SqlSessionTemplate or SqlSessionFactory bean (If you want to back to the behavior of 2.2.1 or before, specify false). If you use together with spring-native, should be set true.",
+ "type": "java.lang.Boolean"
+ },
+ {
+ "name": "mybatis-flex.scripting-language-driver.velocity.userdirective",
+ "deprecation": {
+ "level": "error",
+ "reason": "The 'userdirective' is deprecated since Velocity 2.x. This property defined for keeping backward compatibility with older velocity version.",
+ "replacement": "mybatis-flex.scripting-language-driver.velocity.velocity-settings.runtime.custom_directives"
+ }
+ },
+ {
+ "defaultValue": true,
+ "name": "mybatis-flex.datasource",
+ "description": "多数据源配置",
+ "type": "java.util.Map"
+ },
+ {
+ "defaultValue": 0,
+ "name": "mybatis-flex.global-config.normal-value-of-logic-delete",
+ "description": "逻辑删除未删除值标记",
+ "type": "java.lang.Object"
+ },
+ {
+ "defaultValue": 1,
+ "name": "mybatis-flex.global-config.deleted-value-of-logic-delete",
+ "description": "逻辑删除已删除值标记",
+ "type": "java.lang.Object"
+ },
+ {
+ "defaultValue": 10,
+ "name": "mybatis-flex.global-config.default-page-size",
+ "description": "默认的分页查询时的每页数据量",
+ "type": "java.lang.Integer"
+ },
+ {
+ "defaultValue": 2,
+ "name": "mybatis-flex.global-config.default-relation-query-depth",
+ "description": "默认的 Relation 注解查询深度",
+ "type": "java.lang.Integer"
+ },
+ {
+ "name": "mybatis-flex.global-config.key-config.value",
+ "description": "使用的 ID 生成器名称 或者 Sequence 执行的 SQL 内容",
+ "type": "java.lang.String"
+ },
+ {
+ "defaultValue": true,
+ "name": "mybatis-flex.global-config.key-config.before",
+ "description": "是否在数据插入之前执行,只在非自增上配置有效",
+ "type": "java.lang.Boolean"
+ },
+ {
+ "name": "mybatis-flex.global-config.key-config.key-type",
+ "description": "ID 生成策略",
+ "type": "com.mybatisflex.annotation.KeyType"
+ },
+ {
+ "name": "mybatis-flex.global-config.logic-delete-column",
+ "description": "全局默认逻辑删除字段",
+ "type": "java.lang.String"
+ },
+ {
+ "name": "mybatis-flex.global-config.tenant-column",
+ "description": "全局默认多租户字段",
+ "type": "java.lang.String"
+ },
+ {
+ "name": "mybatis-flex.global-config.version-column",
+ "description": "全局默认逻辑乐观锁字段",
+ "type": "java.lang.String"
+ }
+ ]
+}
diff --git a/mybatis-flex-spring-boot4-starter/src/main/resources/META-INF/spring.factories b/mybatis-flex-spring-boot4-starter/src/main/resources/META-INF/spring.factories
new file mode 100644
index 00000000..204184d0
--- /dev/null
+++ b/mybatis-flex-spring-boot4-starter/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,9 @@
+# Depends On Database Initialization Detectors
+org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector=\
+com.mybatisflex.spring.boot.MybatisFlexDependsOnDatabaseInitializationDetector
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+ com.mybatisflex.spring.boot.v4.FlexTransactionAutoConfiguration,\
+ com.mybatisflex.spring.boot.v4.MultiDataSourceAutoConfiguration,\
+ com.mybatisflex.spring.boot.v4.MybatisFlexAutoConfiguration,\
+ com.mybatisflex.spring.boot.MybatisFlexAdminAutoConfiguration,\
+ com.mybatisflex.spring.boot.MybatisLanguageDriverAutoConfiguration
diff --git a/mybatis-flex-spring-boot4-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/mybatis-flex-spring-boot4-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 00000000..c739aa40
--- /dev/null
+++ b/mybatis-flex-spring-boot4-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,5 @@
+com.mybatisflex.spring.boot.v4.FlexTransactionAutoConfiguration
+com.mybatisflex.spring.boot.v4.MultiDataSourceAutoConfiguration
+com.mybatisflex.spring.boot.v4.MybatisFlexAutoConfiguration
+com.mybatisflex.spring.boot.MybatisFlexAdminAutoConfiguration
+com.mybatisflex.spring.boot.MybatisLanguageDriverAutoConfiguration
diff --git a/pom.xml b/pom.xml
index dbcdbf0f..a8bd7936 100644
--- a/pom.xml
+++ b/pom.xml
@@ -55,6 +55,7 @@
mybatis-flex-spring
mybatis-flex-spring-boot-starter
mybatis-flex-spring-boot3-starter
+ mybatis-flex-spring-boot4-starter
mybatis-flex-solon-plugin
mybatis-flex-loveqq-starter
mybatis-flex-test
@@ -86,6 +87,7 @@
5.3.27
4.3.10
2.7.11
+ 4.0.0-RC2
3.0.1
1.1.0-java8