001/* 002 * Copyright 2015-2022 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package com.mybatisflex.spring.boot; 017 018import com.mybatisflex.core.FlexGlobalConfig; 019import com.mybatisflex.core.datasource.DataSourceDecipher; 020import com.mybatisflex.core.datasource.DataSourceManager; 021import com.mybatisflex.core.logicdelete.LogicDeleteManager; 022import com.mybatisflex.core.logicdelete.LogicDeleteProcessor; 023import com.mybatisflex.core.mybatis.FlexConfiguration; 024import com.mybatisflex.core.table.DynamicSchemaProcessor; 025import com.mybatisflex.core.table.DynamicTableProcessor; 026import com.mybatisflex.core.table.TableManager; 027import com.mybatisflex.core.tenant.TenantFactory; 028import com.mybatisflex.core.tenant.TenantManager; 029import com.mybatisflex.spring.FlexSqlSessionFactoryBean; 030import org.apache.ibatis.annotations.Mapper; 031import org.apache.ibatis.mapping.DatabaseIdProvider; 032import org.apache.ibatis.plugin.Interceptor; 033import org.apache.ibatis.scripting.LanguageDriver; 034import org.apache.ibatis.session.ExecutorType; 035import org.apache.ibatis.session.SqlSessionFactory; 036import org.apache.ibatis.type.TypeHandler; 037import org.mybatis.spring.SqlSessionFactoryBean; 038import org.mybatis.spring.SqlSessionTemplate; 039import org.mybatis.spring.mapper.MapperFactoryBean; 040import org.mybatis.spring.mapper.MapperScannerConfigurer; 041import org.slf4j.Logger; 042import org.slf4j.LoggerFactory; 043import org.springframework.beans.BeanWrapper; 044import org.springframework.beans.BeanWrapperImpl; 045import org.springframework.beans.factory.*; 046import org.springframework.beans.factory.config.BeanDefinition; 047import org.springframework.beans.factory.support.BeanDefinitionBuilder; 048import org.springframework.beans.factory.support.BeanDefinitionRegistry; 049import org.springframework.boot.autoconfigure.AutoConfigurationPackages; 050import org.springframework.boot.autoconfigure.AutoConfigureAfter; 051import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 052import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 053import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; 054import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; 055import org.springframework.boot.context.properties.EnableConfigurationProperties; 056import org.springframework.context.EnvironmentAware; 057import org.springframework.context.annotation.Bean; 058import org.springframework.context.annotation.Configuration; 059import org.springframework.context.annotation.Import; 060import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; 061import org.springframework.core.env.Environment; 062import org.springframework.core.io.Resource; 063import org.springframework.core.io.ResourceLoader; 064import org.springframework.core.type.AnnotationMetadata; 065import org.springframework.util.Assert; 066import org.springframework.util.CollectionUtils; 067import org.springframework.util.ObjectUtils; 068import org.springframework.util.StringUtils; 069 070import javax.sql.DataSource; 071import java.beans.PropertyDescriptor; 072import java.util.List; 073import java.util.Optional; 074import java.util.Set; 075import java.util.stream.Collectors; 076import java.util.stream.Stream; 077 078 079/** 080 * Mybatis-Flex 的核心配置。 081 * <p> 082 * 参考 <a href="https://github.com/mybatis/spring-boot-starter/blob/master/mybatis-spring-boot-autoconfigure/src/main/java/org/mybatis/spring/boot/autoconfigure/MybatisAutoConfiguration.java"> 083 * MybatisAutoConfiguration.java</a> 084 * <p> 085 * 为 Mybatis-Flex 开启自动配置功能,主要修改以下几个方面: 086 * <p> 087 * 1、替换配置为 mybatis-flex 的配置前缀<br> 088 * 2、修改 SqlSessionFactory 为 FlexSqlSessionFactoryBean<br> 089 * 3、修改 Configuration 为 FlexConfiguration<br> 090 * 091 * @author Eddú Meléndez 092 * @author Josh Long 093 * @author Kazuki Shimizu 094 * @author Eduardo Macarrón 095 * @author michael 096 * @author 王帅 097 */ 098@Configuration(proxyBeanMethods = false) 099@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class}) 100@ConditionalOnSingleCandidate(DataSource.class) 101@EnableConfigurationProperties(MybatisFlexProperties.class) 102@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class}) 103public class MybatisFlexAutoConfiguration implements InitializingBean { 104 105 protected static final Logger logger = LoggerFactory.getLogger(MybatisFlexAutoConfiguration.class); 106 107 protected final MybatisFlexProperties properties; 108 109 protected final Interceptor[] interceptors; 110 111 protected final TypeHandler[] typeHandlers; 112 113 protected final LanguageDriver[] languageDrivers; 114 115 protected final ResourceLoader resourceLoader; 116 117 protected final DatabaseIdProvider databaseIdProvider; 118 119 protected final List<ConfigurationCustomizer> configurationCustomizers; 120 121 protected final List<SqlSessionFactoryBeanCustomizer> sqlSessionFactoryBeanCustomizers; 122 123 //数据源解密器 124 protected final DataSourceDecipher dataSourceDecipher; 125 126 //动态表名 127 protected final DynamicTableProcessor dynamicTableProcessor; 128 129 //动态 schema 处理器 130 protected final DynamicSchemaProcessor dynamicSchemaProcessor; 131 132 //多租户 133 protected final TenantFactory tenantFactory; 134 135 //自定义逻辑删除处理器 136 protected final LogicDeleteProcessor logicDeleteProcessor; 137 138 //初始化监听 139 protected final List<MyBatisFlexCustomizer> mybatisFlexCustomizers; 140 141 142 public MybatisFlexAutoConfiguration(MybatisFlexProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider, 143 ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider, 144 ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider, 145 ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider, 146 ObjectProvider<List<SqlSessionFactoryBeanCustomizer>> sqlSessionFactoryBeanCustomizers, 147 ObjectProvider<DataSourceDecipher> dataSourceDecipherProvider, 148 ObjectProvider<DynamicTableProcessor> dynamicTableProcessorProvider, 149 ObjectProvider<DynamicSchemaProcessor> dynamicSchemaProcessorProvider, 150 ObjectProvider<TenantFactory> tenantFactoryProvider, 151 ObjectProvider<LogicDeleteProcessor> logicDeleteProcessorProvider, 152 ObjectProvider<MyBatisFlexCustomizer> mybatisFlexCustomizerProviders 153 ) { 154 this.properties = properties; 155 this.interceptors = interceptorsProvider.getIfAvailable(); 156 this.typeHandlers = typeHandlersProvider.getIfAvailable(); 157 this.languageDrivers = languageDriversProvider.getIfAvailable(); 158 this.resourceLoader = resourceLoader; 159 this.databaseIdProvider = databaseIdProvider.getIfAvailable(); 160 this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable(); 161 this.sqlSessionFactoryBeanCustomizers = sqlSessionFactoryBeanCustomizers.getIfAvailable(); 162 163 //数据源解密器 164 this.dataSourceDecipher = dataSourceDecipherProvider.getIfAvailable(); 165 166 //动态表名 167 this.dynamicTableProcessor = dynamicTableProcessorProvider.getIfAvailable(); 168 169 //动态 schema 处理器 170 this.dynamicSchemaProcessor = dynamicSchemaProcessorProvider.getIfAvailable(); 171 172 //多租户 173 this.tenantFactory = tenantFactoryProvider.getIfAvailable(); 174 175 //逻辑删除处理器 176 this.logicDeleteProcessor = logicDeleteProcessorProvider.getIfAvailable(); 177 178 //初始化监听器 179 this.mybatisFlexCustomizers = mybatisFlexCustomizerProviders.orderedStream().collect(Collectors.toList()); 180 } 181 182 @Override 183 public void afterPropertiesSet() { 184 // 检测 MyBatis 原生配置文件是否存在 185 checkConfigFileExists(); 186 187 // 添加 MyBatis-Flex 全局配置 188 if (properties.getGlobalConfig() != null) { 189 properties.getGlobalConfig().applyTo(FlexGlobalConfig.getDefaultConfig()); 190 } 191 192 //数据源解密器 193 if (dataSourceDecipher != null) { 194 DataSourceManager.setDecipher(dataSourceDecipher); 195 } 196 197 // 动态表名配置 198 if (dynamicTableProcessor != null) { 199 TableManager.setDynamicTableProcessor(dynamicTableProcessor); 200 } 201 202 // 动态 schema 处理器配置 203 if (dynamicSchemaProcessor != null) { 204 TableManager.setDynamicSchemaProcessor(dynamicSchemaProcessor); 205 } 206 207 //多租户 208 if (tenantFactory != null) { 209 TenantManager.setTenantFactory(tenantFactory); 210 } 211 212 //逻辑删除处理器 213 if (logicDeleteProcessor != null) { 214 LogicDeleteManager.setProcessor(logicDeleteProcessor); 215 } 216 217 //初始化监听器 218 if (mybatisFlexCustomizers != null) { 219 mybatisFlexCustomizers.forEach(myBatisFlexCustomizer -> myBatisFlexCustomizer.customize(FlexGlobalConfig.getDefaultConfig())); 220 } 221 } 222 223 private void checkConfigFileExists() { 224 if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) { 225 Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation()); 226 Assert.state(resource.exists(), 227 "Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)"); 228 } 229 } 230 231 @Bean 232 @ConditionalOnMissingBean 233 public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { 234 235 SqlSessionFactoryBean factory = new FlexSqlSessionFactoryBean(); 236 factory.setDataSource(dataSource); 237 if (properties.getConfiguration() == null || properties.getConfiguration().getVfsImpl() == null) { 238 factory.setVfs(SpringBootVFS.class); 239 } 240 if (StringUtils.hasText(this.properties.getConfigLocation())) { 241 factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation())); 242 } 243 applyConfiguration(factory); 244 if (this.properties.getConfigurationProperties() != null) { 245 factory.setConfigurationProperties(this.properties.getConfigurationProperties()); 246 } 247 if (!ObjectUtils.isEmpty(this.interceptors)) { 248 factory.setPlugins(this.interceptors); 249 } 250 if (this.databaseIdProvider != null) { 251 factory.setDatabaseIdProvider(this.databaseIdProvider); 252 } 253 if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) { 254 factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage()); 255 } 256 if (this.properties.getTypeAliasesSuperType() != null) { 257 factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType()); 258 } 259 if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) { 260 factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage()); 261 } 262 if (!ObjectUtils.isEmpty(this.typeHandlers)) { 263 factory.setTypeHandlers(this.typeHandlers); 264 } 265 Resource[] mapperLocations = this.properties.resolveMapperLocations(); 266 if (!ObjectUtils.isEmpty(mapperLocations)) { 267 factory.setMapperLocations(mapperLocations); 268 } 269 Set<String> factoryPropertyNames = Stream 270 .of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName) 271 .collect(Collectors.toSet()); 272 Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver(); 273 if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) { 274 // Need to mybatis-spring 2.0.2+ 275 factory.setScriptingLanguageDrivers(this.languageDrivers); 276 if (defaultLanguageDriver == null && this.languageDrivers.length == 1) { 277 defaultLanguageDriver = this.languageDrivers[0].getClass(); 278 } 279 } 280 if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) { 281 // Need to mybatis-spring 2.0.2+ 282 factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver); 283 } 284 applySqlSessionFactoryBeanCustomizers(factory); 285 return factory.getObject(); 286 } 287 288 protected void applyConfiguration(SqlSessionFactoryBean factory) { 289 MybatisFlexProperties.CoreConfiguration coreConfiguration = this.properties.getConfiguration(); 290 FlexConfiguration configuration = null; 291 if (coreConfiguration != null || !StringUtils.hasText(this.properties.getConfigLocation())) { 292 configuration = new FlexConfiguration(); 293 } 294 if (configuration != null && coreConfiguration != null) { 295 coreConfiguration.applyTo(configuration); 296 } 297 if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) { 298 for (ConfigurationCustomizer customizer : this.configurationCustomizers) { 299 customizer.customize(configuration); 300 } 301 } 302 factory.setConfiguration(configuration); 303 } 304 305 protected void applySqlSessionFactoryBeanCustomizers(SqlSessionFactoryBean factory) { 306 if (!CollectionUtils.isEmpty(this.sqlSessionFactoryBeanCustomizers)) { 307 for (SqlSessionFactoryBeanCustomizer customizer : this.sqlSessionFactoryBeanCustomizers) { 308 customizer.customize(factory); 309 } 310 } 311 } 312 313 @Bean 314 @ConditionalOnMissingBean 315 public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { 316 ExecutorType executorType = this.properties.getExecutorType(); 317 if (executorType != null) { 318 return new SqlSessionTemplate(sqlSessionFactory, executorType); 319 } else { 320 return new SqlSessionTemplate(sqlSessionFactory); 321 } 322 } 323 324 /** 325 * This will just scan the same base package as Spring Boot does. If you want more power, you can explicitly use 326 * {@link org.mybatis.spring.annotation.MapperScan} but this will get typed mappers working correctly, out-of-the-box, 327 * similar to using Spring Data JPA repositories. 328 */ 329 public static class AutoConfiguredMapperScannerRegistrar 330 implements BeanFactoryAware, EnvironmentAware, ImportBeanDefinitionRegistrar { 331 332 private BeanFactory beanFactory; 333 private Environment environment; 334 335 @Override 336 public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { 337 338 if (!AutoConfigurationPackages.has(this.beanFactory)) { 339 logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled."); 340 return; 341 } 342 343 logger.debug("Searching for mappers annotated with @Mapper"); 344 345 List<String> packages = AutoConfigurationPackages.get(this.beanFactory); 346 if (logger.isDebugEnabled()) { 347 packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg)); 348 } 349 350 BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); 351 builder.addPropertyValue("processPropertyPlaceHolders", true); 352 builder.addPropertyValue("annotationClass", Mapper.class); 353 builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages)); 354 BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class); 355 Set<String> propertyNames = Stream.of(beanWrapper.getPropertyDescriptors()).map(PropertyDescriptor::getName) 356 .collect(Collectors.toSet()); 357 if (propertyNames.contains("lazyInitialization")) { 358 // Need to mybatis-spring 2.0.2+ 359 builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}"); 360 } 361 if (propertyNames.contains("defaultScope")) { 362 // Need to mybatis-spring 2.0.6+ 363 builder.addPropertyValue("defaultScope", "${mybatis.mapper-default-scope:}"); 364 } 365 366 // for spring-native 367 boolean injectSqlSession = environment.getProperty("mybatis.inject-sql-session-on-mapper-scan", Boolean.class, 368 Boolean.TRUE); 369 if (injectSqlSession && this.beanFactory instanceof ListableBeanFactory) { 370 ListableBeanFactory listableBeanFactory = (ListableBeanFactory) this.beanFactory; 371 Optional<String> sqlSessionTemplateBeanName = Optional 372 .ofNullable(getBeanNameForType(SqlSessionTemplate.class, listableBeanFactory)); 373 Optional<String> sqlSessionFactoryBeanName = Optional 374 .ofNullable(getBeanNameForType(SqlSessionFactory.class, listableBeanFactory)); 375 if (sqlSessionTemplateBeanName.isPresent() || !sqlSessionFactoryBeanName.isPresent()) { 376 builder.addPropertyValue("sqlSessionTemplateBeanName", 377 sqlSessionTemplateBeanName.orElse("sqlSessionTemplate")); 378 } else { 379 builder.addPropertyValue("sqlSessionFactoryBeanName", sqlSessionFactoryBeanName.get()); 380 } 381 } 382 builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); 383 384 registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition()); 385 } 386 387 @Override 388 public void setBeanFactory(BeanFactory beanFactory) { 389 this.beanFactory = beanFactory; 390 } 391 392 @Override 393 public void setEnvironment(Environment environment) { 394 this.environment = environment; 395 } 396 397 private String getBeanNameForType(Class<?> type, ListableBeanFactory factory) { 398 String[] beanNames = factory.getBeanNamesForType(type); 399 return beanNames.length > 0 ? beanNames[0] : null; 400 } 401 402 } 403 404 /** 405 * If mapper registering configuration or mapper scanning configuration not present, this configuration allow to scan 406 * mappers based on the same component-scanning path as Spring Boot itself. 407 */ 408 @org.springframework.context.annotation.Configuration(proxyBeanMethods = false) 409 @Import(AutoConfiguredMapperScannerRegistrar.class) 410 @ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class}) 411 public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean { 412 413 @Override 414 public void afterPropertiesSet() { 415 logger.debug( 416 "Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer."); 417 } 418 419 } 420 421 422}