Mybatis源码分析

1、mybatis原始工作代码

public static void main(String[] args) throws IOException {
        //1、读取配置文件
        InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2、创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(is);
        //3、使用工厂生产对象
        SqlSession session = factory.openSession();
        //4、使用session对象创建Dao接口的代理对象
        IUserDao userDao = session.getMapper(IUserDao.class);
        //5、使用代理对象执行方法
        List users = userDao.findAll();
        for (User user : users){
            System.out.println(user);
        }
        //6、释放资源
        session.close();
        is.close();

    }

可以看的出来

  1. 第一步读取一下配置文件
  2. 第二部就是用sqlsession来读取配置文件。
  3. 使用工厂生产对象
  4. 根据IUserDao的接口来创建代理对象,这个代理对象是不用你来写实现类的。之后直接使用即可。




    
        
            
            
                
                
                
                
            
            
        
    


    
        
    

这里有我的xml配置文件。

2、读取配置文件的源码分析

//1、读取配置文件
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
//2、创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();        
SqlSessionFactory factory = builder.build(is);

我们进入getResourceAsStream,看看是如何读取xml文件的。

/**
   * Returns a resource on the classpath as a Stream object
   *
   * @param resource The resource to find
   * @return The resource
   * @throws java.io.IOException If the resource cannot be found or read
   */
  public static InputStream getResourceAsStream(String resource) throws IOException {
    return getResourceAsStream(null, resource);
  }
  

可以从注解中看出来,返回的是一个流对象,而这个流对象其实就是你传入的xml文件,这个原理不是我们分析mybatis的重点,

因此我们就一笔带过,我们只需要知道,通过这个方法可以返回一个流对象,而这个流对象下一步就是要作为一个参数传递进sqlSessionFactory

public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
 }

进入重载方法

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        //用XMLConfigBuilder这个类解析这个文件
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
       /**
        这里buildbuild(parser.parse())调用的是
        public SqlSessionFactory build(Configuration config) {
            return new DefaultSqlSessionFactory(config);
        }
        **/
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
我们发现返回的是SqlsessionFactory的对象,而这个对象又是根据build方法的,而build方法传入的参数是一个XMLConfigBuilder类型的参数。
因此我们需要首先研究如何传入这个参数,以及如何创建这个SqlsessionFactory的对象。
    
    

我们来看看XMLConfigBuilder这个类如何创建

循序渐进的进入调用的函数
//我们传进去了inputStream,其他两个参数都是null,这个是构造函数
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}

//进入这个函数,可以发现这个函数里进行了两个操作,一个是进行常规的构建,另一个是创建了一个文档。
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = createDocument(new InputSource(inputStream));
}

//这是一些变量的初始化 
/** validation(是否开启验证标记)
    entityResolver (加载本地的DTD文件)
    XPath (XPath对象)
    variables(配置参数集合)
**/
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
    this.validation = validation;
    this.entityResolver = entityResolver;
    this.variables = variables;
    XPathFactory factory = XPathFactory.newInstance();
    this.xpath = factory.newXPath();
}

接下去,我们就要进入最关键的一步,看他是如何根据我们的输入流来创建出文档的。

其实也就是看createDocument函数

