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 MyBatisFlexCustomizer mybatisFlexCustomizer;
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> mybatisFlexCustomizerProvider
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.mybatisFlexCustomizer = mybatisFlexCustomizerProvider.getIfAvailable();
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 (mybatisFlexCustomizer != null) {
219            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}