MyBatis底层原理(小白版本)

news2024/11/26 14:55:45

!特别声明!:这篇文章只是单纯用来应对面试,并不能用来当作深度解析的文章来看。本人才疏学浅,文章也可能有不对的地方,望指正。
此源码分析使用的是Java11

基本使用流程:

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

SqlSession sqlSession = sqlSessionFactory.openSession();

UserMapper mapper = sqlSession.getMapper(UserMapper.class);

User userById = mapper.getUserById(1);

我们在使用 mybatis时,基本使用用法如上所示
我们一步一步的来看

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);

这一步,不做细讲,就是简单的将我们的xml配置文件转化为输入流对象。


第一步

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

这一步,我们调用了 SqlSessionFactoryBuilder对象的build()方法。进入源码分析

1.1 SqlSessionFactoryBuilder对象的build()方法源码分析

在这里插入图片描述

我们首先进入 第一条语句的源码
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

1.2 XMLConfigBuilder()函数源码分析
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;
  }

这个函数大致作用就是 构建了一个Configuration对象,然后给XMLConfigBuilder对象的属性赋值
注意:Configuration对象类似于单例模式,就是整个Mybatis中只有一个Configuration对象。,这个对象很重要)

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

这条语句就到这里结束,就是创建了 一个 XMLConfigBuilder 类的实例,并给属性赋值,同时创建了一个 Configuration对象

接着我们分析第二条语句

return build(parser.parse());

里面先是 调用了 第一条语句创建的对象的parse()方法,我们进入源码进行分析

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

parseConfiguration(parser.evalNode("/configuration"));
我们可以看到其实整个 parse()函数就调用了这一个方法。
我们给parseConfiguration()提供的参数是一个 Xnode对象,对应的是我们配置文件中的一级标签 <configuration>。我们的配置文件大概构造如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置文件-->
<configuration>
	......
    <environments default="development">
        ......
    </environments>
    <mappers>
        ......
    </mappers>
</configuration>

我们继续进入源码

1.4 parseConfiguration()源码分析

在这里插入图片描述
我们可以看到,我们提供的 root 参数就是 <configuration>的节点对象。然后这个parseConfiguration()函数对 配置文件中 <configuration>标签下的二级标签进行了一些操作,进行了哪些操作呢?我们随便进入一个源码进行查看,此处以 <properties>标签为例,我们进入propertiesElement()函数的源码

1.5 propertiesElement()源码分析
 <properties resource="db.properties">
        <property name="username" value="root"/>
        <property name="password" value="111111"/>
 </properties>

xml文件中的一种用法,此处仅为了方便理解下面的代码

private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
    //此处创建了一个 Properties 类的对象。
    //我们知道 在 mybatis的配置文件中,可以通过 <properties>文件引入别的文件:例如数据库
      Properties defaults = context.getChildrenAsProperties();
      //我们看一下 context对象有没有这两种属性
      String resource = context.getStringAttribute("resource");
      String url = context.getStringAttribute("url");
      //全都为 null 说明没有引入别的文件
      if (resource != null && url != null) {
        throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
      }
      //否则的话,把引入的文件的内容 交给 defaults 对象 (大致这样理解就行)
      if (resource != null) {
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
      //我们在解析创建XMLConfigBuilder对象的时候创建了 一个 configuration对象
      Properties vars = configuration.getVariables();
      //看看 configu中是否已经存在别的引文的文件
      //如果有,把内容也交给 defaults 对象
      if (vars != null) {
        defaults.putAll(vars);
      }
      //用 defaults 再去更新 parse和configuration对象中的对应属性
      parser.setVariables(defaults);
      configuration.setVariables(defaults);
    }
  }

总的来说propertiesElement()函数的工作就是 解析配置文件中的<properties>标签的信息,生成相应的对象,更新 configuration对象的相应属性。
那么其他的函数操作应该作用基本相似,解析相应的标签的内容,将内容赋值给 configuration对象的对应属性。
别的标签的具体实现我们就不再深入去看了,但是有一个我们还是需要去看 <mapper>标签。
这个标签可谓是 配置文件的核心了。
我们去看一下

1.6 mapperElement(XNode parent)源码分析

在看源码前,我们需要知道一件事情
mapper映射的几种方式

<--! 1使用类路径 -->
<mappers>
    <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
      <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
   <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<--! 2使用绝对url路径 -->
<mappers>
   <mapper url="file:///var/mappers/AuthorMapper.xml"/>
   <mapper url="file:///var/mappers/BlogMapper.xml"/>
   <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<--! 3使用java类名 -->
