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

news/2024/4/27 19:09:16/文章来源:https://blog.csdn.net/weixin_53456849/article/details/133830354

🎍目录

  • ⛳ 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; @Overridepublic <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@Overridepublic 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); // 使用了缓存//执行CURDreturn 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的insertObject param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.insert(command.getName(), param));} else if (SqlCommandType.UPDATE == command.getType()) { // updateObject param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.update(command.getName(), param));} else if (SqlCommandType.DELETE == command.getType()) {  // deleteObject 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.luyixian.cn/news_show_560592.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

rhel8 nmcli学习

rhel8我自己用过的配置网路方法有以下几个&#xff1a; &#xff08;1)手动配置ifcfg文件&#xff0c;通过NM来生效。 (2)手动配置ifcfg文件&#xff0c;通过重启NetworkManager.service生效。 (3)通过NM自带工具配置网络&#xff0c;比如nmcli。 (4)使用命令 nutui命令&am…

Windows 移动设备管理

Windows 设备管理是指一组流程和工具&#xff0c;可帮助 IT 管理员简化企业中使用的Windows 设备的管理。管理企业中使用的 Windows 设备最好通过实施Windows MDM 解决方案来完成&#xff0c;以从单个控制台保护、管理和监视这些设备。Windows移动设备管理 &#xff08;MDM&…

爬虫 | 基础模块了解

文章目录 &#x1f4da;http协议&#x1f4da;requests模块&#x1f4da;re模块&#x1f407; re.I 或 re.IGNORECASE&#x1f407;re.M或 re.MULTILINE&#x1f407;re.S 或 re.DOTALL&#x1f407; re.A 或 re.ASCII&#x1f407; re.X 或 re.VERBOSE&#x1f407;特殊字符类…

YOLOv5算法改进(11)— 主干网络介绍(MobileNetV3、ShuffleNetV2和GhostNet)

前言:Hello大家好,我是小哥谈。主干网络通常指的是深度学习中的主干模型,通常由多个卷积层和池化层组成,用于提取输入数据的特征。在训练过程中,主干网络的参数会被不断优化以提高模型的准确性。YOLOv5算法中的主干网络可以有多种替换方案,为了后面讲解的方便,本篇文章就…

SystemVerilog Assertions应用指南 第一章(1.25章节 “throughout”运算符)

