Mybaits 源碼解析 (十)----- Spring-Mybatis框架使用與源碼解析

在前面幾篇文章中咱們主要分析了Mybatis的單獨使用,在實際在常規項目開發中,大部分都會使用mybatis與Spring結合起來使用,畢竟如今不用Spring開發的項目實在太少了。本篇文章便來介紹下Mybatis如何與Spring結合起來使用,並介紹下其源碼是如何實現的。html

Spring-Mybatis使用

添加maven依賴

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>4.3.8.RELEASE</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.2</version>
</dependency>

在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.chenhao.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.chenhao.mapper.UserMapper">
    <select id="getUser" parameterType="int" resultType="com.chenhao.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註解的配置類中結合@MapperScan註解來指定須要掃描獲取mapper接口的包。
@Configuration @MapperScan("com.chenhao.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 { //建立SqlSessionFactoryBean對象 SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); //設置數據源  sessionFactory.setDataSource(dataSource()); //設置Mapper.xml路徑 sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml")); // 設置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使用時的如下代碼java

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

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

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

對應着Mapper接口的獲取,相似於原生Mybatis使用時的如下代碼:git

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

接着咱們就能夠在Service中直接從Spring的BeanFactory中獲取了,以下github

因此咱們如今就主要分析下在Spring中是如何生成SqlSessionFactory和Mapper接口的spring

SqlSessionFactoryBean的設計與實現

大致思路

  • mybatis-spring爲了實現spring對mybatis的整合,即將mybatis的相關組件做爲spring的IOC容器的bean來管理,使用了spring的FactoryBean接口來對mybatis的相關組件進行包裝。spring的IOC容器在啓動加載時,若是發現某個bean實現了FactoryBean接口,則會調用該bean的getObject方法,獲取實際的bean對象註冊到IOC容器,其中FactoryBean接口提供了getObject方法的聲明,從而統一spring的IOC容器的行爲。
  • SqlSessionFactory做爲mybatis的啓動組件,在mybatis-spring中提供了SqlSessionFactoryBean來進行包裝,因此在spring項目中整合mybatis,首先須要在spring的配置,如XML配置文件applicationContext.xml中,配置SqlSessionFactoryBean來引入SqlSessionFactory,即在spring項目啓動時能加載並建立SqlSessionFactory對象,而後註冊到spring的IOC容器中,從而能夠直接在應用代碼中注入使用或者做爲屬性,注入到mybatis的其餘組件對應的bean對象。在applicationContext.xml的配置以下:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> // 數據源 <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>

接口設計與實現

SqlSessionFactory的接口設計以下:實現了spring提供的FactoryBean,InitializingBean和ApplicationListener這三個接口,在內部封裝了mybatis的相關組件做爲內部屬性,如mybatisConfig.xml配置資源文件引用,mapper.xml配置資源文件引用,以及SqlSessionFactoryBuilder構造器和SqlSessionFactory引用。sql

// 解析mybatisConfig.xml文件和mapper.xml,設置數據源和所使用的事務管理機制,將這些封裝到Configuration對象 // 使用Configuration對象做爲構造參數,建立SqlSessionFactory對象,其中SqlSessionFactory爲單例bean,最後將SqlSessionFactory單例對象註冊到spring容器。
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> { private static final Logger LOGGER = LoggerFactory.getLogger(SqlSessionFactoryBean.class); // mybatis配置mybatisConfig.xml的資源文件
  private Resource configLocation; //解析完mybatisConfig.xml後生成Configuration對象
  private Configuration configuration; // mapper.xml的資源文件
  private Resource[] mapperLocations; // 數據源
  private DataSource dataSource; // 事務管理,mybatis接入spring的一個重要緣由也是能夠直接使用spring提供的事務管理
  private TransactionFactory transactionFactory; private Properties configurationProperties; // mybatis的SqlSessionFactoryBuidler和SqlSessionFactory
  private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); private SqlSessionFactory sqlSessionFactory; // 實現FactoryBean的getObject方法
 @Override public SqlSessionFactory getObject() throws Exception { //...
 } // 實現InitializingBean的
 @Override public void afterPropertiesSet() throws Exception { //...
 } // 爲單例
  public boolean isSingleton() { return true; } }

咱們重點關注FactoryBean,InitializingBean這兩個接口,spring的IOC容器在加載建立SqlSessionFactoryBean的bean對象實例時,會調用InitializingBean的afterPropertiesSet方法進行對該bean對象進行相關初始化處理。session

InitializingBean的afterPropertiesSet方法

你們最好看一下我前面關於Spring源碼的文章,有Bean的生命週期詳細源碼分析,咱們如今簡單回顧一下,在getBean()時initializeBean方法中調用InitializingBean的afterPropertiesSet,而在前一步操做populateBean中,以及將該bean對象實例的屬性設值好了,InitializingBean的afterPropertiesSet進行一些後置處理。此時咱們要注意,populateBean方法已經將SqlSessionFactoryBean對象的屬性進行賦值了,也就是xml中property配置的dataSource,mapperLocations,configLocation這三個屬性已經在SqlSessionFactoryBean對象的屬性進行賦值了,後面調用afterPropertiesSet時直接可使用這三個配置的值了。mybatis

