MyBatis源码剖析之Mapper代理方式细节

news2024/11/24 12:39:27

MyBatis是一个流行的Java持久层框架,它提供了多种方式来执行数据库操作,其中之一就是通过Mapper代理方式。通过Mapper代理方式,开发者可以编写接口,然后MyBatis会动态地生成接口的实现类,从而避免了繁琐的SQL映射配置。

具体代码如下:
*在这里插入图片描述*

思考⼀个问题,通常的Mapper接⼝我们都没有实现的⽅法却可以使⽤,是为什么呢?

答案很简单:动态代理

public class Configuration {  
    protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
}

public class MapperRegistry {
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
}

开始之前介绍⼀下MyBatis初始化时对接⼝的处理:MapperRegistryConfiguration中的⼀个属性,它内部维护⼀个HashMap⽤于存放mapper接⼝的⼯⼚类,每个接⼝对应⼀个⼯⼚类。mappers中可以配置接⼝的包路径,或者某个具体的接⼝类。
在这里插入图片描述

当解析mappers标签时,它会判断解析到的是mapper配置⽂件时,会再将对应配置⽂件中的增删改查标签 封装成MappedStatement对象,存⼊mappedStatements中。当判断解析到接⼝时,会建此接⼝对应的MapperProxyFactory对象,存⼊HashMap中,key =接⼝的字节码对象,value =此接⼝对应MapperProxyFactory对象。

源码剖析-getmapper()

进⼊ sqlSession.getMapper(UserMapper.class )中

public interface SqlSession extends Closeable {
      //调用configuration中的getMapper
      <T> T getMapper(Class<T> type);
}

public class DefaultSqlSession implements SqlSession {
  @Override
  public <T> T getMapper(Class<T> type) {
    //调用configuration 中的getMapper
    return configuration.<T>getMapper(type, this);
  }
}

public class Configuration {
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //调用MapperRegistry 中的getMapper
    return mapperRegistry.getMapper(type, sqlSession);
  }
}

public class MapperRegistry {
  @SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //从 MapperRegistry 中的 HashMap 中拿 MapperProxyFactory
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      //调用MapperProxyFactory的newInstance通过动态代理⼯⼚⽣成实例
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
}


public class MapperProxyFactory<T> {
  public T newInstance(SqlSession sqlSession) {
    //创建了 JDK动态代理的Handler类
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    //调⽤了重载⽅法
    return newInstance(mapperProxy);
  }
   
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
}

//MapperProxy 类,实现了 InvocationHandler 接⼝
public class MapperProxy<T> implements InvocationHandler, Serializable {
  //省略部分源码
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;
    
  //构造,传⼊了 SqlSession,说明每个session中的代理对象的不同的!  
  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }
  
   //省略部分源码
}

源码剖析-invoke()

在动态代理返回了示例后,我们就可以直接调⽤mapper类中的⽅法了,但代理对象调⽤⽅法,执⾏是在MapperProxy中的invoke⽅法中。

public class MapperProxy<T> implements InvocationHandler, Serializable { 
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //如果是Object定义的⽅法,直接调⽤
      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 对象
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //重点在这:最终调⽤了MapperMethod执⾏的⽅法
    return mapperMethod.execute(sqlSession, args);
  }
}

进⼊execute⽅法:

public class MapperMethod { 
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //判断mapper中的⽅法类型,最终调⽤的还是SqlSession中的⽅法 switch(command.getType()) 
    switch (command.getType()) {
      case INSERT: {
        //转换参数
        Object param = method.convertArgsToSqlCommandParam(args);
        //执⾏INSERT操作
        //转换rowCount
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        //转换参数
        Object param = method.convertArgsToSqlCommandParam(args);
        // 转换 rowCount
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        //转换参数
        Object param = method.convertArgsToSqlCommandParam(args);
        // 转换 rowCount
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        //⽆返回,并且有ResultHandler⽅法参数,则将查询的结果,提交给 ResultHandler 进⾏处理
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        //执⾏查询,返回列表
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        //执⾏查询,返回Map
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        //执⾏查询,返回Cursor
        } 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());
    }
    //返回结果为null,并且返回类型为基本类型,则抛出BindingException异常
    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;
  }
}