<mappers>
   <mapper class="org.mybatis.builder.AuthorMapper"/>
   <mapper class="org.mybatis.builder.BlogMapper"/>
   <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>

<--! 4自动扫描包下所有映射器 -->
<mappers>
   <package name="org.mybatis.builder"/>
</mappers>

我们紧接着看源码

private void mapperElement(XNode parent) throws Exception {
	//可以没有 mappers 标签
    if (parent != null) {
    // child 就是 mappers 下的 mapper 标签
      for (XNode child : parent.getChildren()) {
      	//如果有 package 属性,说明 用是是自动扫描
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          //把相应的属性值 交给 configuration 对象即可
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          //<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
          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   //<mapper url="file:///var/mappers/AuthorMapper.xml"/>
          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 //<mapper class="org.mybatis.builder.PostMapper"/>
          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.");
          }
        }
      }
    }
  }

其实这个函数也是用来解析<mappers>标签的,不过就是根据不同的映射方式,进行不同的方法解析。至于具体干了什么,此处不去探究(主要是因为我菜,看不懂)
至此 , parse()的作用我们有了一个大致的了解,就是将配置文件中的各种标签的信息都解析到 configuration 对象中, 并且返回configuration对象

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

这句代码中外层还有一个 build()函数,我们看一下这个函数干啥了。源码就一句

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

根据 parser.parse()返回的configuration创建一个 DefaultSqlSessionFactory 对象。具体怎么创建的我们不去探究(菜,懂?)
至此。我们的第一大步到此结束
小结:

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

这一步,就是解析我们的xml配置文件,并且将解析的内容赋值给 一个 configuration 对象。同时,使用这个 configuration 对象,创建一个 DefaultSqlSessionFactory 对象。
此行代码可以这样理解

SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory();

第二步

SqlSession sqlSession = sqlSessionFactory.openSession();

通过第一步 , 我们知道 sqlSessionFactory这个对象是 DefaultSqlSessionFactory类的实例,所以我们去 DefaultSqlSessionFactory类中去探究一下 openSession()具体做了什么。

2.1 sqlSessionFactory.openSession() 源码分析
//直接调用的是这个
@Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }
//间接调用这个
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
    //从 configuration 中 获得 environment 标签下的信息
    //在 mybatis的配置文件中,environment 标签 存放的都是一些数据库的信息
      final Environment environment = configuration.getEnvironment();
      //创建一个事务工厂
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //创建一个事务对象,工厂模式
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 创建一个执行器
      final Executor executor = configuration.newExecutor(tx, execType);
      //返回 一个 DefaultSqlSession 对象
      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();
    }
  }

源码比较简单,本质就是返回了一个 DefaultSqlSession类的实例对象。
这个对象有两个比较重要的属性(DefaultSqlSession类的源码中就有,直接可以看到的)

private final Configuration configuration;
private final Executor executor;

那么

SqlSession sqlSession = sqlSessionFactory.openSession();

就可以理解为

SqlSession sqlSession = new DefaultSqlSession();

小结:

SqlSession sqlSession = sqlSessionFactory.openSession();

主要工作就是创建一个 DefaultSqlSession实例对象,赋值给 sqlSession

此处我们稍微看下 executor的构建源码(可跳过)

2.2 configuration.newExecutor(tx, execType)源码解析(可跳过)
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    //第一步,确实executor的类型
    //SIMPLE, REUSE, BATCH
    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);
    }
    //是否有插件植入
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

大概了解即可,不必深究(我也不会,主打就是一个菜)


第三步

UserMapper mapper = sqlSession.getMapper(UserMapper.class);

我们的 sqlSessionDefaultSqlSession类的实例,我们直接去看 DefaultSqlSession类的getMapper()方法

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

他是调用了 configuration对象的getMapper()。我们继续深入

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

configuration对象又调用的 mapperRegistry对象的getMapper()方法。我们继续深入。

3.1 mapperRegistry.getMapper(type, sqlSession)源码分析
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//一个 mapper代理工厂
    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);
    }
  }

看到Proxy这个单词有没有很熟悉,代理
getMapper()的返回值我们可以大胆猜测是一个代理对象。而这个对象代理的就是 sqlSession

return mapperProxyFactory.newInstance(sqlSession);

至于是不是,我们进入源码分析

3.2 mapperProxyFactory.newInstance(sqlSession) 源码分析

为了区分,我们假设上面的函数为newInstance1,下面的为newInstance2
直接调用newInstance2,间接调用 newInstance1

//这是间接调用的函数
// newInstance1
protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
//  newInstance2
// 这是直接调用的函数
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

其实,看到这里,我们就可以结束第三步了

UserMapper mapper = sqlSession.getMapper(UserMapper.class);

