Mybatis源码解析

一 Mybatis具体的执行流程

在这里插入图片描述

1 Resources获取全局配置文件

首先需要定义好文具配置文件,例如mybatis.xml。
这一步实在工具类中实现的 getResourceAsStream:

    static{
        String config = "mybatis.xml";
        try {
            InputStream inputStream = Resources.getResourceAsStream(config);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

进入源码:

这里的classloader传的是null
在这里插入图片描述
注意这个warpper的返回值,后面返回的就是我们全局配置文件的位置
在这里插入图片描述

这里的 ClassLoaderWrapper 是一个类,用于包装对多个类加载器的访问,使它们作为一个整体工作。

在这里插入图片描述
注意这里有一个 getClassLoaders方法,它获取到了所有的classloader,最后用的应该是 systemClassLoader(源码没看懂)

  ClassLoader[] getClassLoaders(ClassLoader classLoader) {
    return new ClassLoader[]{
        classLoader,   // null
        defaultClassLoader // null
        Thread.currentThread().getContextClassLoader(),
        getClass().getClassLoader(),
        systemClassLoader};
  }

这里的systemClassLoader不是一个类加载器,只是ClassLoaderWrapper的一个字段

  ClassLoader defaultClassLoader;
  ClassLoader systemClassLoader;

如图,systemClassLoader不为空
在这里插入图片描述
systemClassLoader详细信息如下,合理怀疑是URLClassLoader
在这里插入图片描述

使用上一步拿到的加载器进行加载在这里,注意这一行代码:

InputStream returnValue = cl.getResourceAsStream(resource);

我们看到returnvalue的值已经是拿到path了
在这里插入图片描述

在这里插入图片描述
我们总会陷入一个误区,returnvalue返回的是绝对路径,就以为在方法里面解析了绝对路径,其实不是的。

Resources.getResourceAsStream 方法并没有直接将传入的 mybatis.xml 字符串转换成文件的绝对路径。相反,它利用了 Java 的类加载机制来查找并加载资源。
在 Java 中,资源(如配置文件、图片等)可以通过类加载器(ClassLoader)来加载。当你调用 Resources.getResourceAsStream(“mybatis.xml”) 时,实际上是在调用 ClassLoader 的 getResourceAsStream 方法(间接地,因为 Resources 类内部会这么做)。
ClassLoader 的 getResourceAsStream 方法会按照以下步骤查找资源:
1.类路径(Classpath)查找:它会首先查找由系统属性 java.class.path(即类路径)指定的目录、JAR 文件和 ZIP 文件中是否存在名为 mybatis.xml 的资源。这里的资源名是不包含路径的,意味着它会查找类路径根目录下的资源,或者类路径中某个 JAR/ZIP 文件内的根目录。
2.包名查找:如果资源名以 / 开头(但在你的例子中,mybatis.xml 并不以 / 开头),则 ClassLoader 会将其视为一个绝对路径,并尝试从类路径的根开始查找。然而,在你的例子中,mybatis.xml 被视为一个相对于类路径根的资源名。
3.父类加载器:如果当前类加载器找不到资源,它可能会委托给其父类加载器进行查找(这取决于类加载器的具体实现和配置)。
返回 InputStream:如果找到了资源,getResourceAsStream 方法会返回一个 InputStream,用于读取资源内容。如果没有找到资源,则返回 null。

重要的是要理解,Resources.getResourceAsStream(或 ClassLoader.getResourceAsStream)并没有将字符串直接转换为文件的绝对路径。相反,它使用了一种更抽象和灵活的方式来查找和加载资源,这种方式不依赖于文件系统的具体布局,而是依赖于类路径的配置。

下面以具体的代码来说明:

getResourceAsStream(resource);

首先我们知道,实际上是调用了ClassLoader的getResourceAsStream方法

    public InputStream getResourceAsStream(String name) {
        URL url = getResource(name);
        try {
            return url != null ? url.openStream() : null;
        } catch (IOException e) {
            return null;
        }
    }

进入getResource方法,这个方法有说法的

	    public URL getResource(String name) {
        URL url;
        /*检查父类加载器:首先,如果当前类加载器(this)有一个父类加载器(parent),
        那么它会调用父类加载器的getResource(name)方法来尝试查找资源。*/
        if (parent != null) {
            url = parent.getResource(name);
            /*如果当前类加载器没有父类加载器(即parent为null),那么它可能会尝试通getBootstrapResource(name)
            方法来从引导类加载器(Bootstrap ClassLoader)的加载路径中查找资源。*/
        } else {
            url = getBootstrapResource(name);
        }
        /*如果以上步骤都没有找到资源,那么当前类加载器会调用自己的findResource(name)
        方法来尝试从自己的加载路径(如类路径、JAR文件等)中查找资源。
        findResource是一个受保护的方法,它必须由子类提供具体的实现。*/
        if (url == null) {
            url = findResource(name);
        }
        return url;
    }

我们进入URLClassLoader的findResource方法:应该就是在这一步找到了

    public URL findResource(final String name) {
        /*
         * The same restriction to finding classes applies to resources
         */
        URL url = AccessController.doPrivileged(
            new PrivilegedAction<URL>() {
                public URL run() {
                    return ucp.findResource(name, true);
                }
            }, acc);

        return url != null ? ucp.checkURL(url) : null;
    }

2 实例化SqlSessionFactoryBuilder并解析配置流文件

 new SqlSessionFactoryBuilder().build(inputStream);

实例化SqlSessionFactoryBuilder并解析配置流文件都是在build()方法中实现的,build()方法相当的重要
其中参数inputStream就是上一步中解析出来的配置文件’mybatis.xml’的流文件
进入方法内部:
可以看到,为了解析配置文件,MyBatis会创建一个XMLConfigBuilder实例。这个实例负责将XML配置文件中的信息解析并构建成Configuration对象。

  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }
//传入的后两个参数为null
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      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.
      }
    }
  }

