原生Mybatis源码简析(上)

1、概述

目前工作中,直接使用mybatis原生API开发的场景很少,基本都是结合spring一起使用。但对于分析mybatis的源码来说,使用API的方式能更容易的理清思路。先介绍下原生API的使用方式。

public static void main(String[] args) {
    String resource = "configuration.xml";
    Reader reader;
    try {
        reader = Resources.getResourceAsReader(resource);
        SqlSessionFactory sqlSessionFactory = new  SqlSessionFactoryBuilder().build(reader);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
            Role role = roleMapper.getRole(1);
            System.out.println(role.getLfPartyId() + "," + role.getPartyName());
        } finally {
            session.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

其中比较重要的类有SqlSessionFactoryBuilderSqlSessionFactorySqlSessionRoleMapper。下面先简要介绍下他们的使用范围与作用。

  • SqlSessionFactoryBuilder
    SqlSessionFactoryBuilder是典型的建造者模式的应用,一般他的生命周期就是存在于方法中,主要作用就是根据mybatis的全局配置文件(这里不对mybatis的配置文件做过多介绍,比较简单)构建出SqlSessionFactory对象,同时会建立好Configuration对象(该对象及其重要,贯穿整个mybatis的生命周期,后面会重点介绍)。
  • SqlSessionFactory
    SqlSessionFactory是典型的工厂模式,用于生成SqlSession对象,一般全局单例。SqlSessionFactory是个接口,有两个实现类DefaultSqlSessionFactorySqlSessionManager,系统默认使用DefaultSqlSessionFactory
  • SqlSession
    SqlSession可以简单理解为JDBC的connect对象,执行SQL方法(具体其实是Executor对象执行的sql方法,具体后面会分析),生命周期也是方法级别。SqlSession也是个接口,有两个实现类DefaultSqlSessionSqlSessionManager,系统默认使用DefaultSqlSession
  • RoleMapper
    RoleMapper接口是一整个Mapper接口的代表,它没有实现类,作用是发送SQL(帮助SqlSession找到SQL),生命周期与SqlSession一致。

总结:从上面简短的介绍就可以看出mybatis框架设计的巧妙,充分体现了依赖倒置原则(面向接口编程)。

2、从初始化开始说起

从上面概述的原生API的使用代码片段可以看出,mybatis的初始化,是从构建SqlSessionFactory对象开始的,那我们就进入到SqlSessionFactoryBuilder内部,看看SqlSessionFactory对象以及重要的Configuration对象是如何被构建出来的。

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        return build(parser.parse());
    } catch (Exception e) {
        ....
    }
}

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

首先解析XML文件构建出Configuration对象,然后将该对象设置为DefaultSqlSessionFactory对象的属性后返回。这里可以很明显的看出,系统确实默认是使用的DefaultSqlSessionFactory对象。这里先分析下Configuration对象是如何构建的,然后再分析DefaultSqlSessionFactory对象的细节。

2.0 Configuration概述

在介绍Configuration对象是如何构建之前,我们先简单介绍下Configuration对象的作用。简单理解,Configuration对象其实就是mybatis的XML配置文件对应的Java类表示形式(类似的有tomact的XML配置文件和spring的XML配置文件,他们都是会解析成对应的Java类)。在SqlSessionFactoryBuilder类内部会解析XML文件,相应的节点都会被解析成Configuration的相关属性。主要有如下的属性解析过程:

  1. properties全局参数
  2. settings设置
  3. typeAliases别名
  4. typeHandler类型处理器
  5. ObjectFactory对象(很少使用)
  6. plugin插件
  7. environment环境(与spring结合后,由spring来管理环境的切换)
  8. DatabaseIdProvider数据库标识(很少使用)
  9. Mapper映射器

其中我们分析的重点是第9步,后面会详细分析,前面的8步比较简单,在后面稍微提及。

2.1 Configuration构建过程

在上面的代码中,我们已经看到,Configuration对象是通过XMLConfigBuilder对象构建而成的,分析下该对象的初始化及其parse方法的具体实现,即可明白Configuration对象的构建过程。

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
}

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}

我们发现直接调用了new Configuration()方法,实例化了Configuration对象,然后在parse方法中通过parseConfiguration方法解析"/configuration"节点来实现对Configuration对象的属性填充。再看看该方法的实现

