Mybatis源碼分析——Mybatis-Spring框架使用與解析

前言

在前面幾篇文章中我們主要分析了Mybatis的單獨使用垮衷,在實際在常規(guī)項目開發(fā)中,大部分都會使用mybatis與Spring結(jié)合起來使用乖坠,畢竟現(xiàn)在不用Spring開發(fā)的項目實在太少了搀突。本篇文章便來介紹下Mybatis如何與Spring結(jié)合起來使用,并介紹下其源碼是如何實現(xiàn)的熊泵。

Mybatis-Spring使用

添加maven依賴

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.2</version>
</dependency>

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.3</version>
</dependency>

<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper-spring-boot-starter</artifactId>
    <version>2.1.5</version>
</dependency>

Mybatis和Spring整合方式即mybatis-spring

在src/main/resources下添加mybatis-config.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <typeAliases>
        <typeAlias alias="User" type="com.yibo.bean.User" />
    </typeAliases>
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <property name="helperDialect" value="mysql"/>
        </plugin>
    </plugins>

</configuration>

在src/main/resources/mapper路徑下添加User.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
<mapper namespace="com.yibo.mapper.UserMapper">
    <select id="getUser" parameterType="int" resultType="com.yibo.bean.User">
        SELECT * FROM USER WHERE id = #{id}
    </select>
</mapper>

在src/main/resources/路徑下添加beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
 
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/test"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
 
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="configLocation" value="classpath:mybatis-config.xml"></property>
        <property name="dataSource" ref="dataSource" />
        <property name="mapperLocations" value="classpath:mapper/*.xml" />
    </bean>
    
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.chenhao.mapper" />
    </bean>
 
</beans>
注解的方式
  • 以上分析都是在spring的XML配置文件applicationContext.xml進行配置的仰迁,mybatis-spring也提供了基于注解的方式來配置sqlSessionFactory和Mapper接口。

  • sqlSessionFactory主要是在@Configuration注解的配置類中使用@Bean注解的名為sqlSessionFactory的方法來配置顽分。

  • Mapper接口主要是通過在@Configuration注解的配置類中結(jié)合@MapperScan注解來指定需要掃描獲取mapper接口的包徐许。

@Configuration
@MapperScan("com.yibo.mapper")
public class AppConfig {

    @Bean
    public DataSource dataSource() {
     return new EmbeddedDatabaseBuilder()
            .addScript("schema.sql")
            .build();
    }

    @Bean
    public DataSourceTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        //創(chuàng)建SqlSessionFactoryBean對象
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        //設(shè)置數(shù)據(jù)源
        sessionFactory.setDataSource(dataSource());
        //設(shè)置Mapper.xml路徑
        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
        // 設(shè)置MyBatis分頁插件
        PageInterceptor pageInterceptor = new PageInterceptor();
        Properties properties = new Properties();
        properties.setProperty("helperDialect", "mysql");
        pageInterceptor.setProperties(properties);
        sessionFactory.setPlugins(new Interceptor[]{pageInterceptor});
        return sessionFactory.getObject();
    }
}

對照Spring-Mybatis的方式,也就是對照beans.xml文件來看

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="configLocation" value="classpath:mybatis-config.xml"></property>
    <property name="dataSource" ref="dataSource" />
    <property name="mapperLocations" value="classpath:mapper/*.xml" />
</bean>

就對應著SqlSessionFactory的生成卒蘸,類似于原生Mybatis使用時的以下代碼

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build( Resources.getResourceAsStream("mybatis-config.xml"));

而UserMapper代理對象的獲取雌隅,是通過掃描的形式獲取,也就是MapperScannerConfigurer這個類

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.yibo.mapper" />
</bean>

對應著Mapper接口的獲取缸沃,類似于原生Mybatis使用時的以下代碼:

qlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);

Mybatis和SpringBoot整合方式

即引入mybatis-spring-boot-startermapper-spring-boot-starter

  • application.properties中的配置
