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