⛳ MyBatis 中 Mapper 接口工作原理实例解析

news2024/11/21 11:02:48

🎍目录

  • ⛳ MyBatis 中 Mapper 接口工作原理实例解析
    • 🎨 一、Mapper 接口是怎么找到实现类的?
    • 🐾 二、从一段代码看起
    • 🚜 三、Mapper 接口
    • 🏭 四、Mapper 接口的动态代理类的生成
    • 🎁 五、总结

⛳ MyBatis 中 Mapper 接口工作原理实例解析

本篇文章主要介绍了MyBatis Mapper接口工作源里实例解析,文中通过示例代码介绍的非常详细;

KeyWords: Mybatis 原理,源码,Mybatis Mapper 接口实现类,代理模式,动态代理,Java动态代理,Proxy.newProxyInstance,Mapper 映射,Mapper 实现

MyBatis 是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。我们在使用 Mybaits 进行 ,通常只需要定义几个 Mapper 接口,然后在编写一个 xml 文件,我们在配置文件中写好 sql , Mybatis 帮我们完成 Mapper 接口道具体实现的调用。以及将结果映射到 model bean 中。

我们在项目中所编写的众多的 Mapper 类只是一个接口(interface ),根据 Java 的多态性我们知道,可以使用接口接口作为形参,进而在运行时确定具体实现的对象是什么。但是,对于 Mapper 接口,我们并没有编写其实现类!Mybatis是如何找到其实现类,进而完成具体的 CRUD 方法调用的呢?原理何在?

🎨 一、Mapper 接口是怎么找到实现类的?

为了弄清楚 Mapper 接口是如何找到实现类的,我们先回忆一下 Mybatis 是怎么使用的,根据实际的例子,进而一点点的去分析。这里的使用指的是Mybatis 单独使用,而不是整合 spring , 因为整合 spring 的话,还需要涉及 Mapper dao 装载到 spring 容器的问题,spring 帮忙创建数据源配置等问题。

通常我们使用 Mybatis 的主要步骤是:

  • 构建 SqlSessionFactory ( 通过 xml 配置文件 , 或者直接编写Java代码)
  • 从 SqlSessionFactory 中获取 SqlSession
  • 从SqlSession 中获取 Mapper
  • 调用 Mapper 的方法 ,例如:blogMapper.selectBlog(int blogId)

🐾 二、从一段代码看起

上面我们概括了使用 Mybatis 的4个步骤。这4个步骤看起来很简单,但是用代码写出来就很多。我们不妨先记着这4个步骤,再去看代码,会容易点。

// 1. 
DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);// 添加Mapper接口
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
// 2. 
SqlSession session = sqlSessionFactory.openSession();
try {
 // 3. 
 BlogMapper mapper = session.getMapper(BlogMapper.class);
 // 4.
 Blog blog = mapper.selectBlog(1);
} finally {
 session.close();
}

在这块代码中,第 1 部分我们使用了 Java 编码的形式来实现 SqlSessionFactory ,也可以使用 xml 。如果使用xml的话,上面的第一部分代码就是这样的:

String resource = "org/mybatis/example/mybatis-config.xml"; // xml内容就不贴了
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

我们本次的目标是弄清楚 “ Mapper 是如何找到实现类的 ”,我们注意上面代码 3 , 4 的位置:

 // 3. 
 BlogMapper mapper = session.getMapper(BlogMapper.class);
 // 4.
 Blog blog = mapper.selectBlog(1);

**这里 mapper 可以调用selectBlog(1) 这个方法,说明 mapper 是个对象,因为对象才具有方法行为实现啊。**BlogMapper接口是不能实例化的,更没有具体方法实现。我们并没有定义一个类,让它实现BlogMapper接口,而在这里它只是通过调用session.getMapper() 所得到的。由此,我们可以推断:肯定是session.getMapper() 方法内部产生了BlogMapper的实现类。有什么技术可以根据BlogMapper 接口生成了一个实现类呢?想到这里,对于有动态代理 使用经验的程序员来说,很容易想到,这背后肯定是基于动态代理技术,具体怎么实现的呢?下面我们来根据源码一探究竟。

