Spring和Mybatis的整合使用及源码解析

简介
       之前分析过Spring的源码,里面涉及到很多Spring的拓展接口,具体的介绍文字在http://blog.csdn.net/lgq2626/article/details/78729368
文章中,文章中介绍了InitializingBean这个接口的是在依赖注入完成之后调用了此接口中的afterPropertiesSet()方法,具体的几个重要的接口方法的执行简单总结下:
首先大致看代码:

populateBean(beanName, mbd, instanceWrapper);//实例化
if (exposedObject != null) {
                exposedObject = initializeBean(beanName, exposedObject, mbd);//处理了BeanPostProcessor实现了接口类的调用 和 init-method配置和InitializingBean实现了接口类的调用
            }

在initializeBean(beanName, exposedObject, mbd)方法中,有几个重要的方法的调用,大致执行顺序为: 1.BeanPostProcessor#postProcessBeforeInitialization()方法
2.InitializingBean#afterPropertiesSet()方法
3.配置文件中init-method()方法
4.BeanPostProcessor#postProcessAfterInitialization()方法

下面开始介绍Mybitis和分析源码:

1.Mybatis在Spring中的使用

Spring和mybatis的整合的pom.xml部分内容如下:

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.2.1</version>
        </dependency>
         <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.2.2</version>
         </dependency>
         <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.41</version>
        </dependency>

Spring和Mybatis整合的配置文件如下:

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">  
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />  
        <property name="url" value="jdbc:mysql://localhost:3306/test" />  
        <property name="username" value="xxx"/>  
        <property name="password" value="xxx"/>  
    </bean>  
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
        <property name="dataSource" ref="dataSource"/>  
    </bean>  

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

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.study.www.dao" />
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    </bean>

有了以上的配置在代码中就可以这样使用mybatis了:

@autowired
private TestDao  testDao;

2.Mybitis源码分析

2.1Mybatis读取配置文件并且解析配置文件(SqlSessionFactoryBean类解读)

从上面的配置文件中可以获得在Spring容器中注册了两个bean,一个是SqlSessionFactoryBean Bean,一个是MapperScannerConfigurer Bean。
我们首先分析SqlSessionFactoryBean 源码:
在buildSqlSessionFactory()方法中,
我们看到了有如下重要代码:

configuration = new Configuration();//Mybatis的大管家,所有的信息都会放在里面,包括plugins插件, environments环境...
 configuration.addInterceptor(plugin);//Mybatis插件
  Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);//Mybatis环境
  configuration.setEnvironment(environment);
   XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              configuration, mapperLocation.toString(), configuration.getSqlFragments());
          xmlMapperBuilder.parse();//解析xml,下面会重点分析

2.1.1 在parse()方法中,我们首先分析configurationElement方法

 configurationElement(parser.evalNode("/mapper"));
private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");//得到namespace
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));//处理<resultMap>这类标签,然后再MapperBuilderAssistant类的addResultMap()方法中把每个ResultMap对象加到Configuration对象中的resultMaps属性中(中间会把<resultMap>标签中的每一个子标签封装成ResultMapping对象,然后封装成ResultMap对象,最后put到Configuration对象中,id规则为:namespace+<resultMap>的id属性 )
      sqlElement(context.evalNodes("/mapper/sql"));//解析文件中的<sql>标签
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));//解析每条sql语句,会在2.1.2分析
    } catch (Exception e) {
      throw new RuntimeException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }

2.1.2分析buildStatementFromContext方法

buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

 public void parseStatementNode() {
    String id = context.getStringAttribute("id");
      //中间省略很多获取属性的代码...
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;//是否是select查询


    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes,
    // in case if IncompleteElementException (issue #291)
    List<XNode> selectKeyNodes = context.evalNodes("selectKey");
    if (configuration.getDatabaseId() != null) {
      parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());//解析<selectKey>标签
    }
 ...
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);//解析sql下面会在2.1.3分析

    ...
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver);//把MappedStatement对象装到Configuration对象的mappedStatements属性中id同样是namespace+标签id
  }