2.1 XMLConfigBuilder的构造过程

  1. XPathParser是MyBatis中的一个解析器组件,它主要负责对XML配置文件(如mybatis-config.xml和Mapper.xml)进行解析。XPathParser基于Java的XPath解析器,封装了XPath的功能。在这里, environment, props这两个参数为空
  public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }
  1. 进入this方法,也就是另一个重构的构造方法。
    在这里插入图片描述
    在这时,environment, props还为空。
    !!!但是,可以看到这一步新建了一个Configuration对象,相当重要
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    //设置当前错误上下文(ErrorContext)中的资源标识,以便于在发生错误时能够提供更清晰的错误信息或日志。
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props); //空
    this.parsed = false; // 设置为false了
    this.environment = environment; //空
    this.parser = parser; // 上一步新建的parser,不为空
  }
  1. 然后重头戏就来了,1,2步的工作是完成

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

上面这行代码的,现在返回了一个XMLConfigBuilder 我们 进入下一行

return build(parser.parse());

2.2 Configuration对象的装配

首先分析一下这行代码:
这行代码首先调用parser.parse()方法。parse()方法是XMLConfigBuilder类中的一个方法,它的作用是根据前面通过构造函数传入的配置文件信息,解析配置文件并构建一个Configuration对象。Configuration对象包含了MyBatis运行所需的所有配置信息,如数据库连接信息、事务管理器配置、映射器配置等。

然后,这行代码调用了build方法(这里假设build方法是当前类中的一个方法,或者是当前类可以访问的某个工具类中的静态方法),并将parser.parse()返回的Configuration对象作为参数传递给它。build方法的作用通常是根据传入的Configuration对象,构建并返回一个SqlSessionFactory实例。SqlSessionFactory是MyBatis中用于创建SqlSession的工厂类,而SqlSession则是执行SQL语句和映射操作的主要接口。