第三步就是得到了 sqlSession的代理对象。


第四步

User userById = mapper.getUserById(1);

让我们的代理对象去执行函数。实际上是我们的真实对象去执行其对应的函数(动态代理的知识)。
具体实现就在代理对象的 invoke()函数中。

在第三步中,我们源码进行到了这一步。我们获得了代理对象。

//这是间接调用的函数
// newInstance1
protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
//  newInstance2
// 这是直接调用的函数
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

newProxyInstance()的基本形式如下

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) 

我们进行对比一下 newInstance1,可以看出
mapperProxy对象占据了InvocationHandler h对象的位置。那么我们可以大胆猜测,mapperProxy对象的类一定是实现了 InvocationHandler的接口的,并且重写了一个很重要的 invoke()函数
我们进入 new MapperProxy()进行分析。

4.1 new MapperProxy()源码分析

事实证明,我们的猜测完全正确。

public class MapperProxy<T> implements InvocationHandler, Serializable {}

然后我们看一下重写的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);
    }
  }

我们看一下 cachedInvoker(method).invoke()的源码

private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
      // A workaround for https://bugs.openjdk.java.net/browse/JDK-8161372
      // It should be removed once the fix is backported to Java 8 or
      // MyBatis drops Java 8 support. See gh-1929
      MapperMethodInvoker invoker = methodCache.get(method);
      if (invoker != null) {
        return invoker;
      }

      return methodCache.computeIfAbsent(method, m -> {
        if (m.isDefault()) {
          try {
            if (privateLookupInMethod == null) {
              return new DefaultMethodInvoker(getMethodHandleJava8(method));
            } else {
              return new DefaultMethodInvoker(getMethodHandleJava9(method));
            }
          } catch (IllegalAccessException | InstantiationException | InvocationTargetException
              | NoSuchMethodException e) {
            throw new RuntimeException(e);
          }
        } else {
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
      });
    } catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
  }

是不是看不懂,我也看不懂。但是有一个跟 sqlSession相关的

return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));

就是这一句(这只是我找的一个借口)(其实我也不知道为啥用这一句,我们就直接看,别管为啥了,),那我们进入 MapperMethod类的源码看看吧。

private final SqlCommand command;
private final MethodSignature method;

public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
}

MapperMethod类中有两个属性,我们分别进入源码看看两个对象中存放的是什么。

4.2 new SqlCommand()源码分析
public static class SqlCommand {

	//两个属性
    private final String name; // sql对应的namespace+id 
    private final SqlCommandType type; // sql 语句的类型。

    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 {
      //获得 sql标签的 id 和 sql 标签的类型(select 啥啥的)。
        name = ms.getId();
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
        }
      }
    }
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
        Class<?> declaringClass, Configuration configuration) {
        //这就是经常提到的 namespace+id 可以唯一确定一个 sql 语句
        //这个方法就是 将 namespace+id 作为一个 key 去获得一个 ,appedStatement对象
        // Map<String, MappedStatement> mappedStatements; 这是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;
    }
}

看不懂没关系,只要记住 command中存放的是 sql 语句的 id 以及 类型即可。
new MethodSignature 的源码我们就不看了,method对象存放的是sql语句的返回值类型以及参数

我们赶紧回到 MapperMethod类的源码来,不要过多去纠结 SqlCommand类和 MethodSignature的源码了。我们知道里面主要有啥就行了。

private final SqlCommand command;
private final MethodSignature method;

public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
}

此处先停止。我们先回到 MapperProxyinvoke()方法。我们必须要明白,我们第四步是要搞清楚到底是执行了哪个方法

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

cachedInvoker(method)是一个方法的返回对象。

return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));

这个方法的返回对象是 PlainMethodInvoker 类型的
那么

return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
//就相当于 
return new PlainMethodInvoker().invoke(proxy, method, args, sqlSession);
//就相当于最终我们返回的是 PlainMethodInvoker 对象的 invoke()函数的返回值

我们进入PlainMethodInvoker类的源码去看一下

4.3 PlainMethodInvoker类源码分析
private static class PlainMethodInvoker implements MapperMethodInvoker {
    private final MapperMethod mapperMethod;

    public PlainMethodInvoker(MapperMethod mapperMethod) {
      super();
      this.mapperMethod = mapperMethod;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      return mapperMethod.execute(sqlSession, args);
    }
  }

看看我们发现了什么

 @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      return mapperMethod.execute(sqlSession, args);
    }

最终返回的结果居然是 mapperMethod.execute()函数的返回值。
我们进入 mapperMethod.execute()源码中看看~