mybatis.type-aliases-package=com.yibo.source.code.domain.entity
mybatis.mapper-locations=classpath:mapper/*.xml
mapper.identity=MYSQL
mapper.not-empty=false
  • 主配置類中加入@MapperScan
@MapperScan("com.yibo.source.code.mapper")//掃描Mapper接口

這樣我們就可以在Service中直接從Spring的BeanFactory中獲取了恰起,如下

public class UserServiceImpl implements UserService{

    @Autowired
    private UserMapper userMapper;
    
}   

所以我們現(xiàn)在就主要分析下在Spring中是如何生成SqlSessionFactory和Mapper接口的

SqlSessionFactoryBean的設(shè)計與實現(xiàn)

大體思路:

  • mybatis-spring為了實現(xiàn)spring對mybatis的整合,即將mybatis的相關(guān)組件作為spring的IOC容器的bean來管理趾牧,使用了spring的FactoryBean接口來對mybatis的相關(guān)組件進行包裝检盼。spring的IOC容器在啟動加載時,如果發(fā)現(xiàn)某個bean實現(xiàn)了FactoryBean接口翘单,則會調(diào)用該bean的getObject方法吨枉,獲取實際的bean對象注冊到IOC容器蹦渣,其中FactoryBean接口提供了getObject方法的聲明,從而統(tǒng)一spring的IOC容器的行為东羹。

  • SqlSessionFactory作為mybatis的啟動組件剂桥,在mybatis-spring中提供了SqlSessionFactoryBean來進行包裝,所以在spring項目中整合mybatis属提,首先需要在spring的配置权逗,如XML配置文件applicationContext.xml中,配置SqlSessionFactoryBean來引入SqlSessionFactory冤议,即在spring項目啟動時能加載并創(chuàng)建SqlSessionFactory對象斟薇,然后注冊到spring的IOC容器中,從而可以直接在應用代碼中注入使用或者作為屬性恕酸,注入到mybatis的其他組件對應的bean對象堪滨。在applicationContext.xml的配置如下:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
       // 數(shù)據(jù)源
       <property name="dataSource" ref="dataSource" />
       // mapper.xml的資源文件,也就是SQL文件
       <property name="mapperLocations" value="classpath:mybatis/mapper/**/*.xml" />
       //mybatis配置mybatisConfig.xml的資源文件
       <property name="configLocation" value="classpath:mybatis/mybitas-config.xml" />
</bean>

接口設(shè)計與實現(xiàn)

SqlSessionFactory的接口設(shè)計如下:實現(xiàn)了spring提供的FactoryBean,InitializingBean和ApplicationListener這三個接口蕊温,在內(nèi)部封裝了mybatis的相關(guān)組件作為內(nèi)部屬性袱箱,如mybatisConfig.xml配置資源文件引用,mapper.xml配置資源文件引用义矛,以及SqlSessionFactoryBuilder構(gòu)造器和SqlSessionFactory引用发笔。

// 解析mybatisConfig.xml文件和mapper.xml,設(shè)置數(shù)據(jù)源和所使用的事務管理機制凉翻,將這些封裝到Configuration對象
// 使用Configuration對象作為構(gòu)造參數(shù)了讨,創(chuàng)建SqlSessionFactory對象,其中SqlSessionFactory為單例bean制轰,最后將SqlSessionFactory單例對象注冊到spring容器前计。
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
    private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class);
    
    // mybatis配置mybatisConfig.xml的資源文件
    private Resource configLocation;
    
    //解析完mybatisConfig.xml后生成Configuration對象
    private Configuration configuration;
    
    // mapper.xml的資源文件
    private Resource[] mapperLocations;
    
    // 數(shù)據(jù)源
    private DataSource dataSource;
    
    // 事務管理,mybatis接入spring的一個重要原因也是可以直接使用spring提供的事務管理
    private TransactionFactory transactionFactory;
    private Properties configurationProperties;
    
    // mybatis的SqlSessionFactoryBuidler和SqlSessionFactory
    private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    private SqlSessionFactory sqlSessionFactory;
    
    // 實現(xiàn)FactoryBean的getObject方法
    public SqlSessionFactory getObject() throws Exception {
        if (this.sqlSessionFactory == null) {
            this.afterPropertiesSet();
        }

        return this.sqlSessionFactory;
    }
    
    // 實現(xiàn)InitializingBean的
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(this.dataSource, "Property 'dataSource' is required");
        Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
        Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property 'configuration' and 'configLocation' can not specified with together");
        this.sqlSessionFactory = this.buildSqlSessionFactory();
    }
    
    // 單例
    public boolean isSingleton() {
        return true;
    }
}