总的来说,MyBatis的Mapper代理方式通过动态代理机制,将接口方法与SQL语句进行映射,使得开发者能够更加方便地进行数据库操作,同时避免了繁琐的SQL映射配置。这种方式使得代码更加清晰简洁,并且提供了良好的可扩展性和可维护性。

相关文章:
MyBatis插件原理探究和自定义插件实现

MyBatis源码剖析之Configuration、SqlSession、Executor、StatementHandler细节

MyBatis源码剖析之二级缓存细节

MyBatis源码剖析之延迟加载源码细节

本文内容到此结束了,
如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。
如有错误❌疑问💬欢迎各位指出。
主页:共饮一杯无的博客汇总👨‍💻

保持热爱,奔赴下一场山海。🏃🏃🏃

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

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

相关文章

前端主题切换方案——CSS变量

前言 主题切换是前端开发中老生常谈的问题&#xff0c;本文将介绍主流的前端主题切换实现方案——CSS变量 CSS变量 简介 编写CSS样式时&#xff0c;为了避免代码冗余&#xff0c;降低维护成本&#xff0c;一些CSS预编译工具&#xff08;Sass/Less/Stylus&#xff09;等都支…

Java基础入门篇——Java注释、关键字和标识符(二)

目录 一、注释 1.1注释介绍 1.2单行注释 1.3多行注释 1.4文档注释 1.5javadoc注释标签语法 二、关键字 三、字面量 3.1什么是标识符&#xff1f; 3.2标识符命名规则 四、总结 一、注释 在Java中&#xff0c;注释是用来向代码添加解释、说明和文档信息的文本。Java…

网工这行最大的内卷,就是养了一批“HCIE”

下午好&#xff0c;我是老杨。 这段时间&#xff0c;我个人工作上的事儿挺多&#xff0c;一直在处理。 手底下的一个重要项目组&#xff0c;想招个新人&#xff0c;面了几批都不满意&#xff0c;难搞。 前两天刚定下人选&#xff0c;现下和你聊上几句。 上回说过&#xff0…

Killing LeetCode [82] 删除排序链表中的重复元素 II

Description 给定一个已排序的链表的头 head &#xff0c; 删除所有重复的元素&#xff0c;使每个元素只出现一次 。返回 已排序的链表 。 Intro Ref Link&#xff1a;https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/ Difficulty&#xff1a;Medium T…

怎么在树莓派环境上搭建web网站,并发布到外网可访问,今天教给大家

怎么在树莓派上搭建web网站&#xff0c;并发布到外网可访问&#xff1f; 文章目录 怎么在树莓派上搭建web网站&#xff0c;并发布到外网可访问&#xff1f;概述使用 Raspberry Pi Imager 安装 Raspberry Pi OS测试 web 站点安装静态样例站点 将web站点发布到公网安装 Cpolarcpo…

OpenStack监控工具

OpenStack是一个开源的云计算管理平台项目&#xff0c;是一系列软件开源项目的组合。由NASA和Rackspace合作研发并发起&#xff0c;以Apache许可证&#xff08;Apache软件基金会发布的一个自由软件许可证&#xff09;授权。 OpenStack为私有云和公有云提供可扩展的弹性的云计算…

mac电脑三维建模 Rhino 7中文 7.32

Rhino 7特别功能介绍 • 不受约束的自由形式 3D 建模工具&#xff0c;如仅在成本高出 20 到 50 倍的产品中发现的工具。为您可以想象的任何形状建模。 • 设计、原型、工程、分析和制造从飞机到珠宝的任何东西所需的准确性。 • 与您的所有其他设计、绘图、CAM、工程、分析、…

git使用(常见用法)

一.下载git git官方下载跳转 安装简单,有手就行 二. git的简单使用 1. 连接远程仓库 #初始化 git init #配置账户 git config --global user.name “输入你的用户名” git config --global user.email “输入你的邮箱” git config --list #--q退出 #配置验证邮箱 ssh-key…

vue2 引用swiper

参考引用 &#xff1a; vue使用swiper实现轮播图一页多张图片效果_swiper多张图片排列的轮播_空空的博客-CSDN博客 vue2 使用swiper_vue2使用swiper__NIXIAKF的博客-CSDN博客 常见的属性&#xff1a;Swiper 中常见的属性以及方法_swiper-wrapper_超勇的.的博客-CSDN博客 1、…

