mybatis源码解析-sql执行流程

news2024/11/5 15:31:49
1 执行器的创建

1. SimpleExecutor

  • 描述:最基本的执行器,每次查询都会创建新的语句对象,并且不会缓存任何结果。

  • 特点:

    • 每次查询都会创建新的 PreparedStatement 对象。

    • 不支持一级缓存。

    • 适用于简单的查询操作,不需要缓存的情况。

2. ReuseExecutor

  • 描述:复用型执行器,会复用 PreparedStatements。

  • 特点:

    • 通过缓存 PreparedStatement 对象来提高性能。

    • 支持一级缓存。

    • 适用于多次执行相同的 SQL 语句,尤其是参数不同的情况。

3. BatchExecutor

  • 描述:批量执行器,用于批量执行 SQL 语句。

  • 特点:

    • 支持批量插入和更新操作。

    • 通过缓存 PreparedStatement 对象来提高性能。

    • 将多个 SQL 语句打包在一起,减少数据库通信次数,提高性能。

    • 适用于大数据量的批量操作。

    • 需要手动调用 flushStatements 方法来提交批量操作。

执行语句如下:

SqlSession session = sqlSessionFactory.openSession();

核心类:DefaultSqlSessionFactory 执行方法:openSession


public SqlSession openSession() {
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}


private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    //1 获取环境变量
    final Environment environment = configuration.getEnvironment();
    //2 获取事务工厂
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    //3 创建一个sql执行器对象 默认创建SimpleExecutor
    final Executor executor = configuration.newExecutor(tx, execType);
    //4 创建返回一个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();
  }
}

2 代理对象的创建

语句如下

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

源码解析

核心类:DefaultSqlSession 核心方法:getMapper

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

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  //1 直接去缓存knownMappers中通过Mapper的class类型去找我们的mapperProxyFactory
  //xml解析时 会把所有的mapper接口存放至这个map value是一个代理
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  //2 缓存中没有获取到 直接抛出异常
  if (mapperProxyFactory == null) {
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  }
  try {
    //3 通过MapperProxyFactory来创建我们的实例
    return mapperProxyFactory.newInstance(sqlSession);
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}

public T newInstance(SqlSession sqlSession) {
  //1 创建我们的代理对象
  final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  //2 创建我们的Mapper代理对象返回
  return newInstance(mapperProxy);
}
//JDK代理生成对象
protected T newInstance(MapperProxy<T> mapperProxy) {
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
3 sql的执行

执行语句:

User user = mapper.selectById(1);

执行这条语句时,由于mapper是一个代理对象,会自动执行创建代理对象中构造函数的InvocationHandler中invoke方法,这里mybatis包装了InvocationHandler对象,自定义了一个类继承InvocationHandler。

核心类:MapperProxy 核心方法:invoke


public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    //1 判断我们的方法是不是我们的Object类定义的方法,若是直接通过反射调用
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    } else if (method.isDefault()) {   
    //是否接口的默认方法
    //2 调用我们的接口中的默认方法
      return invokeDefaultMethod(proxy, method, args);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }

  //3 真正的进行调用,做了二个事情
  //第一步:把我们的方法对象封装成一个MapperMethod对象(带有缓存作用的)
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  //4 通过sqlSessionTemplate来调用我们的目标方法
  return mapperMethod.execute(sqlSession, args);
}


public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  //1 判断我们执行sql命令的类型
  switch (command.getType()) {
    //insert操作
    case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
      break;
    }
    //update操作
    case UPDATE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
      break;
    }
    //delete操作
    case DELETE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
      break;
    }
    //select操作
    case SELECT:
      //返回值为空
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        //返回值是一个List
        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);
        // 通过调用DefaultSqlSession来执行我们的sql
        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;
}

public <T> T selectOne(String statement, Object parameter) {
  // Popular vote was to return null on 0 results and throw exception on too many.
  //1 这里selectOne调用也是调用selectList方法
  List<T> list = this.selectList(statement, parameter);
  //2 若查询出来有且有一个一个对象,直接返回要给
  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;
  }
}

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    //1 第一步:通过我们的statement去我们的全局配置类中获取MappedStatement
    MappedStatement ms = configuration.getMappedStatement(statement);
    //2 通过执行器去执行我们的sql对象 默认实现:CachingExecutor
    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();
  }
}

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
  /**
   * 判断我们我们的mapper中是否开启了二级缓存<cache></cache>
   */
  Cache cache = ms.getCache();
  /**
   * 判断是否配置了<cache></cache>
   */
  if (cache != null) {
    //判断是否需要刷新缓存
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
      ensureNoOutParams(ms, boundSql);
      /**
       * 先去二级缓存中获取
       */
      @SuppressWarnings("unchecked")
      List<E> list = (List<E>) tcm.getObject(cache, key);
      /**
       * 二级缓存中没有获取到
       */
      if (list == null) {
        //去一级缓存获取 实现类:BaseExecutor
        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);
}


