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