🚜 三、Mapper 接口

从上面的代码中,我们知道 BlogMapper 接口的实现类是从session.getMapper中得来的,大概是基于动态代理技术实现。我们既然能够从SqlSession中得到BlogMapper接口的,那么我们肯定需要先在哪里把它放进去了,然后 SqlSession 才能生成我们想要的代理类啊。上面代码中有这么一行:

configuration.addMapper(BlogMapper.class);

跟着这个 addMapper 方法的代码实现是这样的:

 public <T> void addMapper(Class<T> type) {
  mapperRegistry.addMapper(type);
 }

我们看到这里 mapper 实际上被添加到 mapperRegissry 中。继续跟进代码:

public class MapperRegistry {
 private final Map<Class<?>, MapperProxyFactory<?>> knownMappers 
                         = new HashMap<Class<?>, 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)); // 注意这里
 
    MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
    parser.parse();
    loadCompleted = true;

   } finally {
    if (!loadCompleted) {
     knownMappers.remove(type);
    }
   }
  }
 }
}

看到这里我们知道上面所执行的configuration.addMapper(BlogMapper.class); 其实最终被放到了HashMap中,其名为knownMappers ,knowMappers是MapperRegistry 类的一个私有属性,它是一个HashMap 。其Key 为当前Class对象,value 为一个MapperProxyFactory 实例。

这里我们总结一下: 诸如BlogMapper 之类的Mapper接口被添加到了MapperRegistry 中的一个HashMap中。并以 Mapper 接口的 Class 对象作为 Key , 以一个携带Mapper接口作为属性的MapperProxyFactory 实例作为value 。MapperProxyFacory从名字来看,好像是一个工厂,用来创建Mapper Proxy的工厂。我们继续往下看。

🏭 四、Mapper 接口的动态代理类的生成

上面我们已经知道,Mapper 接口被到注册到了MapperRegistry中——放在其名为knowMappers 的HashMap属性中,我们在调用Mapper接口的方法的时候,是这样的:

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

这里,我们跟踪一下session.getMapper() 方法的代码实现,这里 SqlSession 是一个接口,他有两个实现类,一个是DefaultSqlSession,另外一个是SqlSessionManager,这里我们用的是DefaultSqlSession. 为什么是DefaultSqlSession呢?因为我们在初始化SqlSessionFactory的时候所调用的SqlSessionFactoryBuilder的build()方法里边配置的就是DefaultSqlSession, 所以,我们进入到DefaultSession类中,看看它对session.getMapper(BlogMapper.class)是怎么实现的:

public class DefaultSqlSession implements SqlSession {
 private Configuration configuration; 
  @Override
 public <T> T getMapper(Class<T> type) {
  return configuration.<T>getMapper(type, this); //最后会去调用MapperRegistry.getMapper
 }
}

如代码所示,这里的 getMapper 调用了 configuration.getMapper , 这一步操作其实最终是调用了MapperRegistry,而此前我们已经知道,MapperRegistry是存放了一个HashMap的,我们继续跟踪进去看看,那么这里的get,肯定是从这个hashMap中取数据。我们来看看代码:

public class MapperRegistry {
 
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();// Mapper 映射
 
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  final MapperProxyFactory<T> mapperProxyFactory =
                 (MapperProxyFactory<T>) knownMappers.get(type);
  try {
   return mapperProxyFactory.newInstance(sqlSession); // 重点看这里
  } catch (Exception e) {
  }
 }
}

我们调用的session.getMapper(BlogMapper.class);最终会到达上面这个方法,这个方法,根据BlogMapper的class对象,以它为key在knowMappers 中找到了对应的value —— MapperProxyFactory(BlogMapper) 对象,然后调用这个对象的newInstance()方法。根据这个名字,我们就能猜到这个方法是创建了一个对象,代码是这样的:

