View Javadoc
1   /*
2    * Copyright 2010-2023 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *    https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.mybatis.spring;
17  
18  import static org.springframework.util.Assert.notNull;
19  import static org.springframework.util.Assert.state;
20  import static org.springframework.util.ObjectUtils.isEmpty;
21  import static org.springframework.util.StringUtils.hasLength;
22  import static org.springframework.util.StringUtils.tokenizeToStringArray;
23  
24  import java.io.IOException;
25  import java.lang.reflect.Modifier;
26  import java.sql.SQLException;
27  import java.util.ArrayList;
28  import java.util.Arrays;
29  import java.util.HashSet;
30  import java.util.List;
31  import java.util.Optional;
32  import java.util.Properties;
33  import java.util.Set;
34  import java.util.function.IntFunction;
35  import java.util.stream.Stream;
36  
37  import javax.sql.DataSource;
38  
39  import org.apache.ibatis.builder.xml.XMLConfigBuilder;
40  import org.apache.ibatis.builder.xml.XMLMapperBuilder;
41  import org.apache.ibatis.cache.Cache;
42  import org.apache.ibatis.executor.ErrorContext;
43  import org.apache.ibatis.io.Resources;
44  import org.apache.ibatis.io.VFS;
45  import org.apache.ibatis.mapping.DatabaseIdProvider;
46  import org.apache.ibatis.mapping.Environment;
47  import org.apache.ibatis.plugin.Interceptor;
48  import org.apache.ibatis.reflection.factory.ObjectFactory;
49  import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
50  import org.apache.ibatis.scripting.LanguageDriver;
51  import org.apache.ibatis.session.Configuration;
52  import org.apache.ibatis.session.SqlSessionFactory;
53  import org.apache.ibatis.session.SqlSessionFactoryBuilder;
54  import org.apache.ibatis.transaction.TransactionFactory;
55  import org.apache.ibatis.type.TypeHandler;
56  import org.mybatis.logging.Logger;
57  import org.mybatis.logging.LoggerFactory;
58  import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
59  import org.springframework.beans.factory.FactoryBean;
60  import org.springframework.beans.factory.InitializingBean;
61  import org.springframework.context.ApplicationListener;
62  import org.springframework.context.ConfigurableApplicationContext;
63  import org.springframework.context.event.ContextRefreshedEvent;
64  import org.springframework.core.io.Resource;
65  import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
66  import org.springframework.core.io.support.ResourcePatternResolver;
67  import org.springframework.core.type.ClassMetadata;
68  import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
69  import org.springframework.core.type.classreading.MetadataReaderFactory;
70  import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
71  import org.springframework.util.ClassUtils;
72  
73  /**
74   * {@code FactoryBean} that creates a MyBatis {@code SqlSessionFactory}. This is the usual way to set up a shared
75   * MyBatis {@code SqlSessionFactory} in a Spring application context; the SqlSessionFactory can then be passed to
76   * MyBatis-based DAOs via dependency injection.
77   * <p>
78   * Either {@code DataSourceTransactionManager} or {@code JtaTransactionManager} can be used for transaction demarcation
79   * in combination with a {@code SqlSessionFactory}. JTA should be used for transactions which span multiple databases or
80   * when container managed transactions (CMT) are being used.
81   *
82   * @author Putthiphong Boonphong
83   * @author Hunter Presnall
84   * @author Eduardo Macarron
85   * @author EddĂș MelĂ©ndez
86   * @author Kazuki Shimizu
87   * @author Jens Schauder
88   *
89   * @see #setConfigLocation
90   * @see #setDataSource
91   */
92  public class SqlSessionFactoryBean
93      implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ContextRefreshedEvent> {
94  
95    private static final Logger LOGGER = LoggerFactory.getLogger(SqlSessionFactoryBean.class);
96  
97    private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new PathMatchingResourcePatternResolver();
98    private static final MetadataReaderFactory METADATA_READER_FACTORY = new CachingMetadataReaderFactory();
99  
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 
114   private SqlSessionFactory sqlSessionFactory;
115 
116   // EnvironmentAware requires spring 3.1
117   private String environment = SqlSessionFactoryBean.class.getSimpleName();
118 
119   private boolean failFast;
120 
121   private Interceptor[] plugins;
122 
123   private TypeHandler<?>[] typeHandlers;
124 
125   private String typeHandlersPackage;
126 
127   @SuppressWarnings("rawtypes")
128   private Class<? extends TypeHandler> defaultEnumTypeHandler;
129 
130   private Class<?>[] typeAliases;
131 
132   private String typeAliasesPackage;
133 
134   private Class<?> typeAliasesSuperType;
135 
136   private LanguageDriver[] scriptingLanguageDrivers;
137 
138   private Class<? extends LanguageDriver> defaultScriptingLanguageDriver;
139 
140   // issue #19. No default provider.
141   private DatabaseIdProvider databaseIdProvider;
142 
143   private Class<? extends VFS> vfs;
144 
145   private Cache cache;
146 
147   private ObjectFactory objectFactory;
148 
149   private ObjectWrapperFactory objectWrapperFactory;
150 
151   /**
152    * Sets the ObjectFactory.
153    *
154    * @since 1.1.2
155    *
156    * @param objectFactory
157    *          a custom ObjectFactory
158    */
159   public void setObjectFactory(ObjectFactory objectFactory) {
160     this.objectFactory = objectFactory;
161   }
162 
163   /**
164    * Sets the ObjectWrapperFactory.
165    *
166    * @since 1.1.2
167    *
168    * @param objectWrapperFactory
169    *          a specified ObjectWrapperFactory
170    */
171   public void setObjectWrapperFactory(ObjectWrapperFactory objectWrapperFactory) {
172     this.objectWrapperFactory = objectWrapperFactory;
173   }
174 
175   /**
176    * Gets the DatabaseIdProvider
177    *
178    * @since 1.1.0
179    *
180    * @return a specified DatabaseIdProvider
181    */
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    * @since 1.1.0
190    *
191    * @param databaseIdProvider
192    *          a DatabaseIdProvider
193    */
194   public void setDatabaseIdProvider(DatabaseIdProvider databaseIdProvider) {
195     this.databaseIdProvider = databaseIdProvider;
196   }
197 
198   /**
199    * Gets the VFS.
200    *
201    * @return a specified VFS
202    */
203   public Class<? extends VFS> getVfs() {
204     return this.vfs;
205   }
206 
207   /**
208    * Sets the VFS.
209    *
210    * @param vfs
211    *          a VFS
212    */
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   public Cache getCache() {
223     return this.cache;
224   }
225 
226   /**
227    * Sets the Cache.
228    *
229    * @param cache
230    *          a Cache
231    */
232   public void setCache(Cache cache) {
233     this.cache = cache;
234   }
235 
236   /**
237    * Mybatis plugin list.
238    *
239    * @since 1.0.1
240    *
241    * @param plugins
242    *          list of plugins
243    */
244   public void setPlugins(Interceptor... plugins) {
245     this.plugins = plugins;
246   }
247 
248   /**
249    * Packages to search for type aliases.
250    * <p>
251    * Since 2.0.1, allow to specify a wildcard such as {@code com.example.*.model}.
252    *
253    * @since 1.0.1
254    *
255    * @param typeAliasesPackage
256    *          package to scan for domain objects
257    */
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    * @since 1.1.2
267    *
268    * @param typeAliasesSuperType
269    *          super class for domain objects
270    */
271   public void setTypeAliasesSuperType(Class<?> typeAliasesSuperType) {
272     this.typeAliasesSuperType = typeAliasesSuperType;
273   }
274 
275   /**
276    * Packages to search for type handlers.
277    * <p>
278    * Since 2.0.1, allow to specify a wildcard such as {@code com.example.*.typehandler}.
279    *
280    * @since 1.0.1
281    *
282    * @param typeHandlersPackage
283    *          package to scan for type handlers
284    */
285   public void setTypeHandlersPackage(String typeHandlersPackage) {
286     this.typeHandlersPackage = typeHandlersPackage;
287   }
288 
289   /**
290    * Set type handlers. They must be annotated with {@code MappedTypes} and optionally with {@code MappedJdbcTypes}
291    *
292    * @since 1.0.1
293    *
294    * @param typeHandlers
295    *          Type handler list
296    */
297   public void setTypeHandlers(TypeHandler<?>... typeHandlers) {
298     this.typeHandlers = typeHandlers;
299   }
300 
301   /**
302    * Set the default type handler class for enum.
303    *
304    * @since 2.0.5
305    *
306    * @param defaultEnumTypeHandler
307    *          The default type handler class for enum
308    */
309   public void setDefaultEnumTypeHandler(
310       @SuppressWarnings("rawtypes") Class<? extends TypeHandler> defaultEnumTypeHandler) {
311     this.defaultEnumTypeHandler = defaultEnumTypeHandler;
312   }
313 
314   /**
315    * List of type aliases to register. They can be annotated with {@code Alias}
316    *
317    * @since 1.0.1
318    *
319    * @param typeAliases
320    *          Type aliases list
321    */
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    * @since 1.0.1
331    *
332    * @param failFast
333    *          enable failFast
334    */
335   public void setFailFast(boolean failFast) {
336     this.failFast = failFast;
337   }
338 
339   /**
340    * Set the location of the MyBatis {@code SqlSessionFactory} config file. A typical value is
341    * "WEB-INF/mybatis-configuration.xml".
342    *
343    * @param configLocation
344    *          a location the MyBatis config file
345    */
346   public void setConfigLocation(Resource configLocation) {
347     this.configLocation = configLocation;
348   }
349 
350   /**
351    * Set a customized MyBatis configuration.
352    *
353    * @param configuration
354    *          MyBatis configuration
355    *
356    * @since 1.3.0
357    */
358   public void setConfiguration(Configuration configuration) {
359     this.configuration = configuration;
360   }
361 
362   /**
363    * Set locations of MyBatis mapper files that are going to be merged into the {@code SqlSessionFactory} configuration
364    * at runtime.
365    * <p>
366    * This is an alternative to specifying "&lt;sqlmapper&gt;" entries in an MyBatis config file. This property being
367    * based on Spring's resource abstraction also allows for specifying resource patterns here: e.g.
368    * "classpath*:sqlmap/*-mapper.xml".
369    *
370    * @param mapperLocations
371    *          location of MyBatis mapper files
372    */
373   public void setMapperLocations(Resource... mapperLocations) {
374     this.mapperLocations = mapperLocations;
375   }
376 
377   /**
378    * Set optional properties to be passed into the SqlSession configuration, as alternative to a
379    * {@code &lt;properties&gt;} tag in the configuration xml file. This will be used to resolve placeholders in the
380    * config file.
381    *
382    * @param sqlSessionFactoryProperties
383    *          optional properties for the SqlSessionFactory
384    */
385   public void setConfigurationProperties(Properties sqlSessionFactoryProperties) {
386     this.configurationProperties = sqlSessionFactoryProperties;
387   }
388 
389   /**
390    * Set the JDBC {@code DataSource} that this instance should manage transactions for. The {@code DataSource} should
391    * match the one used by the {@code SqlSessionFactory}: for example, you could specify the same JNDI DataSource for
392    * both.
393    * <p>
394    * A transactional JDBC {@code Connection} for this {@code DataSource} will be provided to application code accessing
395    * this {@code DataSource} directly via {@code DataSourceUtils} or {@code DataSourceTransactionManager}.
396    * <p>
397    * The {@code DataSource} specified here should be the target {@code DataSource} to manage transactions for, not a
398    * {@code TransactionAwareDataSourceProxy}. Only data access code may work with
399    * {@code TransactionAwareDataSourceProxy}, while the transaction manager needs to work on the underlying target
400    * {@code DataSource}. If there's nevertheless a {@code TransactionAwareDataSourceProxy} passed in, it will be
401    * unwrapped to extract its target {@code DataSource}.
402    *
403    * @param dataSource
404    *          a JDBC {@code DataSource}
405    */
406   public void setDataSource(DataSource dataSource) {
407     if (dataSource instanceof TransactionAwareDataSourceProxy) {
408       // If we got a TransactionAwareDataSourceProxy, we need to perform
409       // transactions for its underlying target DataSource, else data
410       // access code won't see properly exposed transactions (i.e.
411       // transactions for the target DataSource).
412       this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
413     } else {
414       this.dataSource = dataSource;
415     }
416   }
417 
418   /**
419    * Sets the {@code SqlSessionFactoryBuilder} to use when creating the {@code SqlSessionFactory}.
420    * <p>
421    * This is mainly meant for testing so that mock SqlSessionFactory classes can be injected. By default,
422    * {@code SqlSessionFactoryBuilder} creates {@code DefaultSqlSessionFactory} instances.
423    *
424    * @param sqlSessionFactoryBuilder
425    *          a SqlSessionFactoryBuilder
426    */
427   public void setSqlSessionFactoryBuilder(SqlSessionFactoryBuilder sqlSessionFactoryBuilder) {
428     this.sqlSessionFactoryBuilder = sqlSessionFactoryBuilder;
429   }
430 
431   /**
432    * Set the MyBatis TransactionFactory to use. Default is {@code SpringManagedTransactionFactory}.
433    * <p>
434    * The default {@code SpringManagedTransactionFactory} should be appropriate for all cases: be it Spring transaction
435    * management, EJB CMT or plain JTA. If there is no active transaction, SqlSession operations will execute SQL
436    * statements non-transactionally.
437    * <p>
438    * <b>It is strongly recommended to use the default {@code TransactionFactory}.</b> If not used, any attempt at
439    * getting an SqlSession through Spring's MyBatis framework will throw an exception if a transaction is active.
440    *
441    * @see SpringManagedTransactionFactory
442    *
443    * @param transactionFactory
444    *          the MyBatis TransactionFactory
445    */
446   public void setTransactionFactory(TransactionFactory transactionFactory) {
447     this.transactionFactory = transactionFactory;
448   }
449 
450   /**
451    * <b>NOTE:</b> This class <em>overrides</em> any {@code Environment} you have set in the MyBatis config file. This is
452    * used only as a placeholder name. The default value is {@code SqlSessionFactoryBean.class.getSimpleName()}.
453    *
454    * @param environment
455    *          the environment name
456    */
457   public void setEnvironment(String environment) {
458     this.environment = environment;
459   }
460 
461   /**
462    * Set scripting language drivers.
463    *
464    * @param scriptingLanguageDrivers
465    *          scripting language drivers
466    *
467    * @since 2.0.2
468    */
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
477    *          A default scripting language driver class
478    *
479    * @since 2.0.2
480    */
481   public void setDefaultScriptingLanguageDriver(Class<? extends LanguageDriver> defaultScriptingLanguageDriver) {
482     this.defaultScriptingLanguageDriver = defaultScriptingLanguageDriver;
483   }
484 
485   /**
486    * Add locations of MyBatis mapper files that are going to be merged into the {@code SqlSessionFactory} configuration
487    * at runtime.
488    * <p>
489    * This is an alternative to specifying "&lt;sqlmapper&gt;" entries in an MyBatis config file. This property being
490    * based on Spring's resource abstraction also allows for specifying resource patterns here: e.g.
491    * "classpath*:sqlmap/*-mapper.xml".
492    *
493    * @param mapperLocations
494    *          location of MyBatis mapper files
495    *
496    * @see #setMapperLocations(Resource...)
497    *
498    * @since 3.0.2
499    */
500   public void addMapperLocations(Resource... mapperLocations) {
501     setMapperLocations(appendArrays(this.mapperLocations, mapperLocations, Resource[]::new));
502   }
503 
504   /**
505    * Add type handlers.
506    *
507    * @param typeHandlers
508    *          Type handler list
509    *
510    * @since 3.0.2
511    */
512   public void addTypeHandlers(TypeHandler<?>... typeHandlers) {
513     setTypeHandlers(appendArrays(this.typeHandlers, typeHandlers, TypeHandler[]::new));
514   }
515 
516   /**
517    * Add scripting language drivers.
518    *
519    * @param scriptingLanguageDrivers
520    *          scripting language drivers
521    *
522    * @since 3.0.2
523    */
524   public void addScriptingLanguageDrivers(LanguageDriver... scriptingLanguageDrivers) {
525     setScriptingLanguageDrivers(
526         appendArrays(this.scriptingLanguageDrivers, scriptingLanguageDrivers, LanguageDriver[]::new));
527   }
528 
529   /**
530    * Add Mybatis plugins.
531    *
532    * @param plugins
533    *          list of plugins
534    *
535    * @since 3.0.2
536    */
537   public void addPlugins(Interceptor... plugins) {
538     setPlugins(appendArrays(this.plugins, plugins, Interceptor[]::new));
539   }
540 
541   /**
542    * Add type aliases.
543    *
544    * @param typeAliases
545    *          Type aliases list
546    *
547    * @since 3.0.2
548    */
549   public void addTypeAliases(Class<?>... typeAliases) {
550     setTypeAliases(appendArrays(this.typeAliases, typeAliases, Class[]::new));
551   }
552 
553   private <T> T[] appendArrays(T[] oldArrays, T[] newArrays, IntFunction<T[]> generator) {
554     if (oldArrays == null) {
555       return newArrays;
556     } else {
557       if (newArrays == null) {
558         return oldArrays;
559       } else {
560         List<T> newList = new ArrayList<>(Arrays.asList(oldArrays));
561         newList.addAll(Arrays.asList(newArrays));
562         return newList.toArray(generator.apply(0));
563       }
564     }
565   }
566 
567   /**
568    * {@inheritDoc}
569    */
570   @Override
571   public void afterPropertiesSet() throws Exception {
572     notNull(dataSource, "Property 'dataSource' is required");
573     notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
574     state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
575         "Property 'configuration' and 'configLocation' can not specified with together");
576 
577     this.sqlSessionFactory = buildSqlSessionFactory();
578   }
579 
580   /**
581    * Build a {@code SqlSessionFactory} instance.
582    * <p>
583    * The default implementation uses the standard MyBatis {@code XMLConfigBuilder} API to build a
584    * {@code SqlSessionFactory} instance based on a Reader. Since 1.3.0, it can be specified a {@link Configuration}
585    * instance directly(without config file).
586    *
587    * @return SqlSessionFactory
588    *
589    * @throws Exception
590    *           if configuration is failed
591    */
592   protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
593 
594     final Configuration targetConfiguration;
595 
596     XMLConfigBuilder xmlConfigBuilder = null;
597     if (this.configuration != null) {
598       targetConfiguration = this.configuration;
599       if (targetConfiguration.getVariables() == null) {
600         targetConfiguration.setVariables(this.configurationProperties);
601       } else if (this.configurationProperties != null) {
602         targetConfiguration.getVariables().putAll(this.configurationProperties);
603       }
604     } else if (this.configLocation != null) {
605       xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
606       targetConfiguration = xmlConfigBuilder.getConfiguration();
607     } else {
608       LOGGER.debug(
609           () -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
610       targetConfiguration = new Configuration();
611       Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
612     }
613 
614     Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
615     Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
616     Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
617 
618     if (hasLength(this.typeAliasesPackage)) {
619       scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
620           .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
621           .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
622     }
623 
624     if (!isEmpty(this.typeAliases)) {
625       Stream.of(this.typeAliases).forEach(typeAlias -> {
626         targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
627         LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
628       });
629     }
630 
631     if (!isEmpty(this.plugins)) {
632       Stream.of(this.plugins).forEach(plugin -> {
633         targetConfiguration.addInterceptor(plugin);
634         LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
635       });
636     }
637 
638     if (hasLength(this.typeHandlersPackage)) {
639       scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
640           .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
641           .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
642     }
643 
644     if (!isEmpty(this.typeHandlers)) {
645       Stream.of(this.typeHandlers).forEach(typeHandler -> {
646         targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
647         LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
648       });
649     }
650 
651     targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);
652 
653     if (!isEmpty(this.scriptingLanguageDrivers)) {
654       Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
655         targetConfiguration.getLanguageRegistry().register(languageDriver);
656         LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
657       });
658     }
659     Optional.ofNullable(this.defaultScriptingLanguageDriver)
660         .ifPresent(targetConfiguration::setDefaultScriptingLanguage);
661 
662     if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
663       try {
664         targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
665       } catch (SQLException e) {
666         throw new IOException("Failed getting a databaseId", e);
667       }
668     }
669 
670     Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
671 
672     if (xmlConfigBuilder != null) {
673       try {
674         xmlConfigBuilder.parse();
675         LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
676       } catch (Exception ex) {
677         throw new IOException("Failed to parse config resource: " + this.configLocation, ex);
678       } finally {
679         ErrorContext.instance().reset();
680       }
681     }
682 
683     targetConfiguration.setEnvironment(new Environment(this.environment,
684         this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
685         this.dataSource));
686 
687     if (this.mapperLocations != null) {
688       if (this.mapperLocations.length == 0) {
689         LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
690       } else {
691         for (Resource mapperLocation : this.mapperLocations) {
692           if (mapperLocation == null) {
693             continue;
694           }
695           try {
696             XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
697                 targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
698             xmlMapperBuilder.parse();
699           } catch (Exception e) {
700             throw new IOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
701           } finally {
702             ErrorContext.instance().reset();
703           }
704           LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
705         }
706       }
707     } else {
708       LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
709     }
710 
711     return this.sqlSessionFactoryBuilder.build(targetConfiguration);
712   }
713 
714   /**
715    * {@inheritDoc}
716    */
717   @Override
718   public SqlSessionFactory getObject() throws Exception {
719     if (this.sqlSessionFactory == null) {
720       afterPropertiesSet();
721     }
722 
723     return this.sqlSessionFactory;
724   }
725 
726   /**
727    * {@inheritDoc}
728    */
729   @Override
730   public Class<? extends SqlSessionFactory> getObjectType() {
731     return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
732   }
733 
734   /**
735    * {@inheritDoc}
736    */
737   @Override
738   public boolean isSingleton() {
739     return true;
740   }
741 
742   /**
743    * {@inheritDoc}
744    */
745   @Override
746   public void onApplicationEvent(ContextRefreshedEvent event) {
747     if (failFast) {
748       // fail-fast -> check all statements are completed
749       this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
750     }
751   }
752 
753   private Set<Class<?>> scanClasses(String packagePatterns, Class<?> assignableType) throws IOException {
754     Set<Class<?>> classes = new HashSet<>();
755     String[] packagePatternArray = tokenizeToStringArray(packagePatterns,
756         ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
757     for (String packagePattern : packagePatternArray) {
758       Resource[] resources = RESOURCE_PATTERN_RESOLVER.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
759           + ClassUtils.convertClassNameToResourcePath(packagePattern) + "/**/*.class");
760       for (Resource resource : resources) {
761         try {
762           ClassMetadata classMetadata = METADATA_READER_FACTORY.getMetadataReader(resource).getClassMetadata();
763           Class<?> clazz = Resources.classForName(classMetadata.getClassName());
764           if (assignableType == null || assignableType.isAssignableFrom(clazz)) {
765             classes.add(clazz);
766           }
767         } catch (Throwable e) {
768           LOGGER.warn(() -> "Cannot load the '" + resource + "'. Cause by " + e.toString());
769         }
770       }
771     }
772     return classes;
773   }
774 
775 }