//在源码中我们也可以看见,他给予了我们一个注释,这个函数必须要在commonconstructor之后运行,也就是我们之前说的那个变量初始化函数。
private Document createDocument(InputSource inputSource) {
    // important: this must only be called AFTER common constructor
    try {
      //这里先创建一个对象,在此对象里构建属性
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      //带有set的方法很明显就是设置属性值了,大家可以根据英文来明白意思,例如setIgnoringComments的意思就是是否忽略注释。
      factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
      factory.setValidating(validation);
      factory.setNamespaceAware(false);
      factory.setIgnoringComments(true);
      factory.setIgnoringElementContentWhitespace(false);
      factory.setCoalescing(false);
      factory.setExpandEntityReferences(true);
        
      //当工厂的属性都搞定后,就根据这个工厂创建一个DocumentBuilder的对象
      DocumentBuilder builder = factory.newDocumentBuilder();
      //这里也是进行两个设置,一个是EntityResolver 解析要解析的 XML 文档中存在的实体。将其设置为 null 将会导致底层实现使用其自身的默认实现和行为。
      //另一个是设置句柄,关于这两个函数,大家都可以点开这两个函数的抽象类,上面的文档用英文注释讲清楚了其的作用。
      builder.setEntityResolver(entityResolver);
      builder.setErrorHandler(new ErrorHandler() {
        @Override
        public void error(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void fatalError(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void warning(SAXParseException exception) throws SAXException {
          // NOP
        }
      });
      //最后根据这个builder会依据你的输入流返回一个Ducument文件,这里显然也是用了工厂模式,不需要你去new,而是builder帮你自动的申请空间。
      return builder.parse(inputSource);
    } catch (Exception e) {
      throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    }
  }

讲了这么多的函数,是不是有点绕晕了,其实我们还没讲到正题,我们还得清楚,我们目的究竟是什么?

我们的目的其实就是要去解析XML文档,而我们已经将其转化为了Document文档了,接下去就要进入另外一个函数了。

我们已经将这个XMLConfigBuilder对象给创建出来了,其实我们也知道这个对象里面有什么东西了。接着我们就要进入这个build(parser.parse())了。

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
     return build(parser.parse());

/**进入这个函数,注意返回的类型就是SqlSessionFactory,就是我们要用的sqlSession工厂,我们来看看它是怎么创建出来的。
    我们惊讶的发现传递进去的参数是Configuration类型的参数,那我们明白了,parser.parse()返回的是一个config类型的参数,
    我们首先要看看它是怎么创建出来的。
**/
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

/**
    我们首先进入parser.parse()函数,我们可以看见一开始它会判断parsed这个变量是否为true,其实就是看是否分析过你这个内容了,
    一开始应该是没有,我们立马变为true,之后进行分析,进入parseConfiguration函数。在此之前还要进入parser.evalNode()这个函数。
**/
public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
//可以看到我们之前分析了很久的文档在这里排上了用处,传递了进去,我们也进入重载方法一探究竟。
public XNode evalNode(String expression) {
    return evalNode(document, expression);
  }
//我们需要注意,这里的root是xml转化成的doc文档,expression是/configuration,我们需要进入evaluate来得到一个Node,然后通过这个Node初始化为一个Xnode,最后我们再返回这个Xnode即可。我们看文档可以知道Node其实就是<>

public XNode evalNode(Object root, String expression) {
    Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
    if (node == null) {
      return null;
    }
    return new XNode(this, node, variables);
  }

//我们进入实现类
//-Override-
    public Object evaluate(String expression, Object item, QName returnType)
            throws XPathExpressionException {
        //this check is necessary before calling eval to maintain binary compatibility
        //验证我们的expression是否为空,我们的参数是/configuration
        requireNonNull(expression, "XPath expression");
        //验证是否支持返回的类型,这里我们的参数是Node
        isSupported(returnType);

        try {

            XObject resultObject = eval(expression, item);
            return getResultAsType(resultObject, returnType);
        } catch (java.lang.NullPointerException npe) {
            // If VariableResolver returns null Or if we get
            // NullPointerException at this stage for some other reason
            // then we have to reurn XPathException
            throw new XPathExpressionException (npe);
        } catch (TransformerException te) {
            Throwable nestedException = te.getException();
            if (nestedException instanceof javax.xml.xpath.XPathFunctionException) {
                throw (javax.xml.xpath.XPathFunctionException)nestedException;
            } else {
                // For any other exceptions we need to throw
                // XPathExpressionException (as per spec)
                throw new XPathExpressionException (te);
            }
        }

    }
//我们继续深入探索eval()
/**
     * Evaluate an {@code XPath} expression in the specified context.
     * @param expression The XPath expression.
     * @param contextItem The starting context.
     * @return an XObject as the result of evaluating the expression
     * @throws TransformerException if evaluating fails
     */
//在注解里我们可以看得很清晰,这个函数通过调用了Xpath的构造函数,创建了该类型
    private XObject eval(String expression, Object contextItem)
        throws TransformerException {
        requireNonNull(expression, "XPath expression");
        com.sun.org.apache.xpath.internal.XPath xpath = new com.sun.org.apache.xpath.internal.XPath(expression,
            null, prefixResolver, com.sun.org.apache.xpath.internal.XPath.SELECT);

        return eval(contextItem, xpath);
    }
//因此我们可以理解这一系列得到的结果其实就是通过"/configuration"来得到xml文件里以为标签的内容。
parseConfiguration(parser.evalNode("/configuration"));//这一步就是分析以节点里的内容。

/**我们这个函数,然而就是最关键的一个函数了,这个函数就是解析xml文件的核心部分了,我们就不一一进入各个函数了,
    但是顺着我们刚刚理的脉络,我们能够知道各个函数都在对xml的相关标签进行解析,例如第一个函数propertiesElement,
    就是提取出xml文件中properties里的内容,然后对configuration类的属性进行设置。
    
**/
private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      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"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

当以上配置完成后就可以返回Config对象了,进入下面这个函数,以config为参数,新建一个SqlSession工厂,看名称就知道是默认的。

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

我们进入这个构造方法,这里我只截取了一小部分,大家也能看得见在这个类里面有个私有变量Configuration,我们之前传入的参数就等于这个私有变量,

这样之后我们就能用的到它了。直到这一步,我们的工厂也已经新建好了。步骤看似很复杂,但其实剖析一遍就容易理解了。

public class DefaultSqlSessionFactory implements SqlSessionFactory {

  private final Configuration configuration;

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

[图片上传失败...(image-36069f-1596118699454)]

经过以上的分析,再看这张图是不是就豁然开朗了,这是在调试中的数据信息,我们可以看见工厂就是有那么一个configuration对象,而这个对象里储存的就是xml的信息,我们的信息大部分都储存在了environment里面,里面的变量都经过了解析得到了结果,这就是mybatis读取配置文件的方式。

3、SqlSession的分析

mybatis读取完了xml,返回了一个工厂,我们就要用这个工厂来进行sql语句的执行了。

//3、使用工厂生产对象
SqlSession session = factory.openSession();
//4、使用session对象创建Dao接口的代理对象
IUserDao userDao = session.getMapper(IUserDao.class);

我们进入openSession()方法

@Override
public SqlSession openSession() {
    //getDefaultExecutorType()传入的是一个Simply类型,是一个枚举的执行类型。
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
//进入这个函数,注意我们传入的参数分别是执行类型是Simply,事务隔离级别是null,自动提交是否
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);
      //一开始的executor只是一个简单类型的,但是现在是根据我们的configuration里的信息新建的一个执行器,将其作为参数,新建了一个sqlSession。
      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();
    }
  }

理解了上面的代码,我们就知道openSession()的目的其实就是将工厂里的configuration信息传递到了SqlSession对象里,这也是工厂模式的运用,

我们不再自己new Sqlsession,而是让工厂帮我们new这个对象,我们直接拿来用就可以了。参数什么的都不需要我们自己来传递,我们只需要传递

xml文件,简单的调用几个方法即可完成这一系列复杂的操作。

接着我们进入getMapper方法,我们可以看见,返回的类型是我们传进去的类型。

@Override
public  T getMapper(Class type) {
  return configuration.getMapper(type, this);
}

继续进入方法,这里我们会发现有一个mapperProxyFactory,这个东西得到的值其实是dao.IUserDao,也就是我们提供的接口,

要注意的是我们使用mapper接口,我们只需要提供SQL语句,而不需要使用提供实现类,原因就是mybatis使用了动态代理,

从这个工厂名字就能看得出,这一步就是动态代理。

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

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);
    }
  }