我們重點關(guān)注FactoryBean垃杖,InitializingBean這兩個接口男杈,spring的IOC容器在加載創(chuàng)建SqlSessionFactoryBean的bean對象實例時,會調(diào)用InitializingBean的afterPropertiesSet方法進行對該bean對象進行相關(guān)初始化處理调俘。

InitializingBean的afterPropertiesSet方法

大家最好看一下我前面關(guān)于Spring源碼的文章势就,有Bean的生命周期詳細源碼分析,我們現(xiàn)在簡單回顧一下脉漏,在getBean()時initializeBean方法中調(diào)用InitializingBean的afterPropertiesSet苞冯,而在前一步操作populateBean中,以及將該bean對象實例的屬性設(shè)值好了侧巨,InitializingBean的afterPropertiesSet進行一些后置處理舅锄。此時我們要注意,populateBean方法已經(jīng)將SqlSessionFactoryBean對象的屬性進行賦值了司忱,也就是xml中property配置的dataSource皇忿,mapperLocations畴蹭,configLocation這三個屬性已經(jīng)在SqlSessionFactoryBean對象的屬性進行賦值了,后面調(diào)用afterPropertiesSet時直接可以使用這三個配置的值了鳍烁。

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
        implements AutowireCapableBeanFactory {

    // bean對象實例創(chuàng)建的核心實現(xiàn)方法
    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
            throws BeanCreationException {

        // Instantiate the bean.
        // 1.新建Bean包裝類
        BeanWrapper instanceWrapper = null;
        //如果RootBeanDefinition是單例的叨襟,則移除未完成的FactoryBean實例的緩存
        if (mbd.isSingleton()) {
            // 2.如果是FactoryBean,則需要先移除未完成的FactoryBean實例的緩存
            instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
        }
        if (instanceWrapper == null) {
            // 3.根據(jù)beanName幔荒、mbd糊闽、args,使用對應的策略創(chuàng)建Bean實例爹梁,并返回包裝類BeanWrapper
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
        // 4.拿到創(chuàng)建好的Bean實例
        final Object bean = instanceWrapper.getWrappedInstance();
        // 5.拿到Bean實例的類型
        Class<?> beanType = instanceWrapper.getWrappedClass();
        if (beanType != NullBean.class) {
            mbd.resolvedTargetType = beanType;
        }

        // Allow post-processors to modify the merged bean definition.
        synchronized (mbd.postProcessingLock) {
            if (!mbd.postProcessed) {
                try {
                    // 6.應用后置處理器MergedBeanDefinitionPostProcessor右犹,允許修改MergedBeanDefinition,
                    // Autowired注解姚垃、Value注解正是通過此方法實現(xiàn)注入類型的預解析
                    applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
                }
                catch (Throwable ex) {
                    throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                            "Post-processing of merged bean definition failed", ex);
                }
                mbd.postProcessed = true;
            }
        }

        // Eagerly cache singletons to be able to resolve circular references
        // even when triggered by lifecycle interfaces like BeanFactoryAware.
        // 7.判斷是否需要提早曝光實例:單例 && 允許循環(huán)依賴 && 當前bean正在創(chuàng)建中
        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                isSingletonCurrentlyInCreation(beanName));
        if (earlySingletonExposure) {
            if (logger.isTraceEnabled()) {
                logger.trace("Eagerly caching bean '" + beanName +
                        "' to allow for resolving potential circular references");
            }
            // 8.提前曝光beanName的ObjectFactory念链,用于解決循環(huán)引用
            // 8.1 應用后置處理器SmartInstantiationAwareBeanPostProcessor,允許返回指定bean的早期引用积糯,若沒有則直接返回bean
            addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        }

        // Initialize the bean instance.
        // 初始化bean實例掂墓。
        Object exposedObject = bean;
        try {
        
        // InstantiationAwareBeanPostProcessor執(zhí)行:
        //  (1). 調(diào)用InstantiationAwareBeanPostProcessor的postProcessAfterInstantiation,
        //  (2). 調(diào)用InstantiationAwareBeanPostProcessor的postProcessProperties和postProcessPropertyValues
            // 9.對bean進行屬性填充;其中看成,可能存在依賴于其他bean的屬性君编,則會遞歸初始化依賴的bean實例
            populateBean(beanName, mbd, instanceWrapper);
            
            // Aware接口的方法調(diào)用
            // BeanPostProcess執(zhí)行:調(diào)用BeanPostProcessor的postProcessBeforeInitialization
            // 調(diào)用init-method:首先InitializingBean的afterPropertiesSet,然后應用配置的init-method
            // BeanPostProcess執(zhí)行:調(diào)用BeanPostProcessor的postProcessAfterInitialization
            // 10.對bean進行初始化
            exposedObject = initializeBean(beanName, exposedObject, mbd);
        }
        catch (Throwable ex) {
            if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
                throw (BeanCreationException) ex;
            }
            else {
                throw new BeanCreationException(
                        mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
            }
        }

        if (earlySingletonExposure) {
            // 11.如果允許提前曝光實例绍昂,則進行循環(huán)依賴檢查
            Object earlySingletonReference = getSingleton(beanName, false);
            // 11.1 earlySingletonReference只有在當前解析的bean存在循環(huán)依賴的情況下才會不為空
            if (earlySingletonReference != null) {
                if (exposedObject == bean) {
                    // 11.2 如果exposedObject沒有在initializeBean方法中被增強,則不影響之前的循環(huán)引用
                    exposedObject = earlySingletonReference;
                }
                else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                    // 11.3 如果exposedObject在initializeBean方法中被增強 && 不允許在循環(huán)引用的情況下使用注入原始bean實例
                    // && 當前bean有被其他bean依賴
                    // 11.4 拿到依賴當前bean的所有bean的beanName數(shù)組
                    String[] dependentBeans = getDependentBeans(beanName);
                    Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
                    for (String dependentBean : dependentBeans) {
                        // 11.5 嘗試移除這些bean的實例偿荷,因為這些bean依賴的bean已經(jīng)被增強了窘游,他們依賴的bean相當于臟數(shù)據(jù)
                        if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                            // 11.6 移除失敗的添加到 actualDependentBeans
                            actualDependentBeans.add(dependentBean);
                        }
                    }
                    if (!actualDependentBeans.isEmpty()) {
                        // 11.7 如果存在移除失敗的,則拋出異常跳纳,因為存在bean依賴了“臟數(shù)據(jù)”
                        throw new BeanCurrentlyInCreationException(beanName,
                                "Bean with name '" + beanName + "' has been injected into other beans [" +
                                StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                                "] in its raw version as part of a circular reference, but has eventually been " +
                                "wrapped. This means that said other beans do not use the final version of the " +
                                "bean. This is often the result of over-eager type matching - consider using " +
                                "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
                    }
                }
            }
        }

        // Register bean as disposable.
        try {
            // 12.注冊用于銷毀的bean忍饰,執(zhí)行銷毀操作的有三種:自定義destroy方法、DisposableBean接口寺庄、DestructionAwareBeanPostProcessor
            registerDisposableBeanIfNecessary(beanName, bean, mbd);
        }
        catch (BeanDefinitionValidationException ex) {
            throw new BeanCreationException(
                    mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
        }
        // 13.完成創(chuàng)建并返回
        return exposedObject;
    }
}

