结合前面的 MyBatis Demo,其核心代码如下

//xml配置文件路径
String resource = "mybatis-config.xml";
//读取配置
InputStream inputStream = Resources.getResourceAsStream(resource);
//构建 SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过 SqlSessionfactory 获取 SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//通过 SqlSession 获取 Mapper 接口
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);


配置文件

<?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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://172.31.32.184:3306/constxiong?serverTimezone=UTC&amp;useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="constxiong@123"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="constxiong/mapper/UserMapper.xml"/>
    </mappers>
</configuration>


这种是配置解析 xml 文件的方式,通过 SqlSessionFactoryBuilder 构建 SqlSessionFactory 实例,从 SqlSession 中获取 UserMapper 接口。

从源码角度看,大致流程


1、通过流获取 xml 文件,构建 SqlSessionFactory 对象

  • 使用 XPath 库解析 xml 配置文件

  • 其中包含 mappers、mapper 标签的解析,获取 mapper 标签的 source 参数

  • 使用 XMLMapperBuilder 对象解析根据 resource 参数定位的 Mapper xml 文件,包含 namespace、cache-ref、* cache、parameterMap、resultMap、sql、select、insert、update、delete 标签的解析以及它们节点上的参数

  • 在 configuration 对象中注册 namespace 及其对应 Mapper 接口 Class 对象的代理工厂对象 MapperProxyFactory

  • Mapper 接口中的方法上的注解解析,转换为对应的 Statement 与 ResultMap


2、通过 SqlSession 对象获取 UserMapper 的代理对象 MapperProxy

  • configuration 对象中 mapperRegistry 获取对应的 MapperProxyFactory

  • MapperProxyFactory 对象实例化 Mapper 接口的代理对象 MapperProxy 


3、执行 UserMapper 的接口方法时,调用代理对象 MapperProxy 的 invoke 方法,执行相应的 SQL

  • MapperProxy 的 invoke 方法,构建了 PlainMethodInvoker 对象

  • 调用 PlainMethodInvoker 对象的 invoke 方法,委托给 MapperMethod 属性的 execute 方法执行相应的 SQL,返回结果


还有一种通过硬编码的方式,构建 Configuration 对象,注册 UserMapper 接口,Demo 中也给出示例了,总体流程差别不大。


回到之前列出的第一个问题


Mapper 接口如何与写 SQL 的 XML 文件进行绑定的?

先给出答案:

  • Mapper 接口与 XML 文件的绑定是通过 XML 里 mapper 标签的 namespace 值与 Mapper 接口的 包路径.接口名 进行绑定

  • Mapper 接口的方法名与 XML 文件中的 sql、select、insert、update、delete 标签的 id 参数值进行绑定

  • 其中涉及到了 MappedStatement 的 id、SqlCommand 的 name 的值为 Mapper 接口的 包路径.接口名.方法名



详细分析


要点 1、Mapper 接口与 XML 文件的绑定是通过 XML 里 mapper 标签的 namespace 值与 Mapper 接口的 包路径.接口名 进行绑定


源码体现在 XMLMapperBuilder 的 bindMapperForNamespace 方法

private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
        Class<?> boundType = null;
        try {
            boundType = Resources.classForName(namespace);
        } catch (ClassNotFoundException e) {
            // ignore, bound type is not required
        }
        if (boundType != null && !configuration.hasMapper(boundType)) {
            // Spring may not know the real resource name so we set a flag
            // to prevent loading again this resource from the mapper interface
            // look at MapperAnnotationBuilder#loadXmlResource
            configuration.addLoadedResource("namespace:" + namespace);
            configuration.addMapper(boundType);
        }
    }
}




要点 2、Mapper 接口的方法名与 XML 文件中的 sql、select、insert、update、delete 标签的 id 参数值进行绑定


源码体现在两个部分1)生成 id 与 MappedStatement 对象注册到 configurationXMLMapperBuilder configurationElement 方法中

//sql标签
sqlElement(context.evalNodes("/mapper/sql"));
//selectinsertupdatedelete标签
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

XMLMapperBuilder sqlElement 方法中
String id = context.getStringAttribute("id");
id = builderAssistant.applyCurrentNamespace(idfalse);
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
    sqlFragments.put(id, context);
}

XMLStatementBuilder parseStatementNode 方法中
//获取 Mapper xml 中标签 id
String id = context.getStringAttribute("id");
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
    fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
    resultSetTypeEnum, flushCache, useCache, resultOrdered,
    keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);

MapperBuilderAssistant addMappedStatement 方法中
id = applyCurrentNamespace(id, false);

MapperBuilderAssistant applyCurrentNamespace 方法中
return currentNamespace + "." + base;

MapperBuilderAssistant addMappedStatement 方法中,最后把 MappedStatement 注册到 configuration 对象中
configuration.addMappedStatement(statement);


2)根据 Mapper 接口方法查到并调用对应的 MappedStatement,完成绑定MapperProxy cachedInvoker 方法创建 PlainMethodInvoker 对象,创建了 MapperMethod 对象
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));

MapperMethod 对象的 SqlCommand 中的 name 属性根据解析设置为对应的 MappedStatement 的 id
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,configuration);
name = ms.getId();


MapperMethod execute 方法 SqlCommand 类型,通过 sqlSession 根据 SqlCommand 的 name(上一步被设置为 对应的 MappedStatement 的 id) 找到 MappedStatement 执行 select、insert、update、delete

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        .
        .
        .
    return result;
  }
}


更多相关文章

  1. 详解第三种创建线程的方式-Callable接口
  2. Spring Ioc 实例化 Bean 对象有几种方式?
  3. Javascript面向对象入门
  4. 【编测编学】接口测试面试题必背(下)
  5. 如何在 Java 中构造对象(学习 Java 编程语言 034)
  6. 快速测试 API 接口的新技能
  7. java创建对象的过程(内存角度分析)

随机推荐

  1. Typeahead 0.10.2没有在Rails 4 / Bootst
  2. 告诉javascript首先运行jquery ajax
  3. GET ajax请求发送到同一个php文件
  4. 无法使用jquery发送简单的ajax请求来获取
  5. 解析PHP的基本文本输出
  6. 利用jQuery实现CheckBox全选/全不选/反选
  7. jquery中的globalEval()源码分析
  8. 从jQuery每个循环中删除item [i]
  9. Jquery 特效 图片轮转 菜单
  10. jQuery学习21---简单动画效果,show,hide,