001/*
002 * Copyright 2010-2023 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;
017
018import com.github.gavlyukovskiy.boot.jdbc.decorator.DecoratedDataSource;
019import com.mybatisflex.core.FlexConsts;
020import com.mybatisflex.core.datasource.FlexDataSource;
021import com.mybatisflex.core.mybatis.FlexConfiguration;
022import com.mybatisflex.core.mybatis.FlexSqlSessionFactoryBuilder;
023import org.apache.ibatis.builder.xml.XMLConfigBuilder;
024import org.apache.ibatis.builder.xml.XMLMapperBuilder;
025import org.apache.ibatis.cache.Cache;
026import org.apache.ibatis.executor.ErrorContext;
027import org.apache.ibatis.io.Resources;
028import org.apache.ibatis.io.VFS;
029import org.apache.ibatis.mapping.DatabaseIdProvider;
030import org.apache.ibatis.mapping.Environment;
031import org.apache.ibatis.plugin.Interceptor;
032import org.apache.ibatis.reflection.factory.ObjectFactory;
033import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
034import org.apache.ibatis.scripting.LanguageDriver;
035import org.apache.ibatis.session.Configuration;
036import org.apache.ibatis.session.SqlSessionFactory;
037import org.apache.ibatis.session.SqlSessionFactoryBuilder;
038import org.apache.ibatis.transaction.TransactionFactory;
039import org.apache.ibatis.type.TypeHandler;
040import org.mybatis.logging.Logger;
041import org.mybatis.logging.LoggerFactory;
042import org.mybatis.spring.SqlSessionFactoryBean;
043import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
044import org.springframework.beans.factory.FactoryBean;
045import org.springframework.beans.factory.InitializingBean;
046import org.springframework.context.ApplicationListener;
047import org.springframework.context.ConfigurableApplicationContext;
048import org.springframework.context.event.ContextRefreshedEvent;
049import org.springframework.core.io.Resource;
050import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
051import org.springframework.core.io.support.ResourcePatternResolver;
052import org.springframework.core.type.ClassMetadata;
053import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
054import org.springframework.core.type.classreading.MetadataReaderFactory;
055import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
056import org.springframework.util.ClassUtils;
057
058import javax.sql.DataSource;
059import java.io.IOException;
060import java.lang.reflect.Modifier;
061import java.sql.SQLException;
062import java.util.HashSet;
063import java.util.Optional;
064import java.util.Properties;
065import java.util.Set;
066import java.util.stream.Stream;
067
068import static org.springframework.util.Assert.notNull;
069import static org.springframework.util.Assert.state;
070import static org.springframework.util.ObjectUtils.isEmpty;
071import static org.springframework.util.StringUtils.hasLength;
072import static org.springframework.util.StringUtils.tokenizeToStringArray;
073
074/**
075 * 参考:https://github.com/mybatis/spring/blob/master/src/main/java/org/mybatis/spring/SqlSessionFactoryBean.java
076 *
077 * <p>在 MyBatis 官方的 SqlSessionFactoryBean 基础上,替换了  FlexSqlSessionFactoryBean。
078 *
079 * <p>源于 {@link SqlSessionFactoryBean},主要是用于构建 {@link com.mybatisflex.core.mybatis.FlexConfiguration },而不是使用原生的 {@link Configuration}。
080 *
081 * <p>此代码主要是用于修改 {@link FlexSqlSessionFactoryBean#buildSqlSessionFactory()} 部分。
082 *
083 * @author Putthiphong Boonphong
084 * @author Hunter Presnall
085 * @author Eduardo Macarron
086 * @author Eddú Meléndez
087 * @author Kazuki Shimizu
088 * @author Jens Schauder
089 * @author 王帅
090 * @author miachel
091 * @author life
092 */
093public class FlexSqlSessionFactoryBean extends SqlSessionFactoryBean
094    implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ContextRefreshedEvent> {
095
096    private static final Logger LOGGER = LoggerFactory.getLogger(SqlSessionFactoryBean.class);
097
098    private static final String P6SPY_DATA_SOURCE_CLASS = "com.github.gavlyukovskiy.boot.jdbc.decorator.DecoratedDataSource";
099
100    private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new PathMatchingResourcePatternResolver();
101    private static final MetadataReaderFactory METADATA_READER_FACTORY = new CachingMetadataReaderFactory();
102
103    private Resource configLocation;
104
105    private Configuration configuration;
106
107    private Resource[] mapperLocations;
108
109    private DataSource dataSource;
110
111    private TransactionFactory transactionFactory;
112
113    private Properties configurationProperties;
114
115    //    private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
116    private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new FlexSqlSessionFactoryBuilder();
117
118    private SqlSessionFactory sqlSessionFactory;
119
120    // EnvironmentAware requires spring 3.1
121    //    private String environment = SqlSessionFactoryBean.class.getSimpleName();
122    private String environment = FlexConsts.NAME;
123
124    private boolean failFast;
125
126    private Interceptor[] plugins;
127
128    private TypeHandler<?>[] typeHandlers;
129
130    private String typeHandlersPackage;
131
132    @SuppressWarnings("rawtypes")
133    private Class<? extends TypeHandler> defaultEnumTypeHandler;
134
135    private Class<?>[] typeAliases;
136
137    private String typeAliasesPackage;
138
139    private Class<?> typeAliasesSuperType;
140
141    private LanguageDriver[] scriptingLanguageDrivers;
142
143    private Class<? extends LanguageDriver> defaultScriptingLanguageDriver;
144
145    // issue #19. No default provider.
146    private DatabaseIdProvider databaseIdProvider;
147
148    private Class<? extends VFS> vfs;
149
150    private Cache cache;
151
152    private ObjectFactory objectFactory;
153
154    private ObjectWrapperFactory objectWrapperFactory;
155
156    /**
157     * Sets the ObjectFactory.
158     *
159     * @param objectFactory a custom ObjectFactory
160     * @since 1.1.2
161     */
162    @Override
163    public void setObjectFactory(ObjectFactory objectFactory) {
164        this.objectFactory = objectFactory;
165    }
166
167    /**
168     * Sets the ObjectWrapperFactory.
169     *
170     * @param objectWrapperFactory a specified ObjectWrapperFactory
171     * @since 1.1.2
172     */
173    @Override
174    public void setObjectWrapperFactory(ObjectWrapperFactory objectWrapperFactory) {
175        this.objectWrapperFactory = objectWrapperFactory;
176    }
177
178    /**
179     * Gets the DatabaseIdProvider
180     *
181     * @return a specified DatabaseIdProvider
182     * @since 1.1.0
183     */
184    @Override
185    public DatabaseIdProvider getDatabaseIdProvider() {
186        return databaseIdProvider;
187    }
188
189    /**
190     * Sets the DatabaseIdProvider. As of version 1.2.2 this variable is not initialized by default.
191     *
192     * @param databaseIdProvider a DatabaseIdProvider
193     * @since 1.1.0
194     */
195    @Override
196    public void setDatabaseIdProvider(DatabaseIdProvider databaseIdProvider) {
197        this.databaseIdProvider = databaseIdProvider;
198    }
199
200    /**
201     * Gets the VFS.
202     *
203     * @return a specified VFS
204     */
205    @Override
206    public Class<? extends VFS> getVfs() {
207        return this.vfs;
208    }
209
210    /**
211     * Sets the VFS.
212     *
213     * @param vfs a VFS
214     */
215    @Override
216    public void setVfs(Class<? extends VFS> vfs) {
217        this.vfs = vfs;
218    }
219
220    /**
221     * Gets the Cache.
222     *
223     * @return a specified Cache
224     */
225    @Override
226    public Cache getCache() {
227        return this.cache;
228    }
229
230    /**
231     * Sets the Cache.
232     *
233     * @param cache a Cache
234     */
235    @Override
236    public void setCache(Cache cache) {
237        this.cache = cache;
238    }
239
240    /**
241     * Mybatis plugin list.
242     *
243     * @param plugins list of plugins
244     * @since 1.0.1
245     */
246    @Override
247    public void setPlugins(Interceptor... plugins) {
248        this.plugins = plugins;
249    }
250
251    /**
252     * Packages to search for type aliases.
253     *
254     * <p>
255     * Since 2.0.1, allow to specify a wildcard such as {@code com.example.*.model}.
256     *
257     * @param typeAliasesPackage package to scan for domain objects
258     * @since 1.0.1
259     */
260    @Override
261    public void setTypeAliasesPackage(String typeAliasesPackage) {
262        this.typeAliasesPackage = typeAliasesPackage;
263    }
264
265    /**
266     * Super class which domain objects have to extend to have a type alias created. No effect if there is no package to
267     * scan configured.
268     *
269     * @param typeAliasesSuperType super class for domain objects
270     * @since 1.1.2
271     */
272    @Override
273    public void setTypeAliasesSuperType(Class<?> typeAliasesSuperType) {
274        this.typeAliasesSuperType = typeAliasesSuperType;
275    }
276
277    /**
278     * Packages to search for type handlers.
279     *
280     * <p>
281     * Since 2.0.1, allow to specify a wildcard such as {@code com.example.*.typehandler}.
282     *
283     * @param typeHandlersPackage package to scan for type handlers
284     * @since 1.0.1
285     */
286    @Override
287    public void setTypeHandlersPackage(String typeHandlersPackage) {
288        this.typeHandlersPackage = typeHandlersPackage;
289    }
290
291    /**
292     * Set type handlers. They must be annotated with {@code MappedTypes} and optionally with {@code MappedJdbcTypes}
293     *
294     * @param typeHandlers Type handler list
295     * @since 1.0.1
296     */
297    @Override
298    public void setTypeHandlers(TypeHandler<?>... typeHandlers) {
299        this.typeHandlers = typeHandlers;
300    }
301
302    /**
303     * Set the default type handler class for enum.
304     *
305     * @param defaultEnumTypeHandler The default type handler class for enum
306     * @since 2.0.5
307     */
308    @Override
309    public void setDefaultEnumTypeHandler(
310        Class<? extends TypeHandler> defaultEnumTypeHandler
311    ) {
312        this.defaultEnumTypeHandler = defaultEnumTypeHandler;
313    }
314
315    /**
316     * List of type aliases to register. They can be annotated with {@code Alias}
317     *
318     * @param typeAliases Type aliases list
319     * @since 1.0.1
320     */
321    @Override
322    public void setTypeAliases(Class<?>... typeAliases) {
323        this.typeAliases = typeAliases;
324    }
325
326    /**
327     * If true, a final check is done on Configuration to assure that all mapped statements are fully loaded and there is
328     * no one still pending to resolve includes. Defaults to false.
329     *
330     * @param failFast enable failFast
331     * @since 1.0.1
332     */
333    @Override
334    public void setFailFast(boolean failFast) {
335        this.failFast = failFast;
336    }
337
338    /**
339     * Set the location of the MyBatis {@code SqlSessionFactory} config file. A typical value is
340     * "WEB-INF/mybatis-configuration.xml".
341     *
342     * @param configLocation a location the MyBatis config file
343     */
344    @Override
345    public void setConfigLocation(Resource configLocation) {
346        this.configLocation = configLocation;
347    }
348
349    /**
350     * Set a customized MyBatis configuration.
351     *
352     * @param configuration MyBatis configuration
353     * @since 1.3.0
354     */
355    @Override
356    public void setConfiguration(Configuration configuration) {
357        if (configuration != null && !(configuration instanceof FlexConfiguration)) {
358            throw new IllegalArgumentException("Only support FlexConfiguration.");
359        }
360
361        this.configuration = configuration;
362    }
363
364    /**
365     * Set locations of MyBatis mapper files that are going to be merged into the {@code SqlSessionFactory} configuration
366     * at runtime.
367     * <p>
368     * This is an alternative to specifying "&lt;sqlmapper&gt;" entries in an MyBatis config file. This property being
369     * based on Spring's resource abstraction also allows for specifying resource patterns here: e.g.
370     * "classpath*:sqlmap/*-mapper.xml".
371     *
372     * @param mapperLocations location of MyBatis mapper files
373     */
374    @Override
375    public void setMapperLocations(Resource... mapperLocations) {
376        this.mapperLocations = mapperLocations;
377    }
378
379    /**
380     * Set optional properties to be passed into the SqlSession configuration, as alternative to a
381     * {@code &lt;properties&gt;} tag in the configuration xml file. This will be used to resolve placeholders in the
382     * config file.
383     *
384     * @param sqlSessionFactoryProperties optional properties for the SqlSessionFactory
385     */
386    @Override
387    public void setConfigurationProperties(Properties sqlSessionFactoryProperties) {
388        this.configurationProperties = sqlSessionFactoryProperties;
389    }
390
391    /**
392     * Set the JDBC {@code DataSource} that this instance should manage transactions for. The {@code DataSource} should
393     * match the one used by the {@code SqlSessionFactory}: for example, you could specify the same JNDI DataSource for
394     * both.
395     * <p>
396     * A transactional JDBC {@code Connection} for this {@code DataSource} will be provided to application code accessing
397     * this {@code DataSource} directly via {@code DataSourceUtils} or {@code DataSourceTransactionManager}.
398     * <p>
399     * The {@code DataSource} specified here should be the target {@code DataSource} to manage transactions for, not a
400     * {@code TransactionAwareDataSourceProxy}. Only data access code may work with
401     * {@code TransactionAwareDataSourceProxy}, while the transaction manager needs to work on the underlying target
402     * {@code DataSource}. If there's nevertheless a {@code TransactionAwareDataSourceProxy} passed in, it will be
403     * unwrapped to extract its target {@code DataSource}.
404     *
405     * @param dataSource a JDBC {@code DataSource}
406     */
407    @Override
408    public void setDataSource(DataSource dataSource) {
409        if (dataSource instanceof TransactionAwareDataSourceProxy) {
410            // If we got a TransactionAwareDataSourceProxy, we need to perform
411            // transactions for its underlying target DataSource, else data
412            // access code won't see properly exposed transactions (i.e.
413            // transactions for the target DataSource).
414            this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
415        } else {
416            this.dataSource = dataSource;
417        }
418    }
419
420    /**
421     * Sets the {@code SqlSessionFactoryBuilder} to use when creating the {@code SqlSessionFactory}.
422     * <p>
423     * This is mainly meant for testing so that mock SqlSessionFactory classes can be injected. By default,
424     * {@code SqlSessionFactoryBuilder} creates {@code DefaultSqlSessionFactory} instances.
425     *
426     * @param sqlSessionFactoryBuilder a SqlSessionFactoryBuilder
427     */
428    @Override
429    public void setSqlSessionFactoryBuilder(SqlSessionFactoryBuilder sqlSessionFactoryBuilder) {
430        this.sqlSessionFactoryBuilder = sqlSessionFactoryBuilder;
431    }
432
433    /**
434     * Set the MyBatis TransactionFactory to use. Default is {@code SpringManagedTransactionFactory}
435     * <p>
436     * The default {@code SpringManagedTransactionFactory} should be appropriate for all cases: be it Spring transaction
437     * management, EJB CMT or plain JTA. If there is no active transaction, SqlSession operations will execute SQL
438     * statements non-transactionally.
439     *
440     * <b>It is strongly recommended to use the default {@code TransactionFactory}.</b> If not used, any attempt at
441     * getting an SqlSession through Spring's MyBatis framework will throw an exception if a transaction is active.
442     *
443     * @param transactionFactory the MyBatis TransactionFactory
444     * @see SpringManagedTransactionFactory
445     */
446    @Override
447    public void setTransactionFactory(TransactionFactory transactionFactory) {
448        this.transactionFactory = transactionFactory;
449    }
450
451    /**
452     * <b>NOTE:</b> This class <em>overrides</em> any {@code Environment} you have set in the MyBatis config file. This is
453     * used only as a placeholder name. The default value is {@code SqlSessionFactoryBean.class.getSimpleName()}.
454     *
455     * @param environment the environment name
456     */
457    @Override
458    public void setEnvironment(String environment) {
459        this.environment = environment;
460    }
461
462    /**
463     * Set scripting language drivers.
464     *
465     * @param scriptingLanguageDrivers scripting language drivers
466     * @since 2.0.2
467     */
468    @Override
469    public void setScriptingLanguageDrivers(LanguageDriver... scriptingLanguageDrivers) {
470        this.scriptingLanguageDrivers = scriptingLanguageDrivers;
471    }
472
473    /**
474     * Set a default scripting language driver class.
475     *
476     * @param defaultScriptingLanguageDriver A default scripting language driver class
477     * @since 2.0.2
478     */
479    @Override
480    public void setDefaultScriptingLanguageDriver(Class<? extends LanguageDriver> defaultScriptingLanguageDriver) {
481        this.defaultScriptingLanguageDriver = defaultScriptingLanguageDriver;
482    }
483
484    /**
485     * {@inheritDoc}
486     */
487    @Override
488    public void afterPropertiesSet() throws Exception {
489        notNull(dataSource, "Property 'dataSource' is required");
490        notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
491        state(
492            (configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
493            "Property 'configuration' and 'configLocation' can not specified with together"
494        );
495
496        this.sqlSessionFactory = buildSqlSessionFactory();
497    }
498
499    /**
500     * Build a {@code SqlSessionFactory} instance.
501     * <p>
502     * The default implementation uses the standard MyBatis {@code XMLConfigBuilder} API to build a
503     * {@code SqlSessionFactory} instance based on a Reader. Since 1.3.0, it can be specified a {@link Configuration}
504     * instance directly(without config file).
505     *
506     * @return SqlSessionFactory
507     * @throws Exception if configuration is failed
508     */
509    @Override
510    protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
511        final Configuration targetConfiguration;
512
513        XMLConfigBuilder xmlConfigBuilder = null;
514        if (this.configuration != null) {
515            targetConfiguration = this.configuration;
516
517            if (targetConfiguration.getVariables() == null) {
518                targetConfiguration.setVariables(this.configurationProperties);
519            } else if (this.configurationProperties != null) {
520                targetConfiguration.getVariables().putAll(this.configurationProperties);
521            }
522        } else if (this.configLocation != null) {
523            xmlConfigBuilder = new XMLConfigBuilder(FlexConfiguration.class, this.configLocation.getInputStream(), null, this.configurationProperties);
524
525            targetConfiguration = xmlConfigBuilder.getConfiguration();
526        } else {
527            LOGGER.debug(
528                () -> "Property 'configuration' or 'configLocation' not specified, using default Flex Configuration");
529
530            //            targetConfiguration = new Configuration();
531            targetConfiguration = new FlexConfiguration();
532
533            Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
534        }
535
536        Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
537        Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
538        Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
539
540        if (hasLength(this.typeAliasesPackage)) {
541            scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
542                .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
543                .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
544        }
545
546        if (!isEmpty(this.typeAliases)) {
547            Stream.of(this.typeAliases).forEach(typeAlias -> {
548                targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
549
550                LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
551            });
552        }
553
554        if (!isEmpty(this.plugins)) {
555            Stream.of(this.plugins).forEach(plugin -> {
556                targetConfiguration.addInterceptor(plugin);
557
558                LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
559            });
560        }
561
562        if (hasLength(this.typeHandlersPackage)) {
563            scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
564                .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
565                .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
566        }
567
568        if (!isEmpty(this.typeHandlers)) {
569            Stream.of(this.typeHandlers).forEach(typeHandler -> {
570                targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
571
572                LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
573            });
574        }
575
576        targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);
577
578        if (!isEmpty(this.scriptingLanguageDrivers)) {
579            Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
580                targetConfiguration.getLanguageRegistry().register(languageDriver);
581
582                LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
583            });
584        }
585
586        Optional.ofNullable(this.defaultScriptingLanguageDriver)
587            .ifPresent(targetConfiguration::setDefaultScriptingLanguage);
588
589        if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
590            try {
591                targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
592            } catch (SQLException e) {
593                throw new IOException("Failed getting a databaseId", e);
594            }
595        }
596
597        Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
598
599        if (xmlConfigBuilder != null) {
600            try {
601                xmlConfigBuilder.parse();
602
603                LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
604            } catch (Exception ex) {
605                throw new IOException("Failed to parse config resource: " + this.configLocation, ex);
606            } finally {
607                ErrorContext.instance().reset();
608            }
609        }
610
611        // 事务由 flex 管理了,无需使用 SpringManagedTransactionFactory,否则会造成在同一个事务下,无法切换数据源的问题
612        // fixed https://gitee.com/mybatis-flex/mybatis-flex/issues/I70QWU
613        // 兼容SpringManagedTransactionFactory否则在使用JdbcTemplate,多数据源使用JdbcTemplate报错
614        // fixed https://gitee.com/mybatis-flex/mybatis-flex/issues/I7HJ4J
615
616        FlexDataSource flexDataSource;
617
618        if (dataSource instanceof FlexDataSource) {
619            flexDataSource = (FlexDataSource) dataSource;
620        } else {
621            flexDataSource = new FlexDataSource(FlexConsts.NAME, dataSource);
622
623            // 解决 p6spy 下多数据源获取 DbType 失败的问题
624            // 首先判断一下是否引入了 p6spy 的包,避免非 p6spy 环境报异常
625            if (ClassUtils.isPresent(P6SPY_DATA_SOURCE_CLASS, this.getClass().getClassLoader())) {
626                if (dataSource instanceof DecoratedDataSource) {
627                    DecoratedDataSource decoratedDataSource = (DecoratedDataSource) dataSource;
628
629                    // 取出被 p6spy 包装的真实数据源
630                    if (decoratedDataSource.getRealDataSource() instanceof FlexDataSource) {
631                        FlexDataSource realDataSource = (FlexDataSource) decoratedDataSource.getRealDataSource();
632
633                        // 将原始 dbTypeHashMap 附加回来
634                        flexDataSource.getDbTypeHashMap().putAll(realDataSource.getDbTypeHashMap());
635                    }
636                }
637            }
638        }
639
640        targetConfiguration.setEnvironment(
641            new Environment(
642                this.environment,
643                this.transactionFactory == null ? new FlexTransactionFactory() : this.transactionFactory,
644                flexDataSource
645            )
646        );
647
648        // 需先构建 sqlSessionFactory,再去初始化 mapperLocations
649        // 因为 xmlMapperBuilder.parse() 用到 FlexGlobalConfig, FlexGlobalConfig 的初始化是在 sqlSessionFactory 的构建方法里进行的
650        // fixed https://gitee.com/mybatis-flex/mybatis-flex/issues/I6X59V
651        SqlSessionFactory sqlSessionFactory = this.sqlSessionFactoryBuilder.build(targetConfiguration);
652
653        if (this.mapperLocations != null) {
654            if (this.mapperLocations.length == 0) {
655                LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
656            } else {
657                for (Resource mapperLocation : this.mapperLocations) {
658                    if (mapperLocation == null) {
659                        continue;
660                    }
661                    try {
662                        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(
663                            mapperLocation.getInputStream(),
664                            targetConfiguration,
665                            mapperLocation.toString(),
666                            targetConfiguration.getSqlFragments()
667                        );
668
669                        xmlMapperBuilder.parse();
670                    } catch (Exception e) {
671                        throw new IOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
672                    } finally {
673                        ErrorContext.instance().reset();
674                    }
675
676                    LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
677                }
678            }
679        } else {
680            LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
681        }
682
683        return sqlSessionFactory;
684    }
685
686    /**
687     * {@inheritDoc}
688     */
689    @Override
690    public SqlSessionFactory getObject() throws Exception {
691        if (this.sqlSessionFactory == null) {
692            afterPropertiesSet();
693        }
694
695        return this.sqlSessionFactory;
696    }
697
698    /**
699     * {@inheritDoc}
700     */
701    @Override
702    public Class<? extends SqlSessionFactory> getObjectType() {
703        return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
704    }
705
706    /**
707     * {@inheritDoc}
708     */
709    @Override
710    public boolean isSingleton() {
711        return true;
712    }
713
714    /**
715     * {@inheritDoc}
716     */
717    @Override
718    public void onApplicationEvent(ContextRefreshedEvent event) {
719        if (failFast) {
720            // fail-fast -> check all statements are completed
721            this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
722        }
723    }
724
725    private Set<Class<?>> scanClasses(String packagePatterns, Class<?> assignableType) throws IOException {
726        Set<Class<?>> classes = new HashSet<>();
727        String[] packagePatternArray = tokenizeToStringArray(packagePatterns, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
728
729        for (String packagePattern : packagePatternArray) {
730            Resource[] resources = RESOURCE_PATTERN_RESOLVER.getResources(
731                ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
732                + ClassUtils.convertClassNameToResourcePath(packagePattern)
733                + "/**/*.class"
734            );
735
736            for (Resource resource : resources) {
737                try {
738                    ClassMetadata classMetadata = METADATA_READER_FACTORY.getMetadataReader(resource).getClassMetadata();
739                    Class<?> clazz = Resources.classForName(classMetadata.getClassName());
740
741                    if (assignableType == null || assignableType.isAssignableFrom(clazz)) {
742                        classes.add(clazz);
743                    }
744                } catch (Throwable e) {
745                    LOGGER.warn(() -> "Cannot load the '" + resource + "'. Cause by " + e.toString());
746                }
747            }
748        }
749
750        return classes;
751    }
752
753}