public class MapperProxyFactory<T> { //映射器代理工厂

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

 public MapperProxyFactory(Class<T> mapperInterface) {
  this.mapperInterface = mapperInterface;
 }
 // 删除部分代码,便于阅读

 @SuppressWarnings("unchecked")
 protected T newInstance(MapperProxy<T> mapperProxy) {
  //使用了JDK自带的动态代理生成映射器代理类的对象
  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);
 }

}

看到这里,就清楚了,最终是通过Proxy.newProxyInstance产生了一个BlogMapper的代理对象。Mybatis 为了完成 Mapper 接口的实现,运用了代理模式。具体是使用了JDK动态代理,这个Proxy.newProxyInstance方法生成代理类的三个要素是:

  • ClassLoader —— 指定当前接口的加载器即可
  • 当前被代理的接口是什么 —— 这里就是 BlogMapper
  • 代理类是什么 —— 这里就是 MapperProxy

代理模式中,代理类(MapperProxy)中才真正的完成了方法调用的逻辑。我们贴出MapperProxy的代码,如下:

public class MapperProxy<T> implements InvocationHandler, Serializable {// 实现了InvocationHandler
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  //代理以后,所有Mapper的方法调用时,都会调用这个invoke方法
  if (Object.class.equals(method.getDeclaringClass())) {
   try {
    return method.invoke(this, args); // 注意1
   } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
   }
  }
  final MapperMethod mapperMethod = cachedMapperMethod(method); // 使用了缓存
  //执行CURD
  return mapperMethod.execute(sqlSession, args); // 注意2
 }  
}

我们调用的 Blog blog = mapper.selectBlog(1); 实际上最后是会调用这个MapperProxy的invoke方法。这段代码中,if 语句先判断,我们想要调用的方法是否来自Object类,这里的意思就是,如果我们调用toString()方法,那么是不需要做代理增强的,直接还调用原来的method.invoke()就行了。只有调用selectBlog()之类的方法的时候,才执行增强的调用——即mapperMethod.execute(sqlSession, args);这一句代码逻辑。

而mapperMethod.execute(sqlSession, args);这句最终就会执行增删改查了,代码如下:

 public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  if (SqlCommandType.INSERT == command.getType()) {     //insert 处理,调用SqlSession的insert
   Object param = method.convertArgsToSqlCommandParam(args);
   result = rowCountResult(sqlSession.insert(command.getName(), param));
  } else if (SqlCommandType.UPDATE == command.getType()) { // update
   Object param = method.convertArgsToSqlCommandParam(args);
   result = rowCountResult(sqlSession.update(command.getName(), param));
  } else if (SqlCommandType.DELETE == command.getType()) {  // delete
   Object param = method.convertArgsToSqlCommandParam(args);
   result = rowCountResult(sqlSession.delete(command.getName(), param));
  } else if (SqlCommandType.SELECT == command.getType()) {
   // 删除部分代码 
  } else {
   throw new BindingException("Unknown execution method for: " + command.getName());
  }
   // 删除部分代码
  return result;
 }

再往下一层,就是执行JDBC那一套了,获取链接,执行,得到ResultSet,解析ResultSet映射成JavaBean。

至此,我们已经摸清楚了Blog blog = mapper.selectBlog(1); 中,BlogMapper接口调用到得到数据库数据过程中,Mybaitis 是如何为接口生成实现类的,以及在哪里出发了最终的CRUD调用。实际上,如果我们在调用Blog blog = mapper.selectBlog(1);之前,把从slqSession中得到的 mapper 对象打印出来就会看到,输出大概是这样的:

com.sun.proxy.$Proxy17

动态代理没错吧,Java动态代理实在是太美妙了。

🎁 五、总结