// bean對象實例建立的核心實現方法
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { // 使用構造函數或者工廠方法來建立bean對象實例 // Instantiate the bean.
    BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); } ... // 初始化bean對象實例,包括屬性賦值,初始化方法,BeanPostProcessor的執行 // Initialize the bean instance.
    Object exposedObject = bean; try { // 1. InstantiationAwareBeanPostProcessor執行: // (1). 調用InstantiationAwareBeanPostProcessor的postProcessAfterInstantiation, // (2). 調用InstantiationAwareBeanPostProcessor的postProcessProperties和postProcessPropertyValues // 2. bean對象的屬性賦值
 populateBean(beanName, mbd, instanceWrapper); // 1. Aware接口的方法調用 // 2. BeanPostProcess執行:調用BeanPostProcessor的postProcessBeforeInitialization // 3. 調用init-method:首先InitializingBean的afterPropertiesSet,而後應用配置的init-method // 4. BeanPostProcess執行:調用BeanPostProcessor的postProcessAfterInitialization
        exposedObject = initializeBean(beanName, exposedObject, mbd); } // Register bean as disposable.
    try { registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); } return exposedObject; }

如上,在populateBean階段,dataSource,mapperLocations,configLocation這三個屬性已經在SqlSessionFactoryBean對象的屬性進行賦值了,調用afterPropertiesSet時直接可使用這三個配置的值了。那咱們來接着看看afterPropertiesSet方法app

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

SqlSessionFactoryBean的afterPropertiesSet方法實現以下:調用buildSqlSessionFactory方法建立用於註冊到spring的IOC容器的sqlSessionFactory對象。咱們接着來看看buildSqlSessionFactory

protected SqlSessionFactory buildSqlSessionFactory() throws IOException { // 配置類
 Configuration configuration; // 解析mybatis-Config.xml文件, // 將相關配置信息保存到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) { //根據configLocation建立xmlConfigBuilder,XMLConfigBuilder構造器中會建立Configuration對象
     xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); //將XMLConfigBuilder構造器中建立的Configuration對象直接賦值給configuration屬性
     configuration = xmlConfigBuilder.getConfiguration(); } //略....

   if (xmlConfigBuilder != null) { try { //解析mybatis-Config.xml文件,並將相關配置信息保存到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); } } if (this.transactionFactory == null) { //事務默認採用SpringManagedTransaction,這一塊很是重要,我將在後買你單獨寫一篇文章講解Mybatis和Spring事務的關係
     this.transactionFactory = new SpringManagedTransactionFactory(); } // 爲sqlSessionFactory綁定事務管理器和數據源 // 這樣sqlSessionFactory在建立sqlSession的時候能夠經過該事務管理器獲取jdbc鏈接,從而執行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對象實例做爲參數, // 調用sqlSessionFactoryBuilder建立sqlSessionFactory對象實例
   return this.sqlSessionFactoryBuilder.build(configuration); }

buildSqlSessionFactory的核心邏輯:解析mybatis配置文件mybatisConfig.xml和mapper配置文件mapper.xml並封裝到Configuration對象中,最後調用mybatis的sqlSessionFactoryBuilder來建立SqlSessionFactory對象。這一點至關於前面介紹的原生的mybatis的初始化過程。另外,當配置中未指定事務時,mybatis-spring默認採用SpringManagedTransaction,這一點很是重要,請你們先在內心作好準備。此時SqlSessionFactory已經建立好了,而且賦值到了SqlSessionFactoryBean的sqlSessionFactory屬性中。

FactoryBean的getObject方法定義

FactoryBean:建立某個類的對象實例的工廠。

spring的IOC容器在啓動,建立好bean對象實例後,會檢查這個bean對象是否實現了FactoryBean接口,若是是,則調用該bean對象的getObject方法,在getObject方法中實現建立並返回實際須要的bean對象實例,而後將該實際須要的bean對象實例註冊到spring容器;若是不是則直接將該bean對象實例註冊到spring容器。

SqlSessionFactoryBean的getObject方法實現以下:因爲spring在建立SqlSessionFactoryBean自身的bean對象時,已經調用了InitializingBean的afterPropertiesSet方法建立了sqlSessionFactory對象,故能夠直接返回sqlSessionFactory對象給spring的IOC容器,從而完成sqlSessionFactory的bean對象的註冊,以後能夠直接在應用代碼注入或者spring在建立其餘bean對象時,依賴注入sqlSessionFactory對象。

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

總結

由以上分析可知,spring在加載建立SqlSessionFactoryBean的bean對象實例時,調用SqlSessionFactoryBean的afterPropertiesSet方法完成了sqlSessionFactory對象實例的建立;在將SqlSessionFactoryBean對象實例註冊到spring的IOC容器時,發現SqlSessionFactoryBean實現了FactoryBean接口,故不是SqlSessionFactoryBean對象實例自身須要註冊到spring的IOC容器,而是SqlSessionFactoryBean的getObject方法的返回值對應的對象須要註冊到spring的IOC容器,而這個返回值就是SqlSessionFactory對象,故完成了將sqlSessionFactory對象實例註冊到spring的IOC容器。建立Mapper的代理對象咱們下一篇文章再講

 

 

 

原文出處:https://www.cnblogs.com/java-chen-hao/p/11833780.html

相關文章
相關標籤/搜索