转载

小撕Mybatis(二)

Mybtais是一种ORM框架,用于实现面向对象编程语言里不同类型系统的数据之间的转换。

本文我将从三个步骤进行Mybatis解读。

小撕Mybatis(二)

Mybatis如何获取执行sql

现在把我们的目光转会一开始的mybatis-config.xml中执行sql语句的xml文件。

<mappers> 
  <mapper resource="org/mybatis/example/BlogMapper.xml"/> 
  </mappers> 
复制代码

这时候有个面试官问你,mappers加载mapper文件有几种方式?

这几种哪个优先级最高?

哈哈,不知道了吧,现在由我来告诉你

<mappers> 
    <!-- 使用相对于类路径的资源引用 -->
  <mapper resource="org/mybatis/example/BlogMapper.xml"/> 
    <!-- 使用完全限定资源定位符(URL) -->
     <mapper url="file:///var/mappers/BlogMapper.xml"/>
    <!-- 使用映射器接口实现类的完全限定类名 -->
    <mapper class="org.mybatis.builder.BlogMapper"/>
    <!-- 将包内的映射器接口实现全部注册为映射器 -->
    <package name="org.mybatis.builder"/>
  </mappers> 
复制代码

我主要解释第三种。第三种的映射器接口实现类是这样的

// 定义接口映射器
public interface TestMapper {
    // 通过MyBatis的注解在Java接口方法上编写SQL语句
    @Select("select * from test where id = #{id}")
    Test selectOneTest(long id);
}
复制代码

我在文章开头也说过了,本文的Mybatis配置只是独立使用MyBatis,不是在Spring框架中集成MyBatis。(下图集成Spring)

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <!-- 定义接口映射器所在的Java包 -->
    <property name="basePackage" value="org.chench.test.mybatis.mapper.impl"/>
</bean>
复制代码

哪个优先级最高,我们可以查看parseConfiguration()这个方法调用的mapperElement(root.evalNode("mappers"));(注:前文代码已给出)

小撕Mybatis(二)

从而可以看出顺序为name、resource、url、class。

解决掉这个面试题之后,我们可以接着看上图的代码,在resource != null的条件下,进入parse()

//解析
  public void parse() {
    //如果没有加载过再加载,防止重复加载
    if (!configuration.isResourceLoaded(resource)) {
      //配置mapper
      configurationElement(parser.evalNode("/mapper"));
      //标记一下,已经加载过了
      configuration.addLoadedResource(resource);
      //绑定映射器到namespace
      bindMapperForNamespace();
    }

    //还有没解析完的东东这里接着解析?  
    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
  }
复制代码

发现关键字mapper,进入configurationElement()

private void configurationElement(XNode context) {
    try {
      //1.配置namespace
      String namespace = context.getStringAttribute("namespace");
      if (namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      //2.配置cache-ref
      cacheRefElement(context.evalNode("cache-ref"));
      //3.配置cache
      cacheElement(context.evalNode("cache"));
      //4.配置parameterMap(已经废弃,老式风格的参数映射)
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      //5.配置resultMap(高级功能)
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      //6.配置sql(定义可重用的 SQL 代码段)
      sqlElement(context.evalNodes("/mapper/sql"));
      //7.配置select|insert|update|delete TODO
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }
复制代码

context显示为

<mapper namespace="org.mybatis.example.BlogMapper">
	  <select id="selectBlog" parameterType="int" resultType="Blog">
	    select * from Blog where id = #{id}
	  </select>
	</mapper>
复制代码

接下来就是解析sql语句的内容

buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
复制代码
/*  解析语句(select|insert|update|delete)
<select
  id="selectPerson"
  parameterType="int"
  parameterMap="deprecated"
  resultType="hashmap"
  resultMap="personResultMap"
  flushCache="false"
  useCache="true"
  timeout="10000"
  fetchSize="256"
  statementType="PREPARED"
  resultSetType="FORWARD_ONLY">
  SELECT * FROM PERSON WHERE ID = #{id}*/
public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");
    ...
    ..
    .
    }
复制代码
小撕Mybatis(二)

到这里mybatis就已经将sql语句解析完成了,接下来就需要进行封装。

//又去调助手类
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
复制代码

进入addMappedStatement()中调用的addMappedStatement(),发现其所在的类为Configuration,这个类应该是个全局的配置类,里面还包括我之前提到的Environment。

这时候又有问题了?最佳实践中,通常个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么? Dao接口里的方法,参数不同时,方法能重载吗?

这时候我们进入addMappedStatement()方法,发现MappedStatement这个类对应着映射的语句,包含SQL源码。由此我们可以解答上面的问题。

答: Dao接口,就是人们常说的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中Mappedstatement的id值,接口方法内的参数,就是传递给sql的参数。Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可确定Mappedstatement,例: com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到 namespace为com.mybatis3.mappers.studentDao下面id = findstudentById的MappedStatement。

在Mybatis中,每一个select、insert、update、delete标签,都会被解析为一个Mappedstatement对象。Dao接口里的方法是不能重载的,因为是全限名+方法名的保存和寻找策略。 Dao接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回。

在解析mapper的过程中,有mapperProxyFactory这个代理工厂,生成代理类的实例。其作用是生成了DAO层的实例。这也就是为什么DAO层的接口,能够直接调用方法的原因了——其实不是接口调用方法,而是它的代理类调用方法。

此时就拿到了sql语句。

一篇放不下传送门

原文  https://juejin.im/post/5e7b6edbf265da42e31a671d
正文到此结束
Loading...