private void parseConfiguration(XNode root) {
    try {
        propertiesElement(root.evalNode("properties"));
        Properties settings = settingsAsProperties(root.evalNode("settings"));
        loadCustomVfs(settings);
        loadCustomLogImpl(settings);
        // Configuration类中有typeAliasRegistry属性,是对HashMap的简单封装,里面存储alias->Class的关联关系
        typeAliasesElement(root.evalNode("typeAliases"));
        // Configuration类中有InterceptorChain属性,是对ArrayList的简单封装,里面存储所有的插件实体对象,内部有个pluginAll方法,通过层层包装的方式来实现插件的调用
        pluginElement(root.evalNode("plugins"));
        objectFactoryElement(root.evalNode("objectFactory"));
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        reflectorFactoryElement(root.evalNode("reflectorFactory"));
        settingsElement(settings);
        // read it after objectFactory and objectWrapperFactory issue #631
        environmentsElement(root.evalNode("environments"));
        databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        // Configuration类中有typeHandlerRegistry属性,内部有多个HashMap的属性,里面存储JDBC type到Java type的类型转换规则
        typeHandlerElement(root.evalNode("typeHandlers"));
        // Configuration类中有mapperRegistry属性,是对HashMap的简单封装,里面存储Mapper接口到`MapperProxyFactory`的映射关系
        // 解析的同时也会向mappedStatements属性添加值,该属性也是对HashMap的简单封装,里面存储namespace+id 与MappedStatement对象的映射关系,后者就是mapper.xml文件中的select、insert、update、delete等SQL语句的节点。
        mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

代码都做了相关注释,内部实现也都不是很复杂(需要说明的是在settingsElement(settings);方法中,会根据配置的defaultExecutorType属性设置Executor的类型,默认是Simple,configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));),
其次一个较为复杂且最为重要的就是mapperElement(root.evalNode("mappers"));方法,即是我们上面说的到第9步。我们看看他的实现。

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
                String mapperPackage = child.getStringAttribute("name");
                configuration.addMappers(mapperPackage);
            } else {
                // resource、url、class三个属性不可同时存在
                String resource = child.getStringAttribute("resource");
                String url = child.getStringAttribute("url");
                String mapperClass = child.getStringAttribute("class");
                if (resource != null && url == null && mapperClass == null) {
                    ErrorContext.instance().resource(resource);
                    InputStream inputStream = Resources.getResourceAsStream(resource);
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                    mapperParser.parse();
                } else if (resource == null && url != null && mapperClass == null) {
                    ErrorContext.instance().resource(url);
                    InputStream inputStream = Resources.getUrlAsStream(url);
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                    mapperParser.parse();
                } else if (resource == null && url == null && mapperClass != null) {
                    Class mapperInterface = Resources.classForName(mapperClass);
                    configuration.addMapper(mapperInterface);
                } else {
                    throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                }
            }
        }
    }
}

目前大部分都是使用package的方式来批量导入mapper的XML文件,所以我们重点分析他,其他的导入方式在底层原理是类似的。重点是这段代码configuration.addMappers(mapperPackage); ,它最终会调用到MapperRegistry类的addMapper方法中。

public  void addMapper(Class type) {
    if (type.isInterface()) {
        // knownMappers.containsKey判断是否存在
        if (hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
            // 绑定mapper接口与代理类工厂的映射关系,这是mapper接口能直接使用的关键
            // 这里mapper接口的代理类还没实例化,只是绑定了工厂类,代理类的实例化在sqlSession.getMapper()方法中实例化
            knownMappers.put(type, new MapperProxyFactory<>(type));
            // 下面两行代码会具体实现上文提到了mappedStatements属性填充
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
              knownMappers.remove(type);
            }
        }
    }
}

再看MapperAnnotationBuilder类的parse方法

public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
        // 解析mapper的XML文件 
        loadXmlResource();
        // 下面是解析mapper接口中的注解信息,这种方式对代码的侵入性比较大,现阶段很少使用,基本可以不用关注
        configuration.addLoadedResource(resource);
        assistant.setCurrentNamespace(type.getName());
        parseCache();
        parseCacheRef();
        Method[] methods = type.getMethods();
        for (Method method : methods) {
            try {
                if (!method.isBridge()) {
                    parseStatement(method);
                }
            } catch (IncompleteElementException e) {
                configuration.addIncompleteMethod(new MethodResolver(this, method));
            }
        }
    }
    parsePendingMethods();
}

loadXmlResource();方法会调用到XMLMapperBuilder类的parse()方法,

public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
        configurationElement(parser.evalNode("/mapper"));
        configuration.addLoadedResource(resource);
        bindMapperForNamespace();
    }
    // 解析resultMap
    parsePendingResultMaps();
    // 解析cache
    parsePendingCacheRefs();
    // 解析SQL语句
    parsePendingStatements();
}

在该方法里,我们重点关注parsePendingStatements(),在其内部会调用XMLStatementBuilder类的parseStatementNode()方法