4.4 mapperMethod.execute()源码分析
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //根据 comman的type属性,确实我们的sql类型
    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;
  }

我你们可以在上面源码中看到很多这样的代码!

result = rowCountResult(sqlSession.insert(command.getName(), param));

我们发现 我们 的返回结果又变成了 sqlSession执行相应函数的返回结果。还记的我们前面提到的吗? sqlSessionDefaultSqlSession类的实例。我们去DefaultSqlSession类中去找这些方法!!
sqlSession.update(command.getName(), param)为例
进入源码分析

4.5 sqlSession.update(command.getName(), param)源码分析
@Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

我们发现 sqlSession.update()的本质是交给 exectuor 去执行。至于执行器怎么去执行的,我们就不再探究了!(我不会)。到此第四步其实已经执行完了。可能有点乱,我们来小结一下。
小结:

User userById = mapper.getUserById(1);

我们知道, mapper.getUserById(1)回去执行对应 mapper.xml文件中对应的sql语句。第四步的原理解析实际上就是解析二者是怎么匹配的。
我们知道 代理对象执行方法时,其实执行的是 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);
    }
  }

下面我们用个流程图去说明,就不再看源码了。
在这里插入图片描述
简单概括! 代理对象调用方法的过程底层是 :通过 mapperMethod对象的属性(commandmethod) ,匹配 sqlSession对象应该执行的函数。 同时 sqlSession将具体的实现交给 执行器 executor去执行。
这一步的口述我也很难用文字去表述清楚。大家多看几遍应该能理解。


分析就到这里啦,exectuor的底层就不再分析了,我也不会。
至于 mappedStatement 是从哪来的 大家可以看一下这篇文章 点击这里

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1169476.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DAY43 完全背包理论基础 + 518.零钱兑换II

完全背包 有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品都有无限个&#xff08;也就是可以放入背包多次&#xff09;&#xff0c;求解将哪些物品装入背包里物品价值总和最大。 完全背包和01背包问题唯一不同…

【51单片机】串口与LED点阵屏(学习笔记)

一、串口 1、串口的概述 串口是一种应用十分广泛的通讯接口&#xff0c;串口成本低、容易使用、通信线路简单&#xff0c;可实现两个设备的互相通信。 单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信&#xff0c;极大的扩展了单片机的应用…

数据中心如何散热?

数据中心的散热是一个非常重要的问题&#xff0c;因为数据中心内运行的服务器、存储设备以及网络设备等都会产生大量的热量&#xff0c;如果不能有效地进行散热&#xff0c;将会导致设备故障和性能下降。下面是一些常见的数据中心散热方法&#xff1a; 空调系统&#xff1a;数据…

20231103配置cv180zb的编译环境【填坑篇】

20231103配置cv180zb的编译环境【填坑篇】 2023/11/3 11:36 感谢您选择了晶视科技的cv180zb&#xff0c;让我们一起来填坑。 在你根据文档找不到答案的时候&#xff0c;是不是想把他们家那个写文档的家伙打一顿&#xff0c;我顶你。 当你在在网上找一圈&#xff0c;BAIDU/BING/…

C++初阶(八)类和对象

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、Static成员1、Static概念2、Static特性3、试题 二、友元1、友元的类型2、友元函数3、 友元…

CSS中flex和inline-flex的区别

CSS中flex和inline-flex的区别 起因 display:flex\inline-flex是CSS中弹性布局时&#xff0c;用于容器元素的样式选项。 让人有些糊涂&#xff0c;两者的区别时什么。网上看了一些文章都在扯什么宽度之类的&#xff0c;完全就是胡扯。 还有的写什么“将对象作为弹性伸缩盒显示…

【软件STM32cubeIDE下H73xx配置串口uart1+中断接收/DMA收发+HAL库+简单数据解析-基础样例】

#【软件STM32cubeIDE下H73xx配置串口uart1中断接收/DMA收发HAL库简单数据解析-基础样例】 1、前言2、实验器件3-1、普通收发中断接收实验第一步&#xff1a;代码调试-基本配置&#xff08;1&#xff09;基本配置&#xff08;3&#xff09;时钟配置&#xff08;4&#xff09;保存…

TEMU拼多多跨境平台要求提供的UL测试报告如何办理?电子产品UL测试标准要求

平台销售的电子产品&#xff0c;要符合指定的标准&#xff0c;如果不合格很容易发生起火&#xff0c;等危及消费者生命财产的安全&#xff0c;因此很多客户因为缺少UL报告&#xff0c;导致产品被下架&#xff0c;销售权被移除等问题&#xff0c;也少不了同行之间的恶意举报触发…

ActiveMQ、RabbitMQ、RocketMQ、Kafka介绍

