Mybatis执行流程简析

news2025/2/24 6:30:58

一、前言

日常工作中,我们用到mybatis的时候,都是写一个Mapper接口+xml文件/注解形式,然后就可以在业务层去调用我们在Mapper接口中定义的CRUD方法,很方便,但一直都没有去研究过执行逻辑,下面附一篇我自己研究的过程。

二、注入过程分析

平时我们在使用时,都是直接注解标在Mapper接口上,从而去注入一个实例,但我们是并没有实现过这个Mapper接口的,就很容易想到必然是有一个代理类(MapperProxy)来帮我们执行真正的过程,而且既然我们定义了接口,那大概率就是JDK动态代理了。

注入的过程也比较简单,在我们使用时会注明一个mapper扫描的包路径,然后在SqlSessionFactory初始化的过程中,会去解析每个mapper接口,并将其放在Configuration的MapperRegistry中,实际存放位置是在MapperRegistry中Map<Class<?>, MapperProxyFactory<?>> knownMappers。

接下来我们在使用mapper接口时,会从knownMappers中去获取到对应的MapperProxyFactory,从而去实例化真正的代理类,代码如下:

  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(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);
        }
      }
    }
  }
  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 {
      //关键步骤,里面会使用JDK动态代理,创建一个MapperProxy
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

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

}

三、执行过程分析

综上来看,其实我们真正的方法是由MapperProxy来实现的,接下来看看它的逻辑,我们主要分析查询类的执行流程:

  /**
  * 执行入口
  */
  @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 if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    //缓存方法
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //真正的执行逻辑
    return mapperMethod.execute(sqlSession, args);
  }
/**
* 根据sql类型,执行不同的分支
* convertArgsToSqlCommandParam会转换传入的参数
*/
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:
        //如果是void方法,并且自定义了ResultMap等映射,则执行此逻辑
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
        //返参为集合类型
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
        //返参为Map
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
        //返参为游标
          result = executeForCursor(sqlSession, args);
        } else {
        //返参为单对象
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        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;
  }

上面的查询方法,都会走到下面这两行代码

//获取sql执行的映射过程对象,包含了参数、数据源、返参等
MappedStatement ms = configuration.getMappedStatement(statement);
//执行查询方法
executor.query(ms, wrapCollection(parameter), rowBounds, handler);

接着会走到执行器executor,主要有两类实现:

      • org.apache.ibatis.executor.BaseExecutor(基本执行器)
      • CachingExecutor(带缓存的执行器)
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //获取BoundSql对象,包含了解析动态SQl生成的sql语句以及参数映射的封装
    BoundSql boundSql = ms.getBoundSql(parameter);
    //生成缓存key
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    //查询
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }
public BoundSql getBoundSql(Object parameterObject) {
	//调用sqlSource获取BoundSql,sqlSource默认为DynamicSqlSource类型(动态SQL)
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    //若参数映射为空,手动创建boundSql
    if (parameterMappings == null || parameterMappings.isEmpty()) {
      boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    // check for nested result maps in parameter mappings (issue #30)
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
      String rmId = pm.getResultMapId();
      if (rmId != null) {
        ResultMap rm = configuration.getResultMap(rmId);
        if (rm != null) {
          hasNestedResultMaps |= rm.hasNestedResultMaps();
        }
      }
    }

    return boundSql;
  }
  public BoundSql getBoundSql(Object parameterObject) {
	//获取上下文对象,并将传入的入参对象及数据源标识放入bindings
    DynamicContext context = new DynamicContext(configuration, parameterObject);
	//基于动态sql解析成的List<SqlNode> contents,遍历赋值参数
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
      boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    return boundSql;
  }

准备好BoundSql后,就该执行真正的查询了,主要链路如下

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

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

相关文章

在NISQ小型计算机上执行大型并行量子计算的可能性

简介 Steve White提出了密度矩阵重整化群&#xff08;DMRG&#xff09;的基本思想&#xff0c;即纠缠是一种有价值的资源&#xff0c;可以用来精确或近似地描述大量子系统。后来&#xff0c;这一思想被理解为优化矩阵积状态&#xff08;MPS&#xff09;的算法&#xff0c;支持…

组件与Props:React中构建可复用UI的基石

目录 组件&#xff1a;构建现代UI的基本单位 Props&#xff1a;组件之间的数据传递 Props的灵活性&#xff1a;构建可配置的组件 组件间的通信&#xff1a;通过回调函数传递数据 总结&#xff1a; 组件&#xff1a;构建现代UI的基本单位 组件是前端开发中的关键概念之一。…

氮化镓功率HEMT的表征与建模

来源&#xff1a;Characterization and Modeling of a Gallium Nitride Power HEMT(IEEE TRANSACTIONS ON INDUSTRY APPLICATIONS) GaN high-electron-mobility transistor (HEMT) GaN高电子迁移率晶体管   该模型包括一个电压相关电流源Ids、两个电压相关电容Cgd和Cds、一…

UG\NX二次开发 球坐标到直角坐标的转换

文章作者:里海 来源网站:《里海NX二次开发3000例专栏》 感谢粉丝订阅 感谢 JiaLiHuiNaoGui 订阅本专栏,非常感谢。 简介 已知角度θ和ϕ,距离d,求P点坐标。 "> 代码 #include "me.hpp" using namespace NXOpen; using namespace std;void GetP<

汽车贴膜店展示服务预约小程序的作用是什么

很多家庭都有车辆&#xff0c;除了车身自带颜色或外观&#xff0c;部分消费者会选择贴车衣、改色膜以及其它装饰类服务&#xff1b;而市场高需求下也促进了商家生意增长。 但随着线上化程度加深&#xff0c;传统线下门店也面临多重困境&#xff0c;品牌需要线上发展获得生意及…