上面我们用层层深入的方式摸清楚了 Mapper接口是如何找到实现类的。我们分析了 Mapper接口是如何注册的,Mapper接口是如何产生动态代理对象的,Maper接口方法最终是如何执行的。总结起来主要就是这几个点:

  1. Mapper 接口在初始SqlSessionFactory 注册的。

  2. Mapper 接口注册在了名为 MapperRegistry 类的 HashMap中, key = Mapper class value = 创建当前Mapper的工厂。

  3. Mapper 注册之后,可以从SqlSession中get

  4. SqlSession.getMapper 运用了 JDK动态代理,产生了目标Mapper接口的代理对象。

  5. 动态代理的 代理类是 MapperProxy ,这里边最终完成了增删改查方法的调用。

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

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

相关文章

24字符串-kmp寻找重复子串

目录 字符串匹配——kmp算法 LeetCode之路——459. 重复的子字符串 分析&#xff1a; 字符串匹配——kmp算法 强烈建议参考Carl的讲解&#xff1a; 视频讲解版&#xff1a;帮你把KMP算法学个通透&#xff01;&#xff08;理论篇&#xff09;(opens new window) 视频讲解版&…

使用Plotly可视化

显示项目受欢迎程度 改进图表 设置颜色&#xff0c;字体

400电话-400电话办理-400号码办理

400电话是一种特殊的电话号码&#xff0c;以"400"开头&#xff0c;通常用于企业客户服务、售后支持等方面。随着互联网的发展&#xff0c;越来越多的企业开始意识到400电话的重要性&#xff0c;并积极办理400号码。 首先&#xff0c;办理400电话可以提升企业形象和信…

【音视频|ALSA】SS528开发板编译Linux内核ALSA驱动、移植alsa-lib、采集与播放usb耳机声音

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

ArcGIS笔记4_水动力模型验证不理想时如何修改局部水深地形

本文目录 前言Step 1 模型验证不理想的情况Step 2 修改确值点并重新插值 前言 本章主要服务于MIKE水动力模型的调整修改工作。水动力模型跑完之后&#xff0c;常常会出现验证结果不理想的情况&#xff0c;比如潮位验证中&#xff0c;实测站点数据与模拟数据相差很大&#xff0…

【计算机组成体系结构】移码 | 定点小数的表示和运算

一、移码 上篇我们提到了原码&#xff0c;反码和补码的表示形式和如何转换。这篇我们会提到一个新的概念—移码。移码也很简单&#xff0c;其实就是在补码的基础上把符号取反即可。 值得注意的是&#xff0c;移码只能表示整数。而原码&#xff0c;反码和补码既可以表示整数又…

软件工程与计算总结(十一)人机交互设计

目录 ​编辑 一.引例 二.目标 三.人类因素 1.精神模型 2.差异性 四.计算机因素 1.可视化设计 2.常见界面类型 五.人机交互设计的交互性 1.导航 2.反馈 3.设计原则 六.设计过程 1.基本过程 2.界面原型化 一.引例 无论软件功能多么出色&#xff0c;亦或内部的构造…

具有标记和笔记功能的文件管理器TagSpaces

什么是 TagSpaces &#xff1f; TagSpaces 是一款免费、无供应商锁定的开源应用程序&#xff0c;用于借助标签组织、注释和管理本地文件。它具有高级笔记功能和待办事项应用程序的一些功能。该应用程序适用于 Windows、Linux、Mac OS 和 Android。并已经为 Firefox、Edge 和 Ch…

【Android知识笔记】图片专题(BitmapDrawable)

如何计算一张图片的占用内存大小? 注意是占用内存,不是文件大小可以运行时获取重要的是能直接掌握计算方法基础知识 Android 屏幕像素密度分类: (其实还有一种 ldpi = 120,不过这个已经绝种了,所以最低的只需关心mdpi即可) 上表中的比例为:m : h : xh : xxh: xxxh = …

java springboot 通过ConfigurationProperties给第三方bean注入属性

