001/** 002 * Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com). 003 * <p> 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 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 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.mybatis.FlexConfiguration; 019import com.mybatisflex.spring.FlexSqlSessionFactoryBean; 020import org.apache.ibatis.annotations.Mapper; 021import org.apache.ibatis.mapping.DatabaseIdProvider; 022import org.apache.ibatis.plugin.Interceptor; 023import org.apache.ibatis.scripting.LanguageDriver; 024import org.apache.ibatis.session.ExecutorType; 025import org.apache.ibatis.session.SqlSessionFactory; 026import org.apache.ibatis.type.TypeHandler; 027import org.mybatis.spring.SqlSessionFactoryBean; 028import org.mybatis.spring.SqlSessionTemplate; 029import org.mybatis.spring.mapper.MapperFactoryBean; 030import org.mybatis.spring.mapper.MapperScannerConfigurer; 031import org.slf4j.Logger; 032import org.slf4j.LoggerFactory; 033import org.springframework.beans.BeanWrapper; 034import org.springframework.beans.BeanWrapperImpl; 035import org.springframework.beans.factory.*; 036import org.springframework.beans.factory.config.BeanDefinition; 037import org.springframework.beans.factory.support.BeanDefinitionBuilder; 038import org.springframework.beans.factory.support.BeanDefinitionRegistry; 039import org.springframework.boot.autoconfigure.AutoConfigurationPackages; 040import org.springframework.boot.autoconfigure.AutoConfigureAfter; 041import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 042import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 043import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; 044import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; 045import org.springframework.boot.context.properties.EnableConfigurationProperties; 046import org.springframework.context.EnvironmentAware; 047import org.springframework.context.annotation.Bean; 048import org.springframework.context.annotation.Configuration; 049import org.springframework.context.annotation.Import; 050import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; 051import org.springframework.core.env.Environment; 052import org.springframework.core.io.Resource; 053import org.springframework.core.io.ResourceLoader; 054import org.springframework.core.type.AnnotationMetadata; 055import org.springframework.util.Assert; 056import org.springframework.util.CollectionUtils; 057import org.springframework.util.ObjectUtils; 058import org.springframework.util.StringUtils; 059 060import javax.sql.DataSource; 061import java.beans.PropertyDescriptor; 062import java.util.List; 063import java.util.Optional; 064import java.util.Set; 065import java.util.stream.Collectors; 066import java.util.stream.Stream; 067 068 069/** 070 * Mybatis-Flex 的核心配置 071 * <p> 072 * <p> 073 * 参考 {@link <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"> 074 * https://github.com/mybatis/spring-boot-starter/blob/master/mybatis-spring-boot-autoconfigure/src/main/java/org/mybatis/spring/boot/autoconfigure/MybatisAutoConfiguration.java</a>} 075 * <p> 076 * 为 Mybatis-Flex 开启自动配置功能,主要修改一下几个方面: 077 * <p> 078 * 1、替换配置为 mybatis-flex 的配置前缀 079 * 2、修改 SqlSessionFactory 为 FlexSqlSessionFactoryBean 080 * 3、修改 Configuration 为 FlexConfiguration 081 */ 082@Configuration(proxyBeanMethods = false) 083@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class}) 084@ConditionalOnSingleCandidate(DataSource.class) 085@EnableConfigurationProperties(MybatisFlexProperties.class) 086@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class}) 087public class MybatisFlexAutoConfiguration implements InitializingBean { 088 089 protected static final Logger logger = LoggerFactory.getLogger(MybatisFlexAutoConfiguration.class); 090 091 protected final MybatisFlexProperties properties; 092 093 protected final Interceptor[] interceptors; 094 095 protected final TypeHandler[] typeHandlers; 096 097 protected final LanguageDriver[] languageDrivers; 098 099 protected final ResourceLoader resourceLoader; 100 101 protected final DatabaseIdProvider databaseIdProvider; 102 103 protected final List<ConfigurationCustomizer> configurationCustomizers; 104 105 protected final List<SqlSessionFactoryBeanCustomizer> sqlSessionFactoryBeanCustomizers; 106 107 public MybatisFlexAutoConfiguration(MybatisFlexProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider, 108 ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider, 109 ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider, 110 ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider, 111 ObjectProvider<List<SqlSessionFactoryBeanCustomizer>> sqlSessionFactoryBeanCustomizers) { 112 this.properties = properties; 113 this.interceptors = interceptorsProvider.getIfAvailable(); 114 this.typeHandlers = typeHandlersProvider.getIfAvailable(); 115 this.languageDrivers = languageDriversProvider.getIfAvailable(); 116 this.resourceLoader = resourceLoader; 117 this.databaseIdProvider = databaseIdProvider.getIfAvailable(); 118 this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable(); 119 this.sqlSessionFactoryBeanCustomizers = sqlSessionFactoryBeanCustomizers.getIfAvailable(); 120 } 121 122 @Override 123 public void afterPropertiesSet() { 124 checkConfigFileExists(); 125 } 126 127 private void checkConfigFileExists() { 128 if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) { 129 Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation()); 130 Assert.state(resource.exists(), 131 "Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)"); 132 } 133 } 134 135 @Bean 136 @ConditionalOnMissingBean 137 public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { 138// SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); 139 SqlSessionFactoryBean factory = new FlexSqlSessionFactoryBean(); 140 factory.setDataSource(dataSource); 141 if (properties.getConfiguration() == null || properties.getConfiguration().getVfsImpl() == null) { 142 factory.setVfs(SpringBootVFS.class); 143 } 144 if (StringUtils.hasText(this.properties.getConfigLocation())) { 145 factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation())); 146 } 147 applyConfiguration(factory); 148 if (this.properties.getConfigurationProperties() != null) { 149 factory.setConfigurationProperties(this.properties.getConfigurationProperties()); 150 } 151 if (!ObjectUtils.isEmpty(this.interceptors)) { 152 factory.setPlugins(this.interceptors); 153 } 154 if (this.databaseIdProvider != null) { 155 factory.setDatabaseIdProvider(this.databaseIdProvider); 156 } 157 if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) { 158 factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage()); 159 } 160 if (this.properties.getTypeAliasesSuperType() != null) { 161 factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType()); 162 } 163 if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) { 164 factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage()); 165 } 166 if (!ObjectUtils.isEmpty(this.typeHandlers)) { 167 factory.setTypeHandlers(this.typeHandlers); 168 } 169 Resource[] mapperLocations = this.properties.resolveMapperLocations(); 170 if (!ObjectUtils.isEmpty(mapperLocations)) { 171 factory.setMapperLocations(mapperLocations); 172 } 173 Set<String> factoryPropertyNames = Stream 174 .of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName) 175 .collect(Collectors.toSet()); 176 Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver(); 177 if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) { 178 // Need to mybatis-spring 2.0.2+ 179 factory.setScriptingLanguageDrivers(this.languageDrivers); 180 if (defaultLanguageDriver == null && this.languageDrivers.length == 1) { 181 defaultLanguageDriver = this.languageDrivers[0].getClass(); 182 } 183 } 184 if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) { 185 // Need to mybatis-spring 2.0.2+ 186 factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver); 187 } 188 applySqlSessionFactoryBeanCustomizers(factory); 189 return factory.getObject(); 190 } 191 192 protected void applyConfiguration(SqlSessionFactoryBean factory) { 193 MybatisFlexProperties.CoreConfiguration coreConfiguration = this.properties.getConfiguration(); 194// Configuration configuration = null; 195 FlexConfiguration configuration = null; 196 if (coreConfiguration != null || !StringUtils.hasText(this.properties.getConfigLocation())) { 197 configuration = new FlexConfiguration(); 198 } 199 if (configuration != null && coreConfiguration != null) { 200 coreConfiguration.applyTo(configuration); 201 } 202 if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) { 203 for (ConfigurationCustomizer customizer : this.configurationCustomizers) { 204 customizer.customize(configuration); 205 } 206 } 207 factory.setConfiguration(configuration); 208 } 209 210 protected void applySqlSessionFactoryBeanCustomizers(SqlSessionFactoryBean factory) { 211 if (!CollectionUtils.isEmpty(this.sqlSessionFactoryBeanCustomizers)) { 212 for (SqlSessionFactoryBeanCustomizer customizer : this.sqlSessionFactoryBeanCustomizers) { 213 customizer.customize(factory); 214 } 215 } 216 } 217 218 @Bean 219 @ConditionalOnMissingBean 220 public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { 221 ExecutorType executorType = this.properties.getExecutorType(); 222 if (executorType != null) { 223 return new SqlSessionTemplate(sqlSessionFactory, executorType); 224 } else { 225 return new SqlSessionTemplate(sqlSessionFactory); 226 } 227 } 228 229 /** 230 * This will just scan the same base package as Spring Boot does. If you want more power, you can explicitly use 231 * {@link org.mybatis.spring.annotation.MapperScan} but this will get typed mappers working correctly, out-of-the-box, 232 * similar to using Spring Data JPA repositories. 233 */ 234 public static class AutoConfiguredMapperScannerRegistrar 235 implements BeanFactoryAware, EnvironmentAware, ImportBeanDefinitionRegistrar { 236 237 private BeanFactory beanFactory; 238 private Environment environment; 239 240 @Override 241 public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { 242 243 if (!AutoConfigurationPackages.has(this.beanFactory)) { 244 logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled."); 245 return; 246 } 247 248 logger.debug("Searching for mappers annotated with @Mapper"); 249 250 List<String> packages = AutoConfigurationPackages.get(this.beanFactory); 251 if (logger.isDebugEnabled()) { 252 packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg)); 253 } 254 255 BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); 256 builder.addPropertyValue("processPropertyPlaceHolders", true); 257 builder.addPropertyValue("annotationClass", Mapper.class); 258 builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages)); 259 BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class); 260 Set<String> propertyNames = Stream.of(beanWrapper.getPropertyDescriptors()).map(PropertyDescriptor::getName) 261 .collect(Collectors.toSet()); 262 if (propertyNames.contains("lazyInitialization")) { 263 // Need to mybatis-spring 2.0.2+ 264 builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}"); 265 } 266 if (propertyNames.contains("defaultScope")) { 267 // Need to mybatis-spring 2.0.6+ 268 builder.addPropertyValue("defaultScope", "${mybatis.mapper-default-scope:}"); 269 } 270 271 // for spring-native 272 boolean injectSqlSession = environment.getProperty("mybatis.inject-sql-session-on-mapper-scan", Boolean.class, 273 Boolean.TRUE); 274 if (injectSqlSession && this.beanFactory instanceof ListableBeanFactory) { 275 ListableBeanFactory listableBeanFactory = (ListableBeanFactory) this.beanFactory; 276 Optional<String> sqlSessionTemplateBeanName = Optional 277 .ofNullable(getBeanNameForType(SqlSessionTemplate.class, listableBeanFactory)); 278 Optional<String> sqlSessionFactoryBeanName = Optional 279 .ofNullable(getBeanNameForType(SqlSessionFactory.class, listableBeanFactory)); 280 if (sqlSessionTemplateBeanName.isPresent() || !sqlSessionFactoryBeanName.isPresent()) { 281 builder.addPropertyValue("sqlSessionTemplateBeanName", 282 sqlSessionTemplateBeanName.orElse("sqlSessionTemplate")); 283 } else { 284 builder.addPropertyValue("sqlSessionFactoryBeanName", sqlSessionFactoryBeanName.get()); 285 } 286 } 287 builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); 288 289 registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition()); 290 } 291 292 @Override 293 public void setBeanFactory(BeanFactory beanFactory) { 294 this.beanFactory = beanFactory; 295 } 296 297 @Override 298 public void setEnvironment(Environment environment) { 299 this.environment = environment; 300 } 301 302 private String getBeanNameForType(Class<?> type, ListableBeanFactory factory) { 303 String[] beanNames = factory.getBeanNamesForType(type); 304 return beanNames.length > 0 ? beanNames[0] : null; 305 } 306 307 } 308 309 /** 310 * If mapper registering configuration or mapper scanning configuration not present, this configuration allow to scan 311 * mappers based on the same component-scanning path as Spring Boot itself. 312 */ 313 @org.springframework.context.annotation.Configuration(proxyBeanMethods = false) 314 @Import(AutoConfiguredMapperScannerRegistrar.class) 315 @ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class}) 316 public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean { 317 318 @Override 319 public void afterPropertiesSet() { 320 logger.debug( 321 "Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer."); 322 } 323 324 } 325 326}