进入方法看看:可以看到方法类型是Configuration了(用于等一会的build()方法)

  public Configuration parse() {
  //这就是设置为false 的原因:Each XMLConfigBuilder can only be used once.
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //evalNode这个方法的作用是根据给定的 XPath 表达式 "/configuration" 来查找并返回 XML 文档中 <configuration> 元素的节点。
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

把解析的配置文件信息放入Configuration对象就用到了parseConfiguration(parser.evalNode(“/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);
    }
  }

无敌不无敌
补充一点,Configuration对象的作用是相当重要的,如下图
在这里插入图片描述

到这里,我们已经分析了parser.parse(),现在来看看build(parser.parse());

return build(parser.parse());

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

就直接返回了DefaultSqlSessionFactory,这是SqlSessionFactory的实现类。至此实例化SqlSessionFactoryBuilder并解析配置流文件结束,也终于和我看的《深入浅出Mybatis》对应了
在这里插入图片描述

3. SqlSessionFactory的实例化

其实在上一步最后返回的就是SqlSessionFactory对象了。这里再梳理一下流程

  1. 读取MyBatis配置文件:
    这一步骤通常不是直接通过MyBatis的API完成的,而是由开发者在项目中配置好MyBatis的配置文件(通常是mybatis-config.xml),该文件包含了数据库连接信息、事务管理器配置、映射文件路径等。
  2. 构建SqlSessionFactoryBuilder:
    开发者需要创建一个SqlSessionFactoryBuilder的实例,它是用来构建SqlSessionFactory的。这一步没有直接对应的MyBatis源码方法名称,因为SqlSessionFactoryBuilder的实例化通常是通过new SqlSessionFactoryBuilder()完成的。
  3. 使用SqlSessionFactoryBuilder构建SqlSessionFactory:
    使用SqlSessionFactoryBuilder的build(InputStream inputStream)或build(Reader reader)等方法,传入MyBatis配置文件的输入流或读取器,来构建SqlSessionFactory实例。这两个方法是SqlSessionFactoryBuilder类中的核心方法,用于解析配置文件并创建SqlSessionFactory。

4.SqlSession的实例化

SqlSession的实例化是通过SqlSessionFactory的openSession()方法来完成的。
SqlSession是Mybatis的持久化对象,类似于JDBC中的connection。它的底层封装了JDBC连接。可以用SqlSession实例来直接执行已经映射的SQL语句。
先来看

sqlSession = sqlSessionFactory.openSession();

  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }
//这里的getDefaultExecutorType()返回的是ExecutorType.SIMPLE; 第二个参数是level,第三个参数是autoCommit



  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
    //从配置中拿到Environment 
      final Environment environment = configuration.getEnvironment();
      //从Environment中取得TransactionFactory
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //从Environment中取得DataSource
      //通过TransactionFactory,在取得的数据库连接上创建事务对象Transaction;
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //创建Executor对象(该对象非常重要,事实上sqlsession的所有操作都是通过它完成的)
      final Executor executor = configuration.newExecutor(tx, execType);
      //创建sqlsession对象
      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()方法具体都干了什么?

  1. 事务管理器的获取:
    首先,SqlSessionFactory 会根据配置(在 MyBatis 的配置文件 mybatis-config.xml )来确定使用哪个事务管理器(TransactionManager)。
    MyBatis 支持多种事务管理器,如 JDBC 事务管理器(JdbcTransactionManager)、MANAGED 事务管理器(用于 JTA 容器管理的事务)等。
    在这里插入图片描述