loadsh

官网&#xff08;当前版本4.x&#xff09;Lodash Documentationhttps://lodash.com/docs/4.17.15 简介 Lodash是一个著名的javascript原生库&#xff0c;不需要引入其他第三方依赖。是一个意在提高开发者效率,提高JS原生方法性能的JS库。简单的说就是&#xff0c;很多方法loda…

WorkPlus:领先的IM软件,助力企业高效沟通与协作

随着信息技术的发展&#xff0c;企业内部沟通协作变得至关重要&#xff0c;而IM&#xff08;即时通讯&#xff09;软件成为了企业中不可或缺的工具。在众多IM软件中&#xff0c;WorkPlus凭借其卓越的功能和优质的用户体验&#xff0c;成为了企业首选。 WorkPlus为何备受企业青睐…

一文弄懂Linux信号机制

目录 1.什么是信号&#xff1f; 2.信号实现原理 ​3.信号生命周期 4.信号分类 5.信号常见概念 6.信号阻塞和信号忽略的区别&#xff1f; 1.什么是信号&#xff1f; Linux信号机制是进程间通信的一种方式&#xff0c;用于在不同进程之间传递信息。它通过向目标进程发送一…

图片加水印怎么加?

图片加水印怎么加&#xff1f;一般我们给图片加水印都会使用一些工具来进行的。很多常见的 作图工具都有给图片加水印的功能呢。图片加水印是一种常见的图像处理技术&#xff0c;可以在图片上添加一些标识、文字或图形&#xff0c;以保护图片的版权&#xff0c;或者用于品牌宣传…

在Maven中发布项目到Nexus私有服务器

一、测试环境 Sonatype Nexus 3.61.0-02 Maven 3.9.2 二、环境配置 2.1找到maven的配置文件 2.2添加私有仓库账户密码 <servers><server><id>nexus</id><username>admin</username><password>admin</password></server&…

批量预处理哨兵2影像

批量预处理哨兵2影像 最近下载70多景哨兵2影像&#xff0c;平均每个影像在cmd中处理时间都需要半个小时。算下来我一景一景手动处理需要37个小时左右&#xff0c;每天在电脑前待8个小时也要4天多&#xff0c;很浪费时间。如果能够批处理&#xff0c;不需要我手动做的话&#x…

作为开发者如何有效使用 ChatGPT(文末送书)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

基于EasyCVR技术的大数据视频汇聚与智能分析平台设计方案

一、背景需求 大数据中心的数据建设如火如荼&#xff0c;针对其中城市中的视频监管及算法分析&#xff0c;各卡口监控、治安监控&#xff0c;电警监控不同网络、不同地域&#xff0c;如何进行视频融合、进行统一监管&#xff0c;则是大数据中心解决方案数据汇聚的重中之重。 现…

java判断上午下午的方法

一、代码 import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale;public class Main1 {public static void main(String[] args) {Date nowDate new Date();SimpleDateFormat sdf new SimpleDateFormat("aa", Locale.ENGLISH);Strin…

流程图用什么软件做?这4款软件不容错过

流程图是管理者用于管理的一种方式&#xff0c;它能够让管理者详细地知道工作中&#xff0c;哪个环节出了问题&#xff0c;从而针对问题&#xff0c;确定可实施的方案。因此&#xff0c;如何制作出一个完整流畅的流程图&#xff0c;是所有管理者都在思考的问题&#xff0c;小编…

安装Python环境

Python 安装包下载地址&#xff1a;https://www.python.org/downloads/ 打开该链接&#xff0c;可以看到有两个版本的 Python&#xff0c;分别是 Python 3.x 和 Python 2.x&#xff0c;如下图所示&#xff1a; Python下载页面截图 图 1 Python 下载页面截图&#xff08;包含…

专题|国内九大科技上市公司聚焦AIGC落地,八仙过海,谁更神通?

【科技明说 &#xff5c; 重磅专题】 从AI高谈阔论的概念&#xff0c; 到AI真金白银的投资&#xff0c;再到AI因ChatGPT大模型的升温&#xff0c;每一次技术带动产业的革新&#xff0c;都离不开不了两样东西的驱动。一是此起彼伏的技术迭代&#xff0c;二是不计后果的资本注入…

水溶性纳米银颗粒 纳米银颗粒 银纳米颗粒溶液

西&#xff09;产品名称&#xff1a;水溶性纳米银颗粒 安&#xff09;别名 &#xff1a;纳米银溶液 银纳米颗粒溶液 纳米银胶体等 瑞&#xff09;浓度&#xff1a;0.1mg/mL 其它均可定制 禧&#xff09;粒径&#xff1a;5nm 10nm 15nm 20nm 25nm 30nm 35nm 40nm 50nm 60nm 80…

Qt自定义文件选择框

文章目录 前言一、头文件二、源文件三、qss文件四、效果 前言 在开发过程中,经常遇到使用文件选择框的情况,qt默认的文件选择框,样式可能与开发的桌面程序差异比较大.此时,我们可以自定义文件选择框,设置其样式,尽量做到样式统一. 一、头文件 #ifndef CUSTOMFILEDIALOG_H #d…

【数据结构】顺序表的学习

前言:在之前我们学习了C语言的各种各样的语法&#xff0c;因此我们今天开始学习数据结构这一个模块&#xff0c;因此我们就从第一个部分来开始学习"顺序表"。 &#x1f496; 博主CSDN主页:卫卫卫的个人主页 &#x1f49e; &#x1f449; 专栏分类:C程序设计谭浩强版本…