2.1.3 下面分析langDriver.createSqlSource();

 SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
public SqlSource parseScriptNode() {
    //解析动态标签比如if、foreach...每个动态标签都会有不同的handel去处理,如果动态标签嵌套动态标签的话,还会递归去调用parseDynamicTags(XNode node)方法,每一个标签封装成一个sqlNode
    List<SqlNode> contents = parseDynamicTags(context);
    //封装一个MixedSqlNode对象,有一个apply方法,用户解析动态sql
    MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
    SqlSource sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    return sqlSource;
  }

到这里整个configurationElement(XNode context)方法就分析完了。

2.1.4 在parse()方法中,我们在分析bindMapperForNamespace()方法

我们接着回到parse()方法中看 bindMapperForNamespace();方法,这个方法最重要的解析就是把每个Mapper对象也就是Interface接口放到了configuration对象中
knownMappers.put(type, new MapperProxyFactory(type));
其中key为接口对象的class,value为MapperProxyFactory对象 。

——–到这里,SqlSessionFactoryBean类中重要的方法基本分析完了,若遗漏某个重点,还望大神指出来—–

下面开始MapperScannerConfigurer源码

2.2Mybatis读取配置文件并且解析配置文件(MapperScannerConfigurer类解读)

我们直接看postProcessBeanDefinitionRegistry()方法的 代码块

 scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));

2.2.1 doScan()方法解析

解析doScan我们只解析到使用类似@Autowired注解处理过的接口是什么样的对象为止,其他的就是执行时候处理的东西了

Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);//直接调用Spring的东西,递归扫描所有的文件,封装成beandefinition对象,并且把beanName放到DefaultListableBeanFactory容器的beanDefinitionNames属性中,这个比较简单
 definition.setBeanClass(MapperFactoryBean.class);//设置beanDefinition的class
  definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);//把sqlSessionTemplate属性设置为SqlSessionTemplate对象

到目前为止,我们知道DefaultListableBeanFactory中beanDefinitionMap属性所有key为Mybatis扫描接口的class均为MapperFactoryBean对象,也就是说在依赖注入的时候会反射出一个MapperFactoryBean对象,但是MapperFactoryBean对象,但是MapperFactoryBean实现了FactoryBean接口,那么在IOC的时候就会调用到getObject()方法,在getObject()方法中SqlSession的属性是
SqlSessionTemplate对象(注意:SqlSessionTemplate类的构造方法中把sqlSessionProxy属性设置了代理类,这也是我纠结了好久的一个东西,最后发现在这里进行了代理)。
一直顺着getObject()方法跟进去发现最后getObject()返回的是一个对接口类进行代理的代理对象,实现InvocationHandler接口的对象为MapperProxy对象(这一点也可以从代码debug的时候发现)
《Spring和Mybatis的整合使用及源码解析》
到目前为止,整个@Autowired注解的类进行IOC的时候得到的对象就完全明确了,下面2.3会分析执行过程

2.2.2 扫描注册注解类解析

这里主要是解析registerAnnotationConfigProcessors()方法,在这个方法中,会注册到容器中一些直接注解的类例如:AutowiredAnnotationBeanPostProcessor类支持@Autowired注解和@value注解…,如果使用Spring的话,一般在解析<context:component-scan>标签的时候就会解析了,这里一般不会执行到。

2.3Mybitis调用时候的执行过程源码解读

上面说到@autowired得到的一个对象就是一个接口的代理对象,并且代理类为MapperProxy,那么就从Mapperproxy类的invoke作为入口,

2.3.1 MapperProxy invoke()方法解析

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } final MapperMethod mapperMethod = cachedMapperMethod(method);//把接口类,方法,和Configuration封装成MapperMethod对象
    return mapperMethod.execute(sqlSession, args);//下面重点分析
  }