蕴含( (implication)是目前讨论到的允许定义前提条件的一项技术。例如,要对一个指定的序列进行检验,必须某个前提条件为真。也有这样的情况,要求在检验序列的整个过程中,某个条件必须一直为真。蕴含只在时钟边沿检验前提条件一次,然后就开始检验后续算子部分,因此它不检测先行算…

gpt4.0和3.5区别在哪里:什么样的人适合使用gpt4.0?

GPT-4和GPT-3.5相比&#xff0c;就像是智能手机的新一代和上一代。升级了&#xff0c;功能多了&#xff0c;但核心都是让你的工作和生活更便捷。 联网能力 GPT-3.5&#xff1a;与世隔绝&#xff0c;不联网。 GPT-4&#xff1a;5月17日后能上网浏览&#xff0c;信息获取能力加…

GB28181学习(七)——设备视音频文件检索

要求 文件检索主要用于区域、设备、录像时间段、录像地点、录像报警为条件的查询&#xff1b;用Message消息发送检索请求和返回查询结果&#xff0c;传送结果的Message消息可以发送多条&#xff1b;文件检索请求和应答命令采用MANSCDP协议格式定义&#xff1b; 流程 目录检索…

车载电子电器架构 —— 国产基础软件现在与未来

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 屏蔽力是信息过载时代一个人的特殊竞争力&#xff0c;任何消耗你的人和事&#xff0c;多看一眼都是你的不…

PowerShell系列(十二):PowerShell Cmdlet高级参数介绍(二)

目录 1、ErrorVariable 错误变量 2、OutVariable 结果输出 3、OutBuffer 输出Buffer定义 4、PipelineVariable管道参数 今天给大家讲解PowerShell Cmdlet高级参数第二部分相关的知识&#xff0c;希望对大家学习PowerShell能有所帮助&#xff01; 1、ErrorVariable 错误变量…

芯片学习记录AM26LS31INSR

AM26LS31INSR 芯片介绍 AM26LS31 系列器件是四路互补输出线路驱动器&#xff0c;可 满足 ANSI TIA/EIA-422-B 和 ITU &#xff08;原 CCITT &#xff09;建议 V.11 的要求。三态输出可提供用于驱动双绞线或平行 双线传输线路等平衡线路的高电流&#xff0c;并在断电情况下处…

win11下的VS2022+QT6+VTK9.2+PCL1.13.1联合开发环境配置及踩坑记录

准备工作&#xff1a; 安装VS2022&#xff1a;这个比较简单&#xff0c;网上随便找个教程就行 安装QT并为VS2022添加QT Creater插件&#xff1a;VS2022配置Qt6_vs2022 qt6-CSDN博客 安装PCL&#xff1a;vs2022配置pcl1.13.1_pcl配置-CSDN博客 安装PCL过程中本身也会安装VTK&…

六、DHCP实验

拓扑图&#xff1a; DHCP协议&#xff0c;给定一个ip范围使其自动给终端分配IP&#xff0c;提高了IP分配的效率 首先对PC设备选择DHCP分配ip 首先先对路由器的下端配置网关的ip 创建地址池&#xff0c;通过globle的方式实现DHCP ip pool 地址池名称 之后设置地址池的网关地址…

VS Code:CMake配置

概述 在VSCode和编译器MinGW安装完毕后&#xff0c;要更高效率的进行C/C开发&#xff0c;采用CMake。CMake是一个开源、跨平台的编译、测试和打包工具&#xff0c;它使用比较简单的语言描述编译&#xff0c;安装的过程&#xff0c;输出Makefile或者project文件&#xff0c;再去…

两种方式获取Stream流的方式

java.util.stream.Stream<T> 是Java 8 新加入的最常用的流接口。&#xff08;这并不是一个函数式接口&#xff09;获取一个流有以下两种方式 所有的 Collection集合 都可以通过stream默认方法获取流 Stream接口 的静态方法of可以获取数组对应的流 package com.csdn.s…

AI对网络安全的影响与挑战

近年来&#xff0c;随着人工智能&#xff08;AI&#xff09;技术的快速发展&#xff0c;网络安全领域也开始逐渐引入生成式AI应用。根据最新的数据研究&#xff0c;生成式AI对网络安全和合规的影响最大&#xff0c;同时也包括了IT和云的运维、硬件和软件支持领域。通过AI和自动…

Folium 笔记:使用PopUp突出subzone的空间分布

0 效果图 点开某一个区域后&#xff0c;内容是这个区域的用地类型分布 1 读取数据 import folium import matplotlib.pyplot as plt import re import geopandas as gpd subzonegpd.read_file(MasterPlan2019PlanningAreaBoundaryNoSea.geojson) subzone 2 提取subzone 信息 …

SystemVerilog Assertions应用指南 第一章(1.27章节 “within”运算符)

“ within”构造允许在一个序列中定义另一个序列。 seq1 within seq2 这表示seq1在seq2的开始到结束的范围内发生,且序列seq2的开始匹配点必须在seq1的开始匹配点之前发生,序列seq1的结束匹配点必须在seq2的结束匹配点之前结束。属性p32检查序列s32a在信号“ start”的上升沿和…

C++笔记之获取线程ID以及线程ID的用处

C笔记之获取线程ID以及线程ID的用处 code review! 文章目录 C笔记之获取线程ID以及线程ID的用处一.获取ID二.线程ID的用处2.1.线程池管理2.2.动态资源分配2.3.使用线程同步机制实现互斥访问共享资源2.4.使用线程 ID 辅助线程同步2.5.任务分发&#xff1a;线程ID可以用于将任务…

阿里云服务器不能访问网络之安装mysql 提示连接超时

wget -i -c http://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpm 过了一段时间后提示 fail .......time out 链接超时 有可能你的服务器不能访问网络 因为宽带套餐 我购买的时候没有购 重新购买就行了

分布式链路追踪如何跨线程

背景 我们希望实现全链路信息&#xff0c;但是代码中一般都会异步的线程处理。 解决思路 我们可以对以前的 Runable 和 Callable 进行增强。 可以使用 ali 已经存在的实现方式。 TransmittableThreadLocal (TTL) 解决异步执行时上下文传递的问题 核心的实现思路如下&#…