直接看上述代码是有点难度的,我们需要先了解一下我们处在的是什么类,怎么样读取的sql语句,也就是mapper文件的内容。

还记得我们之前分析configuration里的以下代码吗?我们没有细细的分析每一个节点的处理,只是讲了个大概,我们现在准备细细讲讲这个mapper节点的处理,

因为我们最关键的sql语句就是在mapper里。

private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      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"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

我们找到这个函数 mapperElement(root.evalNode("mappers")) ,显然这个函数就是解析mapper节点的。

我们进入这个函数。

/** parent 是mappers,以下是我的mapper内容,我们会发现
    resource = "dao/IUserDao.xml" ,
    child = 
    
        
    
    因此我们应该进入第一个else里的第一个if。
**/
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 {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
            //进入这里
          if (resource != null && url == null && mapperClass == null) {
            //这就是把resource作为参数保存为私有变量
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
              //重点就在这里,我们看看如何分析mapper文件的
            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.");
          }
        }
      }
    }
    //进入mapperParser.parse()
    public class XMLMapperBuilder extends BaseBuilder {

      private final XPathParser parser;
      private final MapperBuilderAssistant builderAssistant;
      private final Map sqlFragments;
      private final String resource;
       
       public void parse() {
        if (!configuration.isResourceLoaded(resource)) {
          configurationElement(parser.evalNode("/mapper"));
          configuration.addLoadedResource(resource);
          bindMapperForNamespace();
        }

        parsePendingResultMaps();
        parsePendingCacheRefs();
        parsePendingStatements();
        }
    }

我们发现configuration在这里解析mapper标签,首先看下我的mapper标签的内容


    

我们进入这个函数,可以很明显发现我们需要解析哪些内容。