之前我们的文章 java boot将一组yml配置信息装配在一个对象中 讲过了 通过ConfigurationProperties将配置文件中的内容默认装配进属性类 但 这建立在 bean是自己定义的 如果 这是个第三方的类呢&#xff1f; 就比如 我们在 application 中写了一套数据源的加载规则 但需要用第…

无法启动此程序,因为计算机中丢失MSVCR71.dll的详细解决修复方法

大家好&#xff01;今天我来给大家分享一下msvcp71.dll丢失的修复方法。 首先&#xff0c;让我们来了解一下msvcp71.dll文件。msvcp71.dll是一个动态链接库文件&#xff0c;它是Microsoft Visual C 2010 Redistributable Package所包含的一个文件。这个文件被许多软件和游戏需…

Web攻防01-ASP应用相关漏洞-HTTP.SYSIIS短文件文件解析ACCESS注入

文章目录 ASP-默认安装-MDB数据库泄漏下载漏洞漏洞描述 ASP-中间件 HTTP.SYS&#xff08;CVE-2015-1635&#xff09;1、漏洞描述2、影响版本3、漏洞利用条件4、漏洞复现 ASP-中间件 IIS短文件漏洞1、漏洞描述2、漏洞成因:3、应用场景&#xff1a;4、利用工具&#xff1a;5、漏洞…

计算机毕业设计选什么题目好?springboot 研究生管理系统

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

普冉PY32系列(八) GPIO模拟和硬件SPI方式驱动无线收发芯片XN297LBW

目录 普冉PY32系列(一) PY32F0系列32位Cortex M0 MCU简介普冉PY32系列(二) Ubuntu GCC Toolchain和VSCode开发环境普冉PY32系列(三) PY32F002A资源实测 - 这个型号不简单普冉PY32系列(四) PY32F002A/003/030的时钟设置普冉PY32系列(五) 使用JLink RTT代替串口输出日志普冉PY32…

java气候分析平台天气预报系统springboot+vue

保护措施 (自动编号、图片、措施简介、措施地点、措施时间、创建时间、措施详情、措施名称)&#xff1b; 报名信息 (自动编号、活动地点、图片、活动名称、活动时间、参与人数、活动详情、审核回复、创建时间、报名时间、手机、活动简介、是否审核、姓名、账号)&#xff1b; 配…

在Node.js项目中使用node-postgres连接postgres以及报错指南

什么是node-postgres 官方文档 nodepostgres是node.js模块的集合&#xff0c;用于与PostgreSQL数据库接口。它支持回调、promise、async/await、连接池、准备好的语句、游标、流式结果、C/C绑定、富类型解析等等&#xff01;就像PostgreSQL本身一样&#xff0c;它有很多功能&a…

下载Python的不同版本在同一台电脑上如何共存

1. 下载安装不同版本的Python 官网下载&#xff1a;https://www.python.org/downloads/安装自己需要的版本&#xff08;我这里以Python3.6和Python3.9为例&#xff0c;下载安装细节不过多赘述&#xff09; &#xff08;这里的安装路径自己设定&#xff0c;命名最好是根据下载…

【MySQL】事务四大特性ACID、并发事务问题、事务隔离级别

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 Redis 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 MySQL 一、事务四大特性ACID1.1 原子性1.2 …

深入探究基于发布/订阅模式的轻量级消息传输协议 MQTT

目录 1、什么是 MQTT&#xff1f; 1.1、MQTT 与 HTTP 比较 1.2、 MQTT 与 XMPP 比较 2、MQTT 可以干啥&#xff1f; 3、 MQTT 协议特性详解 3.1、轻量高效&#xff0c;节省带宽 3.2、可靠的消息传递 3.3、海量连接支持 3.4、安全的双向通信 3.5、在线状态感知 4、MQ…

【数据库系统概论】第四章数据库安全性

数据库的安全性&#xff1a;保护数据库以防止不合法使用所造成的数据泄露、更改或破坏 grant和revoke语法