From a5f56db1d06e14cf32c9a177a5a45dc4a4c527ea Mon Sep 17 00:00:00 2001 From: Macrow Date: Mon, 17 Nov 2025 16:00:19 +0800 Subject: [PATCH 1/2] feat: add module mybatis-flex-spring-boot4-starter --- .../FlexTransactionAutoConfiguration.java | 8 +- .../MultiDataSourceAutoConfiguration.java | 8 +- .../boot/MybatisFlexAutoConfiguration.java | 7 +- mybatis-flex-spring-boot4-starter/pom.xml | 133 +++ .../v4/FlexTransactionAutoConfiguration.java | 74 ++ .../v4/MultiDataSourceAutoConfiguration.java | 159 +++ .../boot/v4/MybatisFlexAutoConfiguration.java | 428 +++++++ .../spring/boot/v4/MybatisFlexProperties.java | 1022 +++++++++++++++++ .../spring/boot/v4/package-info.java | 20 + ...itional-spring-configuration-metadata.json | 91 ++ .../main/resources/META-INF/spring.factories | 9 + ...ot.autoconfigure.AutoConfiguration.imports | 5 + pom.xml | 4 +- 13 files changed, 1964 insertions(+), 4 deletions(-) create mode 100644 mybatis-flex-spring-boot4-starter/pom.xml create mode 100644 mybatis-flex-spring-boot4-starter/src/main/java/com/mybatisflex/spring/boot/v4/FlexTransactionAutoConfiguration.java create mode 100644 mybatis-flex-spring-boot4-starter/src/main/java/com/mybatisflex/spring/boot/v4/MultiDataSourceAutoConfiguration.java create mode 100644 mybatis-flex-spring-boot4-starter/src/main/java/com/mybatisflex/spring/boot/v4/MybatisFlexAutoConfiguration.java create mode 100644 mybatis-flex-spring-boot4-starter/src/main/java/com/mybatisflex/spring/boot/v4/MybatisFlexProperties.java create mode 100644 mybatis-flex-spring-boot4-starter/src/main/java/com/mybatisflex/spring/boot/v4/package-info.java create mode 100644 mybatis-flex-spring-boot4-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 mybatis-flex-spring-boot4-starter/src/main/resources/META-INF/spring.factories create mode 100644 mybatis-flex-spring-boot4-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 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 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 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 getDefaultScriptingLanguageDriver() { + return defaultScriptingLanguageDriver; + } + + /** + * @since 2.1.0 + */ + public void setDefaultScriptingLanguageDriver(Class 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 logImpl; + + /** + * Specifies VFS implementations. + */ + private Class 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 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 getLogImpl() { + return logImpl; + } + + public void setLogImpl(Class logImpl) { + this.logImpl = logImpl; + } + + public Class getVfsImpl() { + return vfsImpl; + } + + public void setVfsImpl(Class 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 getDefaultEnumTypeHandler() { + return defaultEnumTypeHandler; + } + + public void setDefaultEnumTypeHandler(Class 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..fa97dde4 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 @@ -331,7 +333,7 @@ 1.6 - verify + deploy sign From 40f98c84d0d5ccbd1571756c563d91e32fa26ae6 Mon Sep 17 00:00:00 2001 From: Macrow Date: Thu, 20 Nov 2025 09:45:15 +0800 Subject: [PATCH 2/2] chore: add dependency mybatis-flex-spring-boot4-starter in module mybatis-flex-dependencies --- mybatis-flex-dependencies/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mybatis-flex-dependencies/pom.xml b/mybatis-flex-dependencies/pom.xml index cb069fe5..d69eece4 100644 --- a/mybatis-flex-dependencies/pom.xml +++ b/mybatis-flex-dependencies/pom.xml @@ -97,6 +97,11 @@ mybatis-flex-spring-boot3-starter ${project.version} + + com.mybatis-flex + mybatis-flex-spring-boot4-starter + ${project.version} +