8月16日起!亚马逊新商品上架需更新产品类型的274个属性!

亚马逊美国站发布公告称为了帮助买家更轻松地搜索产品&#xff0c;改善买家的购买决策提高卖家的销量&#xff0c;8月16日起受影响的200种产品类型的274个属性在上架前需更新属性&#xff0c;以下是公告内容&#xff1a; 自2023年8月16日起&#xff0c;200种产品类型的274个属…

想在金融界拥有一席之地吗—社科院杜兰大学金融管理硕士助你圆梦

追求高学历是为了什么&#xff1f;一纸证书吗&#xff1f;显然并非如此&#xff0c;只有读过研的人才有话语权。在上升一个平台后&#xff0c;你必然会发现&#xff0c;更高学历得到的不止是一张文凭。而是更大的平台、更广阔的视野、更包容的环境&#xff0c;更多样的文化。最…

Clion开发Stm32之存储模块(W25Q64)驱动编写

前言 涵盖之前文章: Clion开发STM32之HAL库SPI封装(基础库) W25Q64驱动 头文件 #ifndef F1XX_TEMPLATE_MODULE_W25Q64_H #define F1XX_TEMPLATE_MODULE_W25Q64_H#include "sys_core.h" /* Private typedef ---------------------------------------------------…

【0805作业】Linux中 AB终端通过两根有名管道进行通信聊天(半双工)(全双工)

作业一&#xff1a;打开两个终端&#xff0c;要求实现AB进程对话【两根管道】 打开两个终端&#xff0c;要求实现AB进程对话 A进程先发送一句话给B进程&#xff0c;B进程接收后打印B进程再回复一句话给A进程&#xff0c;A进程接收后打印重复1.2步骤&#xff0c;当收到quit后&am…

最佳路径优先搜索算法

本来想直接写A* 的&#xff0c;不过看完最佳路径优先搜索算法后觉得还是要先理解一下这个算法后才能更好的理解A* 算法&#xff0c;所以把这篇文章放到A* 前面。 基本概念 最佳优先搜索算法&#xff08;Best-first-searching&#xff09;是一种启发式搜索算法&#xff08;Heu…

element vue2 动态添加 select+tree

难点在 1 添加一组一组的渲染 是往数组里push对象 循环的&#xff1b;但是要注意对象的结构! 因为这涉及到编辑完成后&#xff0c;表单提交时候的 校验&#xff01; 是校验每一个select tree里边 是否勾选 2 是在后期做编辑回显的时候 保证后端返回的值 是渲染到 select中的tr…

Markdown系列之Flowchat流程图

一.欢迎来到我的酒馆 介绍Markdown的Flowchart流程图语法。 目录 一.欢迎来到我的酒馆二.什么是Flowchart三.更进一步 二.什么是Flowchart 2.1 Flowchart是一款基于javascript的工具&#xff0c;使用它可以用代码创建简单的流程图。具体信息可以查看flowchart官网&#xff1a;…

vscode 设置滑条颜色

1. 默认的滑条是灰黑色的&#xff0c;很难看的清 2. 左下角&#xff0c;打开VS Code 设置功能 3. 输入命令 workbench color&#xff0c;回车 4. 找到工作台&#xff1a;自定义颜色设置&#xff0c;打开设置文件 setting.json 5. 打开配置文件 6. 添加颜色配置 "workben…

【C++】开源:事件驱动网络库libevent配置使用

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍事件驱动库libevent配置使用。 无专精则不能成&#xff0c;无涉猎则不能通。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xf…

C++——STL容器【priority_queue】模拟实现

本章代码&#xff1a;优先级队列模拟实现、priority_queue文档 文章目录 &#x1f408;1. priority_queue介绍&#x1f984;2. priority_queue模拟实现&#x1f427;2.1 构造函数&#x1f427;2.2 建堆向下调整向上调整 &#x1f427;2.3 仿函数&#x1f427;2.4 push & po…

通向架构师的道路之漫谈使用ThreadLocal改进你的层次的划分

一、什么是ThreadLocal 早在JDK 1.2的版本中就提供java.lang.ThreadLocal&#xff0c;ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。 ThreadLocal很容易让人望文生义&#xff0c;想当然地认为是一个“本地线…