下面会以selectList查询,重点分析(我们只看类似selectList查询)mapperMethod.execute(sqlSession, args);
一直跟进去,跟到executeForMany()方法,发现

 result = sqlSession.<E>selectList(command.getName(), param);

这个地方相当饶了,不了解动态代理的就别看下去了,首先来说,这个sqlSession对象是SqlSessionTemplate对象,点进去之后发现

return this.sqlSessionProxy.<E> selectList(statement, parameter);//上文分析过这个sqlSessionProxy是代理对象,代理类是SqlSessionInterceptor

我们只能看SqlSessionInterceptor的invoke方法了,
SqlSessionInterceptor#invoke方法主要分为三块:
1.得到session:

SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);

在getSession方法中,有一个openSession调用,这里走的是一个DefaultSqlSessionFactory()对象,因为在解析SqlSessionFactoryBean的时候就返回的是一个DefaultSqlSessionFactory对象,然后这个openSession比较重要的东西就是configuration.newExecutor(tx, execType, autoCommit);方法,这个Executor是mybatis的执行器,
executor有四种:

    SimpleExecutor普通执行器,
    ReuseExecutor处理预编译语句执行器
    BatchExecutor批量执行器
    CachingExecutor缓存执行器

这里newExecutor方法中还有一个重要的配合用户拓展的代interceptorChain.pluginAll(executor);可以实现Interceptor接口,然后封装插件,比如分页插件。这个天getSession()返回一个DefaultSqlSession对象,也就是说这次的sqlSession是DefaultSqlSession的session。到这里,就算是得到了一个sqlSession

2.处理sql并且执行sql

Object result = method.invoke(sqlSession, args);

这个invoke方法会调到DefaultSqlSession的方法,我们跟进selectList()方法,首先会从Configuration得到一个MappedStatement对象,
然后回调用一个wrapCollection();方法。

private Object wrapCollection(final Object object) {
    if (object instanceof List) {
      StrictMap<Object> map = new StrictMap<Object>();
      map.put("list", object);//如果是list的话封装为一个key为list,,value为object的map
      return map;
    } else if (object != null && object.getClass().isArray()) {
      StrictMap<Object> map = new StrictMap<Object>();
      map.put("array", object);//解析数组
      return map;
    }
    return object;
  }

然后会通过执行器CachingExecutor执行。

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);//得到sqlbound
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

这个getBoundsql 解析sql有四种解析器
《Spring和Mybatis的整合使用及源码解析》

这里解析得到了select * from user where id = ?这种sql。
然后执行query方法,会执行到执行器SimpleExecutor.query(),也就是BaseExecutor的query方法中来,

if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);//会走到BaseExecutor的queryFromDatabase方法中去调用simpleexecutor的doQuery方法下面会贴出代码
      }

会一直走到simpleExecutor类的doQuery方法,到这个时候熟悉的jdbc就要来了,

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);//获取一个PreparedStatementHandler对象
      stmt = prepareStatement(handler, ms.getStatementLog());//获取一个jdbc连接,从连接获取一个PreparedStatement对象,并且set值,下面有贴出代码 
      return handler.<E>query(stmt, resultHandler);//最终会调用到PreparedStatementHandler类的query
    } finally {
      closeStatement(stmt);
    }
  }


 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);//获取jdbc连接对象
    stmt = handler.prepare(connection);//获取一个PreparedStatement对象
    handler.parameterize(stmt);//对占位符?进行set值
    return stmt;
  }

PreparedStatementHandler类的query代码如下

 public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);//通过FastResultSetHandler的handleResultSets进行返回值装配
  }

到这里整个SqlSessionInterceptor的invoke方法中的 method.invoke(sqlSession, args);就执行完了。

3.关闭session

closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);

到这里整个Spring源码执行流程就已经完了,中间还有好多东西没有分析到,其中包括:jdbcType和javaType的对应,解析${id}这种表达式 …等等。希望能帮到你们,如果其中有什么理解的不到位的地方,还望大神指出,共同学习,进步

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/lgq2626/article/details/78891496
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