如上艾蓝,在populateBean階段,dataSource斗塘,mapperLocations赢织,configLocation這三個屬性已經(jīng)在SqlSessionFactoryBean對象的屬性進行賦值了,調(diào)用afterPropertiesSet時直接可以使用這三個配置的值了馍盟。那我們來接著看看afterPropertiesSet方法

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {

    @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");
        // 創(chuàng)建sqlSessionFactory
        this.sqlSessionFactory = buildSqlSessionFactory();
    }
}

SqlSessionFactoryBean的afterPropertiesSet方法實現(xiàn)如下:調(diào)用buildSqlSessionFactory方法創(chuàng)建用于注冊到spring的IOC容器的sqlSessionFactory對象于置。我們接著來看看buildSqlSessionFactory

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {

  protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    // 配置類
    Configuration configuration;
    // 解析mybatis-Config.xml文件八毯,
    // 將相關(guān)配置信息保存到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) {
      //根據(jù)configLocation創(chuàng)建xmlConfigBuilder泊交,XMLConfigBuilder構(gòu)造器中會創(chuàng)建Configuration對象
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      
      //將XMLConfigBuilder構(gòu)造器中創(chuàng)建的Configuration對象直接賦值給configuration屬性
      configuration = xmlConfigBuilder.getConfiguration();
    } else {
      if (LOGGER.isDebugEnabled()) {
        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);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
        }
      }
    }

    if (!isEmpty(this.typeAliases)) {
      for (Class<?> typeAlias : this.typeAliases) {
        configuration.getTypeAliasRegistry().registerAlias(typeAlias);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered type alias: '" + typeAlias + "'");
        }
      }
    }

    if (!isEmpty(this.plugins)) {
      for (Interceptor plugin : this.plugins) {
        configuration.addInterceptor(plugin);
        if (LOGGER.isDebugEnabled()) {
          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);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
        }
      }
    }

    if (!isEmpty(this.typeHandlers)) {
      for (TypeHandler<?> typeHandler : this.typeHandlers) {
        configuration.getTypeHandlerRegistry().register(typeHandler);
        if (LOGGER.isDebugEnabled()) {
          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 {
        //解析mybatis-Config.xml文件,并將相關(guān)配置信息保存到configuration
        xmlConfigBuilder.parse();

        if (LOGGER.isDebugEnabled()) {
          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) {
      //事務默認采用SpringManagedTransaction仰楚,這一塊非常重要捂襟,我將在后買你單獨寫一篇文章講解Mybatis和Spring事務的關(guān)系   
      this.transactionFactory = new SpringManagedTransactionFactory();
    }
    // 為sqlSessionFactory綁定事務管理器和數(shù)據(jù)源
    // 這樣sqlSessionFactory在創(chuàng)建sqlSession的時候可以通過該事務管理器獲取jdbc連接举反,從而執(zhí)行SQL
    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
    
    // 解析mapper.xml
    if (!isEmpty(this.mapperLocations)) {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }

        try {
          // 解析mapper.xml文件,并注冊到configuration對象的mapperRegistry
          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();
        }

        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
      }
    }

    // 將Configuration對象實例作為參數(shù),
    // 調(diào)用sqlSessionFactoryBuilder創(chuàng)建sqlSessionFactory對象實例
    return this.sqlSessionFactoryBuilder.build(configuration);
  }
}

