mybatis在spring容器中加載過程解析

從配置文件(spring-mybatis.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"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.1.xsd


    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">
    
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
    destroy-method="close" >
     <property name="driverClassName">
      <value>${jdbc_driverClassName}</value>
    </property>
    <property name="url">
      <value>${jdbc_url}</value>
    </property>
    <property name="username">
      <value>${jdbc_username}</value>
    </property>
    <property name="password">
      <value>${jdbc_password}</value>
    </property>
    <!-- 連接池最大使用連接數 -->
    <property name="maxActive">
      <value>${max_active}</value>
    </property>
    <!-- 初始化連接大小 -->
    <property name="initialSize">
      <value>${initial_size}</value>
    </property>
    <!-- 獲取連接最大等待時間 -->
    <property name="maxWait">
      <value>${max_wait}</value>
    </property>
    <!-- 連接池最大空閒 
    <property name="maxIdle">
      <value>20</value>
    </property>-->
    <!-- 連接池最小空閒 -->
    <property name="minIdle">
      <value>${min_idle}</value>
    </property>
    <!-- 自動清除無用連接 -->
    <property name="removeAbandoned">
      <value>${remove_abandoned}</value>
    </property>
    <!-- 清除無用連接的等待時間 -->
    <property name="removeAbandonedTimeout">
      <value>${remove_abandoned_timeout}</value>
    </property>
    <!-- 連接屬性 -->
    <property name="connectionProperties">
      <value>${connection_properties}</value>
    </property>
     <!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒 -->
       <property name="timeBetweenEvictionRunsMillis">
	  <value>${time_between_eviction_runs_millis}</value>
    </property>
      <!-- 配置一個連接在池中最小生存的時間,單位是毫秒 -->
	<property name="minEvictableIdleTimeMillis">
	  <value>${min_evictable_idle_time_millis}</value>
    </property>
      <!-- 申請連接的時候檢測 -->
    <property name="testWhileIdle">
	  <value>${test_while_idle}</value>
    </property>
     <!-- 用來檢測連接是否有效的sql,要求是一個查詢語句-->
    <property name="validationQuery">
	  <value>${validation_query}</value>
    </property>
    <property name="filters">
		<value>stat,wall</value>
	</property>
  </bean>
     
    <!-- mybatis文件配置,掃描所有mapper文件 -->
      <bean id="sqlSessionFactory"
          class="org.mybatis.spring.SqlSessionFactoryBean"
          p:dataSource-ref="dataSource"
          p:configLocation="classpath:mybatis-config.xml"
          p:mapperLocations="classpath*:com/**/mapper/*.xml"/>
          <!-- configLocation爲mybatis屬性 mapperLocations爲所有mapper-->
       <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
          <constructor-arg index="0" ref="sqlSessionFactory" />
       <!-- <constructor-arg index="1" value="BATCH" /> 如果想要進行批量操作可加入這個屬性 --> 
 	   </bean> 
   		<!-- spring與mybatis整合配置,掃描所有dao -->
 		<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"
        	p:basePackage="*.dao"
	        p:sqlSessionFactoryBeanName="sqlSessionFactory"/>
 		
   <!-- 對數據源進行事務管理 -->
  <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource"/>
  <tx:annotation-driven transaction-manager="transactionManager" />  
</beans>

SqlSessionFactoryBean

SqlSessionFactoryBean是一個實現了FactoryBean接口的工廠bean,在獲取bean實例時會調用getObject方法,同時實現了InitializingBean,在spring容器實例化該工廠bean後會調用afterPropertiesSet方法
  1. afterPropertiesSet()
//spring容器實例化SqlSessionFactoryBean後調用afterPropertiesSet創建sqlSessionFactory
this.sqlSessionFactory = this.buildSqlSessionFactory();
  1. buildSqlSessionFactory
    首先解析配置文件中的configLocation="classpath:mybatis-config.xml
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
            configuration = xmlConfigBuilder.getConfiguration();

然後解析配置文件中的mapperLocations="classpath*:com/**/mapper/*.xml

XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments());
                        xmlMapperBuilder.parse();

xmlMapperBuilder.parse()中的configurationElement方法追蹤下去,最後到MapperBuilderAssistant的
addMappedStatement,它會將mapper.xml中每個sql語句維護到Configuration的Map<String, MappedStatement> mappedStatements中,其中key是mapper.xml中sql語句的id也是Dao中的方法名

this.configuration.addMappedStatement(statement);

xmlMapperBuilder.parse()中的bindMapperForNamespace

private void bindMapperForNamespace() {
        String namespace = this.builderAssistant.getCurrentNamespace();
        if (namespace != null) {
            Class boundType = null;

            try {
                boundType = Resources.classForName(namespace);
            } catch (ClassNotFoundException var4) {
            }

            if (boundType != null && !this.configuration.hasMapper(boundType)) {
                this.configuration.addLoadedResource("namespace:" + namespace);
                this.configuration.addMapper(boundType);
            }
        }

    }

configuration.addMapper()

public <T> void addMapper(Class<T> type) {
        this.mapperRegistry.addMapper(type);
    }

Configuration的MapperRegistry維護了一個Dao和其代理對象工廠的map:knownMappers

