Spring整合MyBatis(三)sqlSessionFactory創建
摘要: 本文結合《Spring源碼深度解析》來分析Spring 5.0.6版本的源代碼。若有描述錯誤之處,歡迎指正。
目錄
一、SqlSessionFactoryBean的初始化
二、獲取 SqlSessionFactoryBean 實例
通過Spring整合MyBatis的示例,我們感受到了Spring為用戶更加快捷地進行開發所做的努力,開發人員的工作效率由此得到了顯著的提升。但是,相對於使用來說,我們更想知道其背後所隱藏的秘密,Spring整合MyBatis是如何實現的呢?通過分析整合示例中的配置文件,我們可以知道配置的bean其實是成樹狀結構的,而在樹的最頂層是類型為org.mybatis.spring.SqlSessionFactoryBean的bean,它將其他相關bean組裝在了一起,那麽,我們的分析就從此類開始。
通過配置文件我們分析,對於配置文件的讀取解析,Spring應該通過org.mybatis.spring.SqlSessionFactoryBean封裝了MyBatis中的實現。我們進入這個類,首先査看這個類的層次結構,如下圖所示。
根據這個類的層次結構找出我們感興趣的兩個接口,FactoryBean和InitializingBean:
- InitializingBean:實現此接口的bean會在初始化時調用其afterPropertiesSet方法來進行bean的邏輯初始化。
- FactoryBean:一旦某個bean實現此接口,那麽通過getBean方法獲取bean時其實是獲取此類的getObject()返回的實例。
我們首先以InitializingBean接口的afterPropertiesSet()方法作為突破點。
一、SqlSessionFactoryBean的初始化
査看org.mybatis.spring.SqlSessionFactoryBean類型的bean在初始化時做了哪些邏輯實現。
@Override public void afterPropertiesSet() throws Exception { notNull(dataSource, "Property ‘dataSource‘ is required"); notNull(sqlSessionFactoryBuilder,"Property ‘sqlSessionFactoryBuilder‘ is required"); state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null), "Property ‘configuration‘ and ‘configLocation‘ can not specified with together"); this.sqlSessionFactory = buildSqlSessionFactory(); }
很顯然,此函數主要目的就是對於sqlSessionFactory的初始化,通過之前展示的獨立使用MyBatis的示例,我們了解到SqlSessionFactory是所有MyBatis功能的基礎。
protected SqlSessionFactory buildSqlSessionFactory() throws IOException { Configuration configuration; XMLConfigBuilder xmlConfigBuilder = null; if (this.configuration != null) { configuration = this.configuration; if (configuration.getVariables() == null) { configuration.setVariables(this.configurationProperties); } else if (this.configurationProperties != null) { configuration.getVariables().putAll(this.configurationProperties); } } else if (this.configLocation != null) { xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); configuration = xmlConfigBuilder.getConfiguration(); } else { LOGGER.debug(() -> "Property ‘configuration‘ or ‘configLocation‘ not specified, using default MyBatis Configuration"); configuration = new Configuration(); if (this.configurationProperties != null) { configuration.setVariables(this.configurationProperties); } } if (this.objectFactory != null) { configuration.setObjectFactory(this.objectFactory); } if (this.objectWrapperFactory != null) { configuration.setObjectWrapperFactory(this.objectWrapperFactory); } if (this.vfs != null) { configuration.setVfsImpl(this.vfs); } if (hasLength(this.typeAliasesPackage)) { String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeAliasPackageArray) { configuration.getTypeAliasRegistry().registerAliases(packageToScan, typeAliasesSuperType == null ? Object.class : typeAliasesSuperType); LOGGER.debug(() -> "Scanned package: ‘" + packageToScan + "‘ for aliases"); } } if (!isEmpty(this.typeAliases)) { for (Class<?> typeAlias : this.typeAliases) { configuration.getTypeAliasRegistry().registerAlias(typeAlias); LOGGER.debug(() -> "Registered type alias: ‘" + typeAlias + "‘"); } } if (!isEmpty(this.plugins)) { for (Interceptor plugin : this.plugins) { configuration.addInterceptor(plugin); LOGGER.debug(() -> "Registered plugin: ‘" + plugin + "‘"); } } if (hasLength(this.typeHandlersPackage)) { String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeHandlersPackageArray) { configuration.getTypeHandlerRegistry().register(packageToScan); LOGGER.debug(() -> "Scanned package: ‘" + packageToScan + "‘ for type handlers"); } } if (!isEmpty(this.typeHandlers)) { for (TypeHandler<?> typeHandler : this.typeHandlers) { configuration.getTypeHandlerRegistry().register(typeHandler); LOGGER.debug(() -> "Registered type handler: ‘" + typeHandler + "‘"); } } if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls try { configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource)); } catch (SQLException e) { throw new NestedIOException("Failed getting a databaseId", e); } } if (this.cache != null) { configuration.addCache(this.cache); } if (xmlConfigBuilder != null) { try { xmlConfigBuilder.parse(); LOGGER.debug(() -> "Parsed configuration file: ‘" + this.configLocation + "‘"); } catch (Exception ex) { throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex); } finally { ErrorContext.instance().reset(); } } if (this.transactionFactory == null) { this.transactionFactory = new SpringManagedTransactionFactory(); } configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource)); if (!isEmpty(this.mapperLocations)) { for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: ‘" + mapperLocation + "‘", e); } finally { ErrorContext.instance().reset(); } LOGGER.debug(() -> "Parsed mapper file: ‘" + mapperLocation + "‘"); } } else { LOGGER.debug(() -> "Property ‘mapperLocations‘ was not specified or no matching resources found"); } return this.sqlSessionFactoryBuilder.build(configuration); }
從函數中可以看到,盡管我們還是習慣於將MyBatis的配置與Spring的配置獨立開來,但是,這並不代表Spring中的配置不支持直接配置。也就是說,在上面提供的示例中,你完全可以取消配置中的configLocation,而把其中的屬性直接寫在SqlSessionFactoryBean中。
從這個函數中可以得知,配置文件還可以支持其他多種屬性的配置,如configLocation、objectFactory、objectWrapperFactory、typeAliasesPackage、typeAliases、typeHandlersPackage、plugins、typeHandlers、transactionFactory、databaseIdProvider、mapperLocations。
其實,如果只按照常用的配置,那麽我們只需要在函數最開始按照如下方式處理configuration:
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); configuration = xmlConfigBuilder.getConfiguration();
根據configLocation構造XMLConfigBuilder並進行解析,但是,為了體現Spring更強大的兼容性,Spring還整合了MyBatis中其他屬性的註入,並通過實例configuration來承載每一步所獲取的信息並最終使用sqlSessionFactoryBuilder實例根據解析到的configuration創建SqlSessionFactory實例。
二、獲取 SqlSessionFactoryBean 實例
由於SqlSessionFactoryBean實現了FactoryBean接口,所以當通過getBean方法獲取對應實例時,其實是獲取該類的getObject()函數返回的實例,也就是獲取初始化後的sqlSessionFactory屬性。
@Override public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { afterPropertiesSet(); } return this.sqlSessionFactory; }
Spring整合MyBatis(三)sqlSessionFactory創建