buildSqlSessionFactory的核心邏輯:解析mybatis配置文件mybatisConfig.xml和mapper配置文件mapper.xml并封裝到Configuration對象中,最后調(diào)用mybatis的sqlSessionFactoryBuilder來創(chuàng)建SqlSessionFactory對象牧挣。這一點相當于前面介紹的原生的mybatis的初始化過程急前。另外,當配置中未指定事務時瀑构,mybatis-spring默認采用SpringManagedTransaction裆针,這一點非常重要,請大家先在心里做好準備寺晌。此時SqlSessionFactory已經(jīng)創(chuàng)建好了世吨,并且賦值到了SqlSessionFactoryBean的sqlSessionFactory屬性中。

FactoryBean的getObject方法定義

FactoryBean:創(chuàng)建某個類的對象實例的工廠呻征。

spring的IOC容器在啟動耘婚,創(chuàng)建好bean對象實例后,會檢查這個bean對象是否實現(xiàn)了FactoryBean接口陆赋,如果是沐祷,則調(diào)用該bean對象的getObject方法,在getObject方法中實現(xiàn)創(chuàng)建并返回實際需要的bean對象實例攒岛,然后將該實際需要的bean對象實例注冊到spring容器赖临;如果不是則直接將該bean對象實例注冊到spring容器。

