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