执行器(Executor)的创建:
2. 接下来,SqlSessionFactory 会基于事务管理器的类型和其他配置(如是否开启缓存等)来创建一个 Executor 实例。
Executor 是 MyBatis 中的一个核心组件,它负责执行 SQL 语句、管理结果集和游标等。
根据配置的不同,MyBatis 可能会使用不同的 Executor 实现,如 SimpleExecutor、ReuseExecutor 或 BatchExecutor。默认是SimpleExecutor
3. SqlSession 的创建:
有了事务管理器和执行器之后,SqlSessionFactory 就可以创建一个 SqlSession 实例了。
SqlSession 实例会封装事务管理器、执行器以及其他一些配置信息,以便后续执行 SQL 语句时使用。
如果在调用 openSession() 时没有指定自动提交行为,MyBatis 默认会设置为自动提交(这取决于具体实现,但大多数情况下是如此)。
4. 返回 SqlSession:
最后,sqlSessionFactory.openSession() 方法会返回新创建的 SqlSession 实例。
客户端代码可以使用这个 SqlSession 实例来执行 SQL 语句、管理事务以及获取映射器(Mapper)等。

Sqlsession只是个门面,真正干事的是Executor,Sqlsession对数据库的操作都是通过Executor来完成的。

4.1 Executor 的创建

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    //interceptorChain是MyBatis中一个重要的组件,用于管理和执行用户自定义的拦截器
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

根据传入的ExecutorType 来构建对应的执行器,Executor有三种类型

  • SimpleExecutor 是最简单的执行器,每进行一次数据库操作就会创建一个 Statement 对象。它不支持事务的批量处理,每次执行完 SQL 语句后,都会立即关闭 Statement 对象。
  • ReuseExecutor 是可重用的执行器,它会重用 PreparedStatement 和 Statement 对象。它通过缓存机制来减少对预编译语句和映射语句的解析和处理,从而提高了执行效率。
  • BatchExecutor 是批处理执行器,能够执行多个数据修改或插入操作。它通过将相同类型的 SQL 语句分组,并使用 JDBC 的批处理功能(addBatch() 和 executeBatch() 方法)来执行,从而提高了执行效率。但需要注意的是,在使用 BatchExecutor 时,需要手动提交事务。

以SimpleExecutor为例:创建SimpleExecutor的时候我们可以看到,它继承了BaseExecutor,并且实现了doQuery等方法。并且在实现的时候显然根据Configuration 构建了一个StatementHandler对象
在这里插入图片描述
我们可以看到不管是哪个方法都有一个 prepareStatement 方法在return之前
在这里插入图片描述
调用了prepare和parameterize方法

4.2 StatementHandler

StatementHandler是数据库会话器的意思,专门处理数据库会话的。
在Mybatis中默认使用的是PreparedStatementHandler

SimpleExecutorStatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);


Configurationpublic StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

根据代码可以看出来,创建的真实对象是RoutingStatementHandler,它通过适配模式找到对应的StatementHandler来执行,分为三种:对应Executor的三种执行器。这里的delegate就是一个StatementHandler接口对象,这是一个典型的适配器模式
在这里插入图片描述
我们看一下Mybatuis默认使用的PreparedStatementHandler的几个方法

  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
    //instantiateStatement进行了sql的预编译
      statement = instantiateStatement(connection);
      //做一些基础配置
      setStatementTimeout(statement, transactionTimeout);
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }

在上面的代码中instantiateStatement()方法是对sql进行了预编译并且做了一些基础配置,比如超时的配置。之后Executor会调用parameterize()方法去设置参数。

  @Override
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }

所以到这里我们就可以看到一条查询sql的执行过程了:

  1. Executor先调用StatementHandler的prepare()方法进行预编译sql,同时设置一些基本运行的参数。
  2. 调用parameterize()方法
  3. 执行查询

return handler.query(stmt, resultHandler);

4.3 参数处理器

parameterHandler
在4.2中我们看到了parameterize()方法中其实是使用parameterHandler对预编译语句进行设置的。
下面就看看setParameters是怎么实现的

  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> 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 as k 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);
          }
        }
      }
    }
  }

可以看到它还是从parameterObject对象中获取参数的,然后使用typeHandler进行参数的处理