public <E> List<E> 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());
  //1 已经关闭,则抛出 ExecutorException 异常
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  //2 清空本地缓存,如果 queryStack 为零,并且要求清空本地缓存。
  if (queryStack == 0 && ms.isFlushCacheRequired()) {
    clearLocalCache();
  }
  List<E> list;
  try {
    //3 从一级缓存中,获取查询结果
    queryStack++;
    list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    //4 获取到,则进行处理
    if (list != null) {
      //5 处理存过的
      handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    } else {
      //6 获得不到,则从数据库中查询
      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 <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
    localCache.removeObject(key);
  }
  localCache.putObject(key, list);
  if (ms.getStatementType() == StatementType.CALLABLE) {
    localOutputParameterCache.putObject(key, parameter);
  }
  return list;
}


private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
  //选择配置的执行器执行sql对象 默认是SimpleExecutor
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
    localCache.removeObject(key);
  }
  localCache.putObject(key, list);
  if (ms.getStatementType() == StatementType.CALLABLE) {
    localOutputParameterCache.putObject(key, parameter);
  }
  return list;
}


public <E> List<E> 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);
    //1 拿到连接 预处理语句statement 默认实现:PreparedStatementHandler
    stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  //1 执行sql语句
  ps.execute();
  //2 处理返回结果
  return resultSetHandler.handleResultSets(ps);
}

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

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

相关文章

安装fpm,解决*.deb=> *.rpm

要从生成 .deb 包转换为 .rpm 包&#xff0c;可以按照以下步骤修改打包脚本 1. 使用 fpm 工具 fpm 是一个强大的跨平台打包工具&#xff0c;可以将 .deb 包重新打包成 .rpm&#xff0c;也可以直接从源文件打包成 .rpm。 安装 fpm sudo apt-get install ruby-dev sudo gem in…

分布式光伏管理办法

随着分布式光伏项目的不断增加&#xff0c;传统的管理方式已经难以满足高效、精准的管理需求。光伏业务管理系统作为一种集信息化、智能化于一体的管理工具&#xff0c;正在逐步成为分布式光伏项目管理的重要支撑。 光伏业务管理系统通过数字化手段实现对光伏业务全流程的精细化…

数据结构:LRUCache

什么是LRUCache 首先我们来看看什么是cache 缓存&#xff08;Cache&#xff09;通常用于两个速度不同的介质之间&#xff0c;以提高数据访问的速度和效率。这里有几个典型的应用场景&#xff1a; 处理器和内存之间&#xff1a; 处理器&#xff08;CPU&#xff09;的运算速度远…

智能提醒助理系列-springboot项目彩虹日志+TraceID

本系列文章记录“智能提醒助理”产品建设历程&#xff0c;记录实践经验、巩固知识点、锻炼总结能力。 本篇介绍如何让springboot启动日志“彩打” 提升日志识别度&#xff0c;同时增加TraceID&#xff0c;便于同一请求&#xff0c;全链路的追踪。 一、需求出发点 提升日志识别度…

窨井监测遥测终端RTU IP68防水强信号穿透力

在窨井的潮湿 黑暗和腐蚀性环境中 常规物联网设备往往难以生存 如何突破层层环境挑战 轻松应对极端条件 确保信号 24h不掉线&#xff0c;不延迟 不仅是对技术的突破 更是对恶劣环境的征服 ↓↓↓ 坚守 ——严苛环境下的工业设备 计讯物联工业级设备&#xff0c;专为恶劣环境设计…

150道MySQL高频面试题,学完吊打面试官--如何实现索引机制

前言 本专栏为150道MySQL大厂高频面试题讲解分析&#xff0c;这些面试题都是通过MySQL8.0官方文档和阿里巴巴官方手册还有一些大厂面试官提供的资料。 MySQL应用广泛&#xff0c;在多个开发语言中都处于重要地位&#xff0c;所以最好都要掌握MySQL的精华面试题&#xff0c;这也…

基于Matlab 模拟停车位管理系统【源码 GUI】

系统对进入停车位的车辆进行车牌识别&#xff0c;将识别出来的车牌号显示出来&#xff1b;然后对车主进行人脸识别&#xff0c;框出车主照片的人脸部分作为车主信息的标记&#xff0c;记录在系统库中。车辆在库期间&#xff0c;系统使用者可以随意查看车辆与车主信息的获取过程…

微信小程序 https://pcapi-xiaotuxian-front-devtest.itheima.net 不在以下 request 合法域名

微信小程序在调用接口的时候出现以上报错&#xff0c;接口没有问题&#xff0c;是因为小程序自动校验了合法域名 打开本地设置&#xff1a; 勾选不校验合法域名&#xff0c;即可 效果如下&#xff1a;

数据治理,数据提取,大数据中心建设,大数据治理总体解决方案书(word,ppt原件)