public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
        return;
    }

    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String parameterType = context.getStringAttribute("parameterType");
    Class parameterTypeClass = resolveClass(parameterType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultType = context.getStringAttribute("resultType");
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    Class resultTypeClass = resolveClass(resultType);
    String resultSetType = context.getStringAttribute("resultSetType");
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

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

    // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // Parse the SQL (pre:  and  were parsed and removed)
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    String resultSets = context.getStringAttribute("resultSets");
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
        keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
        keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
            configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
            ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

这个方法就是重头戏了,解析mapper的XML文件,将XML中的select、delete、update、insert等节点解析成MappedStatement对象,并缓存到Configure类中。其中不同的节点会匹配不同的statementType,SQL语句会封装到SqlSource对象中,默认是DynamicSqlSource实现类。
至此整个configuration对象的初始化过程中的重要阶段就介绍完毕了。configuration对象在整个mybatis运行过程中的每个阶段都有它的影子,这是一个非常重的对象。对其中的一些比较重要的属性的理解,直接关系到对整个mybatis的运行机制的理解。所以这里再简单总结下configuration对象中比较重要的一些属性。

// 记录Mapper接口与代理工厂类的绑定关系
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
// 责任链模式,记录所有的插件
protected final InterceptorChain interceptorChain = new InterceptorChain();
// 记录所有的类型转换器
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
// 记录所有的类型简称
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
// 记录所有的namespace+id与MappedStatement的绑定关系,其中MappedStatement内部关联具体的SQL语句
protected final Map mappedStatements = new StrictMap("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());

2.2 SqlSessionFactory简介

在之前的分析中,我们已经知道,SqlSessionFactoryBuilder对象,默认是实例化了DefaultSqlSessionFactory对象,我们先看下该对象的初始化过程

private final Configuration configuration;

public DefaultSqlSessionFactory(Configuration configuration) {
    this.configuration = configuration;
}

很简单,DefaultSqlSessionFactory对象内部持有Configuration对象的引用,初始化过程就是完成该属性的复制。下面再分析下其获取SqlSession对象的过程

@Override
public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        final Environment environment = configuration.getEnvironment();
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        final Executor executor = configuration.newExecutor(tx, execType);
        return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
        closeTransaction(tx); // may have fetched a connection so lets call close()
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

这里我们可以看到,mybatis默认确实是使用的DefaultSqlSession实现类。其中ExecutorType在上面已经介绍过,默认使用的Simple。SqlSession对象持有两个比较重要的对象的引用,ConfigurationExecutorConfiguration我们在前面已经介绍过了,Executor我们在后面再详细介绍。
SqlSession就简单介绍到这里,具体的使用原理,在下面介绍Mapper接口的时候再详细说明。

3、Mapper接口的调用过程

我们知道在Java中,接口方法是没法调用的,在Mybatis中却很奇怪的可以调用,且能正常运行。这其中是什么原理呢?
这其实就是代理在其中作祟了。我们从概述介绍的代码片段中的RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);代码行开始,介绍Mapper接口的调用过程。我们知道,Mybatis默认是使用DefaultSqlSession,所以我们就从该类的getMapper方法入手。

// DefaultSqlSession.class
public  T getMapper(Class type) {
    return configuration.getMapper(type, this);
}

// Configuration.class
public  T getMapper(Class type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
}

// MapperRegistry.class
public  T getMapper(Class type, SqlSession sqlSession) {
    final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}
protected T newInstance(MapperProxy mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

如上代码可以看出,DefaultSqlSession的getMapper方法,通过层层委托,最终到了MapperRegistry类的getMapper方法,该类中有个knownMappers属性,在上面介绍初始化过程中已经介绍过,里面存储着type接口与MapperProxyFactory类的映射关系。最终我们得知,getMapper方法返回了type的JDK动态代理对象。其中MapperProxy类实现了InvocationHandler接口,我们在后面调用接口方法时,其实最终都会调用该类的invoke方法(其实就是最简单的JDK动态代理技术,这里就不详细介绍了)。我们就看下该方法的具体实现吧

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        // Object类中的方法,比如wait、notify等不做代理,直接调用
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else if (isDefaultMethod(method)) { // JDK8中接口有默认方法实现,也不做代理,直接调用
            return invokeDefaultMethod(proxy, method, args);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
    // 对method和MapperMethod关联值做了缓存
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}

其中,MapperMethod对象有两个内部类,这里得介绍下:

  • SqlCommand 根据传入的configurationmapperInterfacemethod可以获取该method对象具体需要执行的sql语句。这里就用到了上面初始化过程中介绍的configuration类的一个重要属性mappedStatements(内部保存了namespace+id-->MappedStatement的映射关系)
public static class SqlCommand {

    private final String name;
    private final SqlCommandType type;

    public SqlCommand(Configuration configuration, Class mapperInterface, Method method) {
      final String methodName = method.getName();
      final Class declaringClass = method.getDeclaringClass();
      MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
          configuration);
      if (ms == null) {
        if(method.getAnnotation(Flush.class) != null){
          name = null;
          type = SqlCommandType.FLUSH;
        } else {
          throw new BindingException("Invalid bound statement (not found): "
              + mapperInterface.getName() + "." + methodName);
        }
      } else {
        name = ms.getId();
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
        }
      }
    }

    public String getName() {
      return name;
    }

    public SqlCommandType getType() {
      return type;
    }

    private MappedStatement resolveMappedStatement(Class mapperInterface, String methodName,
        Class declaringClass, Configuration configuration) {
      String statementId = mapperInterface.getName() + "." + methodName;
      if (configuration.hasStatement(statementId)) {
        return configuration.getMappedStatement(statementId);
      } else if (mapperInterface.equals(declaringClass)) {
        return null;
      }
      for (Class superInterface : mapperInterface.getInterfaces()) {
        if (declaringClass.isAssignableFrom(superInterface)) {
          MappedStatement ms = resolveMappedStatement(superInterface, methodName,
              declaringClass, configuration);
          if (ms != null) {
            return ms;
          }
        }
      }
      return null;
    }
  }
  • MethodSignature 根据传入的configurationmapperInterfacemethod可以获取该方法的返回值类型,参数解析器等信息,这里就不详细分析。
    如此我们知道,Mapper接口的方法调用,最终会执行MapperMethod对象的execute方法中。
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
        case SELECT:
            if (method.returnsVoid() && method.hasResultHandler()) {
                executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (method.returnsMany()) {
                // 1
                result = executeForMany(sqlSession, args);
            } else if (method.returnsMap()) {
                result = executeForMap(sqlSession, args);
            } else if (method.returnsCursor()) {
                result = executeForCursor(sqlSession, args);
            } else {
                // 2
                Object param = method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(command.getName(), param);
                if (method.returnsOptional() &&
                    (result == null || !method.getReturnType().equals(result.getClass()))) {
                    result = Optional.ofNullable(result);
                }
            }
            break;
         ...
        default:
            throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
        throw new BindingException("Mapper method '" + command.getName()
            + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
}

方法有点长,我们这里就以select类型作为分析对象。首页根据方法的返回值类型决定具体的实现方式,其中标注的1和2分别表示返回数组和返回普通对象(这里不是很严谨),这两个分支是调用频率最高的,我们这里以1位代表,分析下执行的过程。其中executeForMany方法最终会调用到DefaultSqlSession类的selectList方法中,如下:

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

MappedStatement对象包含有具体需要执行的SQL语句,executor对象是在DefaultSqlSessionFactory调用openSession方法时构建成功的,默认是SimpleExecutor对象(目前大部分的项目不会开启Mybatis的二级缓存,否则这里默认会是CachingExecutor对象,其实就是对SimpleExecutor对象的装饰),并注入到DefaultSqlSession对象的属性中。(这里的executor对象已经是被插件代理过后的对象了)。

4、总结

对于初始化的过程,就不做总结了。比较复杂的过程就是configuration对象的构建比较复杂,因为这个对象太重了,但是在使用的过程中,我们基本是感知不到这个对象的存在的。这也是写一个框架并能让大多数人能接受这个框架的需要注意的点,将复杂留给自己,将简单给与用户。
这里主要总结下Mapper接口具体是如何运行并能执行SQL拿到数据的过程。

  1. 调用SqlSession的getMapper方法
  2. 委托给MapperRegistry对象的getMapper方法,通过type拿到MapperProxyFactory对象,然后该对象会返回一个MapperProxy对象,作为Mapper接口的代理类。
  3. 调用Mapper接口的方法,会知道到MapperProxy对象的invoke方法中,该方法会委托给MapperMethod对象的execute方法
  4. MapperMethod对象的execute方法会根据MapperMethod对象的command属性的type值决定决定SQL的执行类型。这里已select为例,分析executeForMany方法。
  5. executeForMany方法会再委托给SqlSession的selectList方法,且传入command属性的name值即Mapper接口名+方法名。
  6. SqlSession的selectList方法会委托给executor类的query方法去执行具体的SQL

SQL的具体执行又涉及到ExecutorStatementHandlerResultSetHandlerParameterHandler四个重要的类,下次与mybatis的插件机制一起分析。

你可能感兴趣的:(原生Mybatis源码简析(上))