public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            if (this.hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }

            boolean loadCompleted = false;

            try {
                this.knownMappers.put(type, new MapperProxyFactory(type));
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    this.knownMappers.remove(type);
                }

            }
        }

MapperScannerConfigurer

MapperScannerConfigurer實現了BeanDefinitionRegistryPostProcessor接口,在spring容器註冊BeanDefinition的時候會自動調用postProcessBeanDefinitionRegistry方法

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        if (this.processPropertyPlaceHolders) {
            this.processPropertyPlaceHolders();
        }

        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        scanner.setAddToConfig(this.addToConfig);
        scanner.setAnnotationClass(this.annotationClass);
        scanner.setMarkerInterface(this.markerInterface);
        scanner.setSqlSessionFactory(this.sqlSessionFactory);
        scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
        scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
        scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
        scanner.setResourceLoader(this.applicationContext);
        scanner.setBeanNameGenerator(this.nameGenerator);
        scanner.registerFilters();
        scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
    }

最後調用ClassMapperScanner的doScan方法將basePackages下的Dao接口封裝成beanDefinations

public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
        if (beanDefinitions.isEmpty()) {
            this.logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
        } else {
            this.processBeanDefinitions(beanDefinitions);
        }

        return beanDefinitions;
    }

上一步的processBeanDefinitions方法中將beanDefination的BeanClass(原始值是Dao)設置成MapperFactoryBean,最後spring將beanDefination實例化成MapperFactoryBean

 definition.setBeanClass(this.mapperFactoryBean.getClass());

MapperFactoryBean集成DaoSupport實現了InitializingBean,在spring實例化MapperFactoryBean的時後會調用afterPropertiesSet()方法,該方法調用checkDaoConfig方法

public abstract class DaoSupport implements InitializingBean {
    protected final Log logger = LogFactory.getLog(this.getClass());

    public DaoSupport() {
    }

    public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
        this.checkDaoConfig();

        try {
            this.initDao();
        } catch (Exception var2) {
            throw new BeanInitializationException("Initialization of DAO failed", var2);
        }
    }

    protected abstract void checkDaoConfig() throws IllegalArgumentException;

    protected void initDao() throws Exception {
    }
}

MapperProxyFactory的checkDaoConfig方法中生成最終的Dao代理對象工廠MapperProxyFactory(Dao)註冊到DefaultSqlSession.Configuration.MapperRegistry.knownMappers中

protected void checkDaoConfig() {
        super.checkDaoConfig();
        Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required");
        Configuration configuration = this.getSqlSession().getConfiguration();
        if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
            try {
                configuration.addMapper(this.mapperInterface);
            } catch (Exception var6) {
                this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6);
                throw new IllegalArgumentException(var6);
            } finally {
                ErrorContext.instance().reset();
            }
        }

    }
public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            if (this.hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }

            boolean loadCompleted = false;

            try {
                this.knownMappers.put(type, new MapperProxyFactory(type));
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    this.knownMappers.remove(type);
                }

            }
        }

    }

SqlSessionTemplate

當調用Dao的方法查詢數據庫時,實際是MapperProxy代理對象執行

MapperProxy

MapperProxy是JDK的自動代理,實現了InvocationHandler接口,實現了invoke方法,當被代理的目標類是接口時執行mapperMehtod.execute

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            try {
                return method.invoke(this, args);
            } catch (Throwable var5) {
                throw ExceptionUtil.unwrapThrowable(var5);
            }
        } else {
            MapperMethod mapperMethod = this.cachedMapperMethod(method);
            return mapperMethod.execute(this.sqlSession, args);
        }
    }

excucte方法中調用sqlSession的方法,而SqlSessionTemplate正是sqlSession的實現類

 public Object execute(SqlSession sqlSession, Object[] args) {
        Object param;
        Object result;
        if (SqlCommandType.INSERT == this.command.getType()) {
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
        } else if (SqlCommandType.UPDATE == this.command.getType()) {
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
        } else if (SqlCommandType.DELETE == this.command.getType()) {
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
        } else if (SqlCommandType.SELECT == this.command.getType()) {
            if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if (this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } else {
                param = this.method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(this.command.getName(), param);
            }
        }

以DefaultSqlSession的selectOne爲例:

public <T> T selectOne(String statement, Object parameter) {
        List<T> list = this.selectList(statement, parameter);
        if (list.size() == 1) {
            return list.get(0);
        } else if (list.size() > 1) {
            throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
        } else {
            return null;
        }
    }
public <E> List<E> selectList(String statement, Object parameter) {
        return this.selectList(statement, parameter, RowBounds.DEFAULT);
    }

    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        List var5;
        try {
            MappedStatement ms = this.configuration.getMappedStatement(statement);
            var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception var9) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var9, var9);
        } finally {
            ErrorContext.instance().reset();
        }

        return var5;
    }

this.configuration.getMapperStatement(statement)就是從第一步初始化SqlSesissonFactoryBean時的Map(Configuration的Map<String, MappedStatement> mappedStatements,其中key是mapper.xml中sql語句的id也是Dao中的方法名)中取出MappedStatement對象,然後執行query方法

Mybatsi源碼走讀到這裏就沒往下看了,這裏的var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
this.executor是CacheExecutor對象,另起一篇走讀MyBatis的二級緩存原理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章