原生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 <T> void addMapper(Class<T> 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: <selectKey> and <include> 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<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("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> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
}

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

// MapperRegistry.class
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) 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<T> 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 <E> List<E> 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的插件机制一起分析。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 我的以前是个男人,紧抓着棍子,象征着力量的棍子,试图控制别人,掌控一切,用强大力量武装自己,保护自己,其实什么都控...
    兮兮AX阅读 83评论 0 1
  • 1、移植wvdial ,移植pppd /usr/sbin/pppd 2、wvdial【1.6.1版本】 ,pppd...
    咖喱鸡蛋阅读 1,327评论 0 0
  • 文/稳心山人图/稳心山人 临近教师节,高中班群里,博士在读的前数学科代表,放出了不少珍贵的“一手史料”,有智力大赛...
    稳心山人阅读 259评论 2 3
  • 第一次听说滚包货,网上搜了下,真可怕。衣服也不能只图好看。 外贸入港时被水压机压缩成的1立方米大小的货包,每包有2...
    桑杰卓玛阅读 1,153评论 0 0