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 "<sqlmapper>" 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 <properties>} 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}