4.4 结果处理器

ResultSetHandler

4.5 插件拦截器

注意在return executor;之前进行了一个拦截器链的调用

executor = (Executor) interceptorChain.pluginAll(executor);

public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<>();

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target); //逐层代理  
    }
    return target;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}

在这里插入图片描述

在这里插入图片描述

5.Mapper

映射器Mapper是什么,是由Java接口和XML文件共同组成的。它的作用是:

  • 定义参数类型
  • 描述缓存
  • 描述SQL语句
  • 定义查询结果和POJO的映射关系
        Studentdao studentdao = sqlSession.getMapper(Studentdao.class);

通过getMapper方法实现Mapper

重点

  1. MapperProxy在MyBatis中代理的是Mapper接口。也就是这个Studentdao 。MyBatis通过MapperProxy来实现Mapper接口的动态代理。
  2. MapperProxy是MyBatis中的一个重要类,它实现了JDK的动态代理机制(java.lang.reflect.InvocationHandler接口)。当业务代码通过SqlSession的getMapper方法获取Mapper接口的实例时,MyBatis实际上返回的是MapperProxy的代理对象。
  3. 具体来说,MapperProxy代理了Mapper接口中的方法,并将这些方法调用转换为对SqlSession的调用。在MapperProxy中,有一个methodCache的Map用于存放键值对(方法,MapperMethod),这个Map中存储了Mapper接口中每个方法对应的MapperMethod对象。MapperMethod对象封装了方法的SQL语句、参数类型、返回类型等信息。当调用Mapper接口中的方法时,MapperProxy会查找methodCache中对应的MapperMethod对象,并调用其execute方法来执行SQL语句,最终返回结果给调用方。

请看代码:
可以看到通过MapperProxy来实现Mapper接口的动态代理。

//这里的type是Studentdao.class , this 是 DefaultSqlSession,即上一步返回的
  public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
  }

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

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

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

被代理对象的方法的访问都会落实到代理者的invoke上来,所以MapperProxy的invoke()方法才是真实的执行:

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    //MapperMethod能够将Mapper接口中的方法映射为实际的SQL操作。
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

MapperProxy中,有一个methodCache的Map用于存放键值对(方法,MapperMethod),这个Map中存储了Mapper接口中每个方法对应的MapperMethod对象。MapperMethod对象封装了方法的SQL语句、参数类型、返回类型等信息。当调用Mapper接口中的方法时,MapperProxy会查找methodCache中对应的MapperMethod对象,一个MapperMethod对应一个映射器的接口方法。并调用其execute方法来执行SQL语句,最终返回结果给调用方。

可以看到invoke把执行权转交给了MapperMethod:

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: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          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;
      case FLUSH:
        result = sqlSession.flushStatements();
        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;
  }

MapperMethod根据参数和返回值类型选择不同的 sqlsession 方法来执行。这样 mapper 对象与 sqlsession 就真正的关联起来了
MapperMethod是MyBatis内部的一个类,它封装了Mapper接口方法的相关信息,并提供了执行这些方法所需的逻辑。当我们通过Mapper接口的代理对象调用某个方法时,MyBatis会找到对应的MapperMethod实例。这个实例会解析方法的签名、参数等信息,并根据这些信息去找到并执行XML文件中对应的SQL语句。

举例,execute这个方法中如果是select,就进入了case SELECT,以executeForMany方法举例:
实际上就是通过sqlSession对象去运行对象的SQL。

  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    //这里的param就是参数
    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;
  }

其中param就是你传进去的参数:
在这里插入图片描述

到这里,解决了一个关键问题:为什么只用Mapper接口就可以运行SQL:因为映射器的XML文件的命名空间(namespace)对应的便是这个接口的全路径。,那么它根据全路径+方法名 就可以绑定起来,然后通过动态代理技术,让这个接口跑起来。而后采用命令模式,最后使用SqlSession接口的方法让它能够查询。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值