1. 数据管理的现状 2. 数据治理的概述 1.1数据治理概念 2.2数据治理目标 3. 数据治理体系 4. 数据治理核心领域 1.1 数据模型 1.2 数据生命周期 &#xff08;1&#xff09;数据生成及传输 &#xff08;2&#xff09;数据存储 &#xff08;3&#xff09;数据处理和应用…

电机控制储备知识 二:电磁学理论知识

一&#xff1a;磁场的发现过程和和一些实验现象 古代发现&#xff1a;公元前七世纪&#xff0c;中国和古希腊的学者就已经发现了磁石。 吉尔伯特的研究&#xff1a;1600年&#xff0c;英国女王御臣威廉吉尔伯特&#xff08;William Gilbert&#xff09;发表了《地磁论》&#…

Java:数组的定义和使用(万字解析)

目录 1. 数组的概念 2. 数组的基础知识 2.1 数组的创建 \1. 基础创建格式&#xff1a; \2. 类似C语言的创建格式&#xff1a; 【错误的创建(初始化)格式】 2.2 数组的数据类型 2.3 数组的初始化 —— 两种方式 \1.动态初始化&#xff1a;(完全默认初始化) \2. 静态初…

ProLightsfx新的出发–从CSDN到WordPress

原文链接&#xff1a;ProLightsfx新的出发--从CSDN到WordPress_ProLightsfx的技术分享 &#xff08;https://www.prolightsfxjh.com/article/article-new-start/&#xff09; 大概有差不多2年时间没有在csdn发布文章了。可能主要是最近几年工作有些疲惫、精神有些懈怠&#xff…

【react】Redux基础用法

1. Redux基础用法 Redux 是一个用于 JavaScript 应用的状态管理库&#xff0c;它不依赖于任何 UI库&#xff0c;但常用于与 React 框架配合使用。它提供了一种集中式的状态管理方式&#xff0c;将应用的所有状态保存在一个单一的全局 Store&#xff08;存储&#xff09;中&…

VMware虚拟机Debian扩展磁盘

一、 版本 VMware&#xff1a;Workstation 17 Pro虚拟机&#xff1a;Debian11 二、 VMware虚拟机扩展 虚拟机关机状态快照或者备份&#xff1a;以免扩容失败导致文件丢失虚拟机——设置——硬盘——磁盘使用工具——扩展——扩展磁盘容量——设置为想要的大小 三、 虚拟机…

软件设计师-上午题-16 算法(4-5分)

算法题号一般为62-65题(数据结构与算法题号为57-65&#xff0c;共9分)&#xff0c;分值一般为4-5分。 目录 1 回溯法 1.1 N皇后问题 1.2 非递归求解N皇后问题 1.3 递归求解N皇后问题 1.4 真题 2 分治法 2.1 最大字段和问题 2.2 真题 3 动态规划 3.1 0-1背包问题 3.…

【react如何在chrome浏览器里面调试?】

react如何在chrome浏览器里面调试&#xff1f; 1. 首先在在工作区关联源码 2. 安装react的chrome插件。 3. 切换到插件的标签&#xff0c;然后选中你要调试的页面元素&#xff0c;再点击右边的按钮&#xff0c;切换到对应的源码 4. 可以在源码任意位置打断点运行。

【Mysql NDB Cluster 集群(CentOS 7)安装笔记一】

Mysql NDB Cluster 集群(CentOS 7)安装笔记 NDB集群核心概念 NDBCLUSTER(也称为NDB)是一个内存存储引擎,提供高可用性和数据保存功能。 NDBCLUSTER存储引擎可以配置一系列故障转移和负载平衡选项,但从集群级别的存储引擎开始是最容易的。NDB集群的NDB存储引擎包含一整套…

在Microsoft Outlook日历中添加多个时区

在Microsoft Outlook日历中添加多个时区 1.单击Outlook中的文件选项卡&#xff0c;单击选项 2.左侧菜单中选择日历 3.向下滚动到时区部分&#xff0c;并标记当前时区&#xff0c;比如China 4.选中“显示第二个时区”框 5.选择第二个时区并给它一个标签&#xff0c;比如Germa…

考公人数攀升?地信、测绘、地质、遥感等专业,能报考哪些单位

近年来&#xff0c;考公人数持续飙升&#xff0c;国考报名人数更逐年攀升。2025年国家公务员考试共有341.6万人通过资格审查&#xff0c;报录比达86:1。国考报名人数再创新高。 国家公务员考试时间安排 地理学相关岗位分析 地信属于地理科学类&#xff0c;测绘类中不包括地信&…

大华乐橙设备私有平台EasyCVR视频设备轨迹回放平台支持哪些摄像机?摄像机如何选型?

在现代安全监控系统中&#xff0c;视频监控设备扮演着至关重要的角色。视频设备轨迹回放平台EasyCVR以其卓越的兼容性和灵活性&#xff0c;支持接入多种品牌和类型的摄像机。这不仅为用户提供了广泛的选择空间&#xff0c;也使得视频监控系统的构建和管理变得更加高效和便捷。本…