private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      //这个是参数,我的这个mapper没有,但是我们能发现他在这里将参数存了起来。
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      //这个是返回的类型,他会将User给储存起来,这里我也没有统一的用resultMap,而是都在select里
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      //重点来了,解析select标签的内容
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }
//进入最重要的函数,之后进入重载函数。
private void buildStatementFromContext(List list) {
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
  }
// 这个函数的作用就是,新建一个XMLStatementBuilder对象,将解析的sql语句都保存在这个对象里,最后把这个对象保存到了configuration里。
private void buildStatementFromContext(List list, String requiredDatabaseId) {
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context,         requiredDatabaseId);
      try {
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

注意!!!重点来了

经过以上的分析,我们可以知道 statementParser.parseStatementNode() 就是解析sql语句的函数。

我们来看看他是如何解析的

public void parseStatementNode() {
    //我们的id 是 findall
  String id = context.getStringAttribute("id");
    //我们mapper里没有这个
  String databaseId = context.getStringAttribute("databaseId");

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

  String nodeName = context.getNode().getNodeName();
  // 这里SqlCommandType 有UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH这几种命令,显然就是对数据库的操作类型,我们解析出来的是select
  SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  //这里isSelect为true
  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());
  //这里得到parameter,我们的比较简单没有
  String parameterType = context.getStringAttribute("parameterType");
  Class parameterTypeClass = resolveClass(parameterType);

  String lang = context.getStringAttribute("lang");
  LanguageDriver langDriver = getLanguageDriver(lang);

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

  // Parse the SQL (pre:  and  were parsed and removed)
  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;
  }

  SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  //从这里开始,把所有解析出来的数据全部保存到临时变量里。
  Integer fetchSize = context.getIntAttribute("fetchSize");
  Integer timeout = context.getIntAttribute("timeout");
  String parameterMap = context.getStringAttribute("parameterMap");
  String resultType = context.getStringAttribute("resultType");
  Class resultTypeClass = resolveClass(resultType);
  String resultMap = context.getStringAttribute("resultMap");
  String resultSetType = context.getStringAttribute("resultSetType");
  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  if (resultSetTypeEnum == null) {
    resultSetTypeEnum = configuration.getDefaultResultSetType();
  }
  String keyProperty = context.getStringAttribute("keyProperty");
  String keyColumn = context.getStringAttribute("keyColumn");
  String resultSets = context.getStringAttribute("resultSets");
  //最后把所有的临时变量全部都储存到MappedStatement对象里。
  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
      resultSetTypeEnum, flushCache, useCache, resultOrdered,
      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

好的,以上是回顾完了如何解析xml文件,接下去我们继续看这个函数,第一个函数我们已经讲得极为透彻了,我们进入其中的第三个函数

public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }
//出现了!出现了configuration.addMapper(boundType)
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) {
        if (!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);
        }
      }
    }
  }
public  void addMapper(Class type) {
    mapperRegistry.addMapper(type);
  }
//我们总算进入了这个函数
//namespace就是我们提供的接口类,我们不需要提供实现类。
//我们再次进入parse()函数
  public  void addMapper(Class type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        //我们注意到,knowMappers就是储存type和代理对象工厂的,type就是xml中的namespace。
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
//这里就是读取那个接口类的信息,这里用的就是反射。不用创建这个类的对象,却可以一览无遗类的信息最后将所有的信息都储存到我们的config里面
public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }

OK,那倒现在,所有的先决条件都已经讲清楚了,这时候再看getMapper(),思路就清晰多了。

public  T getMapper(Class type, SqlSession sqlSession) {
  return mapperRegistry.getMapper(type, sqlSession);
}
//进入重载
public  T getMapper(Class type, SqlSession sqlSession) {
    //这里就是把type给取出来了强制转化为了代理工厂
    final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
       //返回的就是我们IuserDao的实现类,这里我们需要注意,看到sqlSession,就是最一开始的openSession的对象。
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

其实讲到这里,getMapper已经讲完了,接下去就是如何调用API了,但其实我们发现分析了那么久源码没有分析sqlSession,这是因为我们没有进行

sql语句的操作就不会进入sqlSession的源码,为了之后的理解更加方便,我们直接在这里分析SqlSession

进入一个select函数

//函数很简单,就是利用的selcetList查,如果结果大于1就是错的,或者0就是错的,我们进入selectList
@Override
  public  T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List list = this.selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }
//继续进入重载函数
@Override
  public  List selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }
//来了来了!这里我们从configure里得到了MappedStatment,就可以进行查询操作了,可以回顾之前的分析里有写Statment里面有哪些内容。
  @Override
  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();
    }
  }

//那我们就进入query来研究一下,当搜索executor的时候惊讶的发现,只有cacheExecutor在mybatis里,那意思就是说执行查询会优先进入缓存中,看是否命中
@Override
  public  List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
     //boundSql是一个类,打开后会发现里面有Sql语句与参数作为私有变量。
    BoundSql boundSql = ms.getBoundSql(parameterObject);
     //创建缓存key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
//继续进入重载函数
  @Override
  public  List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
      //在缓存里就执行if,一开始显然不在缓存里,那就执行else
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List list = (List) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    //执行这一步
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
//因此进入这个函数
@SuppressWarnings("unchecked")
  @Override
  public  List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List list;
    try {
      queryStack++;
      list = resultHandler == null ? (List) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //由于我们没有在缓存中命中,因此我们去数据库里进行查询,走这步。
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }
//进入这个查询数据库里的函数
private  List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List list;
    //缓存其实就是类似于一个哈希表,这里因为要从数据库里查询结果,当查完结果这里会放到缓存里,而这里先加的原因是之后删去就不用再判断是否为空了。
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      //重点!!!这里就是做一个真正的查询了。
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
        //这就是为什么要一开始执行putObject,这样就不用判断是否存在了,必定会存在这个key,因为在之前就加入过。
      localCache.removeObject(key);
    }
    //真正的把数据存入缓存中。
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

激动人心的时刻要来临了!解析了半天的源码总算到了真正做查询操作的时候了,真可谓是千呼万唤始出来。

@Override
public  List 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(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    //这里明显就是将参数封装到了stmt里
    stmt = prepareStatement(handler, ms.getStatementLog());
    //这里就是返回值,那就是将结果写入list里
    return handler.query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}

我们先研究prepareStatement这个函数

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    //有没有发现这个Connection很眼熟,我们在自己做JDBC时候也有个Connection,这里的getConnection其实就是类似于工具类的一个封装,做了很多步操作,但是都给封装了起来,最后返回的connection就是连接数据库的connection
    Connection connection = getConnection(statementLog);
    //把stmt给写出来
    stmt = handler.prepare(connection, transaction.getTimeout());
    //这里进行设置参数
    handler.parameterize(stmt);
    return stmt;
  }
//进入重载函数
@Override
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }
//这里就是将参数插入到sql语句里
@Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

到此为止,查询的sql语句已经全部完成,那接下去就是做真正的查询操作了。

@Override
public  List query(Statement statement, ResultHandler resultHandler) throws SQLException {
  return delegate.query(statement, resultHandler);
}
//多么熟悉的代码啊,这不就是我们JDBC最初学的代码吗?原来即使是mybatis这么高端的技术也是合抱之木始于毫末,万丈高楼起于垒土。
 @Override
  public  List query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
      //最后返回包装的结果
    return resultSetHandler.handleResultSets(ps);
  }

OK!如此一来,mybatis的代码也大致清晰了,只差最后一步了,那就是mapper接口的动态代理。

4、mybatis中的动态代理

我们回到这个函数,这是在MapperRegistry里的函数,而之前已经说过

MapperRegistry这个类里有2个关键元素,一个是configuration,一个是knownMappers,这个是个哈希表储存的是namespace与动态代理工厂。

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);
    }
  }

//进入mapperProxyFactory.newInstance(sqlSession);
public T newInstance(SqlSession sqlSession) {
    final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
//进入这个方法,这个方法就是真正的动态代理,返回实现类。
@SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

我们已经得到了那个接口类的方法,并且我们在使用mybatis的时候没有为接口做实现类,但是我们现在直接就可以用这个方法了。

我们来看看是怎么办到的。

List users = userDao.findAll();
//打断点在这个地方,然后单点进入。直接会跳到这个函数,这明显是一个调用反射的函数,说明这个invoke函数会帮我们进行操作。
@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }
//执行这个方法
@Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      return mapperMethod.execute(sqlSession, args);
    }
//经过一系列的跳转,这里不作演示主要是因为判断的代码量太多了,我们会发现selectList函数出来了!!这就是我们之前分析的sqlSession里执行查询的函数呀。
private  Object executeForMany(SqlSession sqlSession, Object[] args) {
    List result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

到此为止,mybatis的源码分析也差不多了,整个顺序还是有点乱的,希望大家在阅读的时候能够打开idea一步一步跟着debug来看源码。

你可能感兴趣的:(Mybatis源码分析)