SqlSessionFactoryBean的getObject方法實現(xiàn)如下:由于spring在創(chuàng)建SqlSessionFactoryBean自身的bean對象時阵子,已經(jīng)調(diào)用了InitializingBean的afterPropertiesSet方法創(chuàng)建了sqlSessionFactory對象思杯,故可以直接返回sqlSessionFactory對象給spring的IOC容器胜蛉,從而完成sqlSessionFactory的bean對象的注冊挠进,之后可以直接在應用代碼注入或者spring在創(chuàng)建其他bean對象時,依賴注入sqlSessionFactory對象誊册。

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {

    @Override
    public SqlSessionFactory getObject() throws Exception {
        if (this.sqlSessionFactory == null) {
            afterPropertiesSet();
        }
        // 直接返回sqlSessionFactory對象
        // 單例對象领突,由所有mapper共享
        return this.sqlSessionFactory;
    }
}

總結(jié)

由以上分析可知,spring在加載創(chuàng)建SqlSessionFactoryBean的bean對象實例時案怯,調(diào)用SqlSessionFactoryBean的afterPropertiesSet方法完成了sqlSessionFactory對象實例的創(chuàng)建君旦;在將SqlSessionFactoryBean對象實例注冊到spring的IOC容器時,發(fā)現(xiàn)SqlSessionFactoryBean實現(xiàn)了FactoryBean接口,故不是SqlSessionFactoryBean對象實例自身需要注冊到spring的IOC容器金砍,而是SqlSessionFactoryBean的getObject方法的返回值對應的對象需要注冊到spring的IOC容器局蚀,而這個返回值就是SqlSessionFactory對象,故完成了將sqlSessionFactory對象實例注冊到spring的IOC容器恕稠。

參考:
https://www.cnblogs.com/java-chen-hao/p/11833780.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末琅绅,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子鹅巍,更是在濱河造成了極大的恐慌千扶,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骆捧,死亡現(xiàn)場離奇詭異澎羞,居然都是意外死亡,警方通過查閱死者的電腦和手機敛苇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門妆绞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人接谨,你說我怎么就攤上這事摆碉。” “怎么了脓豪?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵巷帝,是天一觀的道長。 經(jīng)常有香客問我扫夜,道長楞泼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任笤闯,我火速辦了婚禮堕阔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘颗味。我一直安慰自己超陆,他們只是感情好,可當我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布浦马。 她就那樣靜靜地躺著时呀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪晶默。 梳的紋絲不亂的頭發(fā)上谨娜,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天,我揣著相機與錄音磺陡,去河邊找鬼趴梢。 笑死漠畜,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的坞靶。 我是一名探鬼主播憔狞,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼彰阴!你這毒婦竟也來了躯喇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤硝枉,失蹤者是張志新(化名)和其女友劉穎廉丽,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體妻味,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡正压,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了责球。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片焦履。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖雏逾,靈堂內(nèi)的尸體忽然破棺而出嘉裤,到底是詐尸還是另有隱情,我是刑警寧澤栖博,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布屑宠,位于F島的核電站,受9級特大地震影響仇让,放射性物質(zhì)發(fā)生泄漏典奉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一丧叽、第九天 我趴在偏房一處隱蔽的房頂上張望卫玖。 院中可真熱鬧,春花似錦踊淳、人聲如沸假瞬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脱茉。三九已至,卻和暖如春雹舀,著一層夾襖步出監(jiān)牢的瞬間芦劣,已是汗流浹背粗俱。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工说榆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留虚吟,地道東北人。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓签财,卻偏偏與公主長得像串慰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子唱蒸,可洞房花燭夜當晚...
    茶點故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內(nèi)容