一、消息中间件的使用场景 消息中间件的使用场景总结就是六个字&#xff1a;解耦、异步、削峰 1.解耦 如果我方系统A要与三方B系统进行数据对接&#xff0c;推送系统人员信息&#xff0c;通常我们会使用接口开发来进行。但是如果运维期间B系统进行了调整&#xff0c;或者推送过…

如何开始短视频的制作,短视频脚本如何写?

在短视频创作拍摄的过程中&#xff0c;你有没有遇到过类似的情况&#xff1a; 拍摄拍到中途手忙脚乱的&#xff0c;不知道接下来该拍摄什么类容&#xff0c;或者拍了一半发现拍摄场景不行&#xff0c;又重新调整拍摄场景&#xff0c;再者&#xff0c;拍摄过程中发现缺少了拍摄道…

【面试专题】设计模式篇①

1.工厂设计模式 工厂设计模式是一种创建型模式&#xff0c;它提供了一种创建对象的接口&#xff0c;但具体创建的对象类型可以在运行时决定。工厂设计模式主要解决的是创建对象的灵活性问题。 工厂设计模式主要包括简单工厂模式、工厂方法模式和抽象工厂模式三种。 简单工厂…

虹科示波器 | 汽车免拆检修 | 2010款江铃陆风X8车发动机怠速抖动、加速无力

一、故障现象 一辆2010款江铃陆风X8车&#xff0c;搭载4G6GS4N发动机&#xff0c;累计行驶里程约为20万km。该车在其他修理厂进行发动机大修&#xff0c;维修后试车&#xff0c;发动机怠速抖动、加速无力。用故障检测仪检测&#xff0c;发动机控制模块&#xff08;ECM&#xff…

JVM常用命令

jps —查看pid jstat -gcutil 4364 1000 2000 —查看堆内存占用百分比&#xff0c;每秒打印1次&#xff0c;总共打印2000次 S0&#xff1a;幸存1区当前使用比例 S1&#xff1a;幸存2区当前使用比例 E&#xff1a;伊甸园区使用比例 O&#xff1a;老年代使用比例 M&#xff1a;元…

常用的Linux远程桌面配置方法

TigerVNC 是 VNC&#xff08;虚拟网络计算&#xff09;的高性能、平台中立的实现&#xff0c;VNC 是一种客户端/服务器应用程序&#xff0c;允许用户在远程计算机上启动图形应用程序并与之交互。 TigerVNC 提供运行 3D 和视频应用程序所需的性能水平&#xff0c;并尝试在其支持…

基于单片机的温室环境数据监测系统的设计

收藏和点赞&#xff0c;您的关注是我创作的动力 文章目录 概要 一、总体方案设计2.1 总体架构设计 二、整体硬件电路设计3.1 主控制器电路 三 系统设计概要4.2 主程序设计原理图程序 四、 结论五、 文章目录 概要 与农业发达国家相比&#xff0c;我国的农业科技方面还处于刚刚…

腾讯云双11云服务器活动:88元一年的云服务器值得买吗?

作为一名程序员&#xff0c;在选择云服务器时&#xff0c;最关注的是网络稳定性、价格以及云服务商的规模。腾讯云在2023年10月23日的​双11活动​中推出了一款性价比极高的云服务器&#xff0c;为我们提供了一个非常有吸引力的选择。 1. 关注网络稳定性、价格和云服务商规模 …

jar包的精细化运营,Java模块化简介 | 京东云技术团队

图&#xff1a;模块化手机概念 一、什么是Java模块化 Java模块化&#xff08;module&#xff09;是Java9及以后版本引入的新特性。 官方对模块的定义为&#xff1a;一个被命名的&#xff0c;代码和数据的自描述集合。&#xff08; the module, which is a named, self-descri…

【C语法学习】15 - fopen()函数

文章目录 1 函数原型2 返回值3 参数3.1 文件名3.2 模式3.2.1 以"r"模式打开3.2.2 以"w"模式打开3.2.3 以"a"模式打开3.2.4 以"r"模式打开3.2.5 以"w"模式打开3.2.6 以"a"模式打开 1 函数原型 fopen()&#xff1a…

AI大模型架构师专家,你会问什么来测试我的水平,如何解答上述问题,学习路径是什么

0. 沈剑老师的大模型产品应用经验&#xff1a; 提示词三步骤&#xff1a; 假如我是xxx专家&#xff0c;你会问什么来测试我的水平&#xff1b;假如你是xxx专家&#xff0c;你会如何解答上述问题&#xff1b;假如你是xxx专家&#xff0c;上述问题的学习路径是什么&#xff1b;…