前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >『手写Mybatis』创建简单的映射器代理工厂

『手写Mybatis』创建简单的映射器代理工厂

原创
作者头像
程序员NEO
修改于 2024-06-02 23:50:52
修改于 2024-06-02 23:50:52
501
举报

前言

在阅读本文之前,我相信你已经是一个 Mybatis ORM 框架工具使用的熟练工了,那你是否清楚这个 ORM 框架是怎么屏蔽我们对数据库操作的细节的?

比如我们使用 JDBC 的时候,需要手动建立数据库链接、编码 SQL 语句、执行数据库操作、自己封装返回结果等。

但在使用 ORM 框架后,只需要通过简单配置即可对定义的 DAO 接口进行数据库的操作了。

设计

通常如果能找到大家所在事情的共性内容,具有统一的流程处理,那么它就是可以被凝聚和提炼的,做成通用的组件或者服务,被所有人进行使用,减少重复的人力投入。

而参考我们最开始使用 JDBC 的方式,从连接、查询、封装、返回,其实都一个固定的流程,那么这个过程就可以被提炼以及封装和补全大家所需要的功能。

当我们来设计一个 ORM 框架的过程中,首先要考虑怎么把用户定义的数据库操作接口、xml 配置的 SQL 语句、数据库三者联系起来。其实最适合的操作就是使用代理的方式进行处理,因为代理可以封装一个复杂的流程,来作用于接口的实现类,设计图如下:

来看一下这个设计图的流程:

  1. 首先提供一个映射器的代理实现类 MapperProxy,通过代理类包装对数据库的操作,目前我们本章节会先提供一个简单的包装,模拟对数据库的调用。
  2. 之后对 MapperProxy 代理类,提供工厂实例化操作 MapperProxyFactory#newInstance #newInstance 意思是 MapperProxyFactory 中有一个 newInstance 方法,用来创建 MapperProxy 实例,为每个 IDAO 接口生成代理类。这块其实用到的就是一个简单工厂模式。

接下来我们就按照这个设计实现一个简单的映射器代理操作,编码过程比较简单。如果对代理知识不熟悉可以先补充下代理的知识。

实现

工程结构

代码语言:java
AI代码解释
复制
step-01
└─src
    ├─main
    │  ├─java
    │  │  └─top
    │  │      └─it6666
    │  │          └─mybatis
    │  │              └─binding
    │  └─resources
    └─test
        └─java
            └─top
                └─it6666
                    └─test
                        └─dao

工程源码:https://github.com/BNTang/Java-All/tree/main/mybatis-source-code/step-01

MyBatis 映射器代理类关系:

  • 目前这个 MyBatis 框架的代理操作实现的还只是最核心的功能,相当于是光屁股的娃娃,还没有添加衣服。不过这样渐进式的实现可以让大家先了解到最核心的内容,后续我们在陆续的完善。
  • MapperProxy 负责实现 InvocationHandler 接口的 invoke 方法,最终所有的实际调用都会调用到这个方法包装的逻辑。
  • MapperProxyFactory 是对 MapperProxy 的包装,对外提供实例化对象的操作。

当我们后面开始给每个操作数据库的接口映射器注册代理的时候,就需要使用到这个工厂类了。

映射器代理类

源码详见:top.it6666.mybatis.binding.MapperProxy

代码语言:java
AI代码解释
复制
/**
 * 映射器代理类
 *
 * @author BNTang
 */
public class MapperProxy<T> implements InvocationHandler, Serializable {

    private static final long serialVersionUID = -6424540398559729838L;

    private final Map<String, String> sqlSession;
    private final Class<T> mapperInterface;

    public MapperProxy(Map<String, String> sqlSession, Class<T> mapperInterface) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else {
            return "你的被代理了!" + sqlSession.get(mapperInterface.getName() + "." + method.getName());
        }
    }
}
  1. 通过实现 InvocationHandler#invoke(#后面紧跟的是方法名,意思说的是 InvocationHandler 接口中的 invoke 方法)代理类接口,封装操作逻辑的方式,对外接口提供数据库操作对象。
  2. 目前这里只是简单的封装了一个 sqlSessionMap 对象,你可以想象成所有的数据库语句操作,都是通过 接口名称+方法名 称作为 key,操作作为逻辑的方式进行使用的。那么在反射调用中则获取对应的操作直接执行并返回结果即可。当然这还只是最核心的简化流程,后续不断补充内容后,会看到对数据库的操作。
  3. 另外这里要注意如果是 Object 提供的 toString、hashCode 等方法是不需要代理执行的,所以添加 Object.class.equals(method.getDeclaringClass()) 判断。

代理类工厂

源码详见:top.it6666.mybatis.binding.MapperProxyFactory

代码语言:java
AI代码解释
复制
/**
 * 代理类工厂
 * @author BNTang
 */
public class MapperProxyFactory<T> {
    private final Class<?> mapperInterface;

    public MapperProxyFactory(Class<?> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public T newInstance(Map<String, String> sqlSession) {
        final MapperProxy<T> mapperProxy = (MapperProxy<T>) new MapperProxy<>(sqlSession, mapperInterface);
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
    }
}
  1. 工厂操作相当于把代理的创建给封装起来了,如果不做这层封装,那么每一个创建代理类的操作,都需要自己使用 Proxy.newProxyInstance 进行处理,那么这样的操作方式就显得比较麻烦了。
  2. 另外如果你对代理不是太熟悉,可以着重把 JDK Proxy 的内容做几个案例补充下这块的内容,或者可以去看看我之前写的代理的文章。(https://www.cnblogs.com/BNTang/articles/13769281.html

测试

事先准备

top.it6666.test.dao.IUserDao:

代码语言:java
AI代码解释
复制
public interface IUserDao {
    String queryUserName(String uId);

    Integer queryUserAge(String uId);
}
  • 首先提供一个 DAO 接口,并定义2个接口方法,一个是查询用户名称,一个是查询用户年龄。

测试用例

top.it6666.test:

代码语言:java
AI代码解释
复制
/**
 * @author BNTang
 * @version 1.0
 * @description 测试类
 * @since 2024/4/16 星期二
 **/
public class ApiTest {
    private final Logger logger = LoggerFactory.getLogger(ApiTest.class);

    @Test
    public void test_MapperProxyFactory() {
        MapperProxyFactory<IUserDao> factory = new MapperProxyFactory<>(IUserDao.class);
        Map<String, String> sqlSession = new HashMap<>();

        sqlSession.put("top.it6666.test.dao.IUserDao.queryUserName", "模拟执行 Mapper.xml 中 SQL 语句的操作:查询用户姓名");
        sqlSession.put("top.it6666.test.dao.IUserDao.queryUserAge", "模拟执行 Mapper.xml 中 SQL 语句的操作:查询用户年龄");
        IUserDao userDao = factory.newInstance(sqlSession);

        String res = userDao.queryUserName("10001");
        logger.info("测试结果:{}", res);
    }
}
  • 在单测中创建 MapperProxyFactory 工厂,并手动给 sqlSession Map 赋值,这里的赋值相当于模拟数据库中的操作。
  • 接下来再把赋值信息传递给代理对象实例化操作,这样就可以在我们调用具体的 DAO 方法时从 sqlSession 中取值了。

测试结果:

代码语言:java
AI代码解释
复制
00:33:41.903 [main] INFO  top.it6666.test.ApiTest - 测试结果:你的被代理了!模拟执行 Mapper.xml 中 SQL 语句的操作:查询用户姓名

从测试结果可以看到,我们的接口已经被代理类实现了,同时我们可以在代理类中进行自己的操作封装。那么在我们后续实现的数据库操作中,就可以对这部分内容进行扩展了。

总结

  • 本章节我们初步对 MyBatis 框架中的数据库 DAO 操作接口和映射器通过代理类的方式进行链接,这一步也是 ORM 框架里非常核心的部分。有了这块的内容,就可以在代理类中进行自己逻辑的扩展了。
  • 在框架实现方面引入简单工厂模式包装代理类,屏蔽创建细节,这些也是大家在学习过程中需要注意的设计模式的点。
  • 目前内容还比较简单的,可以手动操作练习,随着我们内容的增加,会有越来越多的包和类引入,完善 ORM 框架功能。

结束语

着急和快,是最大的障碍!慢下来,慢下来,只有慢下来,你才能看到更全的信息,才能学到更扎实的技术。而那些满足你快的短篇内容虽然有时候更抓眼球,但也容易把人在技术学习上带偏,总想着越快越好。

如果您觉得文章对您有所帮助,欢迎您点赞、评论、转发,也欢迎您关注我的公众号『BNTang』,我会在公众号中分享更多的技术文章。

我正在参与2024腾讯技术创作特训营最新征文,快来和我瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
1 条评论
热度
最新
操练起来,唐老师
操练起来,唐老师
回复回复点赞举报
推荐阅读
《Mybatis 手撸专栏》第2章:创建简单的映射器代理工厂
慢下来,慢下来,只有慢下来,你才能看到更全的信息,才能学到更扎实的技术。而那些满足你快的短篇内容虽然有时候更抓眼球,但也容易把人在技术学习上带偏,总想着越快越好。
小傅哥
2022/04/19
3180
《Mybatis 手撸专栏》第2章:创建简单的映射器代理工厂
Mybatis 手撸专栏|第2章:创建简单的映射器代理工厂
大家好!欢迎来到《Mybatis 手撸专栏》的第2章。在上一章中,我们介绍了 Mybatis 的基本原理和实现过程。本章将深入探讨如何创建一个简单的映射器(Mapper)代理工厂。
啵啵肠
2023/11/06
2250
《Mybatis 手撸专栏》第3章:实现映射器的注册和使用
我们可以把 Spring、Mybatis、Dubbo 这样的大型框架或者一些公司内部的较核心项目,都可以称为复杂的系统。这样的工程也不在是初学编程手里的玩具项目,没有所谓的CRUD,更多时候要面对的都是对系统分层的结构设计和聚合逻辑功能的实现,再通过层层转换进行实现和调用。
小傅哥
2022/04/19
6260
《Mybatis 手撸专栏》第3章:实现映射器的注册和使用
源码剖析 Mybatis 映射器(Mapper)工作原理
Mybatis可以说是目前国内使用最广泛的ORM框架。最原始的使用方式下,我们将sql写在xml配置文件中,通过SqlSession,根据statementId来唯一指定要执行的sql。从Mybatis 3.0之后,我们可以通过一个Mapper映射接口来完成相同的功能。你是否思考过,Mapper映射接口内部是如何完成这样的功能的。本文从源码的角度,深入分析mybatis 映射器接口的工作原理。
田守枝
2019/05/21
6.4K1
源码剖析 Mybatis 映射器(Mapper)工作原理
MyBatis的动态代理实现细节
一直以来都在使用MyBatis做持久化框架,也知道当我们定义XXXMapper接口类并利用它来做CRUD操作时,Mybatis是利用了动态代理的技术帮我们生成代理类。那么动态代理内部的实现细节到底是怎么的呀?XXXMapper.java类和XXXMapper.xml到底是如何关联起来的呀?本篇文章就来详细剖析下MyBatis的动态代理的具体实现机制。
二哥聊运营工具
2021/12/17
9110
MyBatis的动态代理实现细节
浅析MyBatis的动态代理原理[通俗易懂]
前言 一直以来都在使用MyBatis做持久化框架,也知道当我们定义XXXMapper接口类并利用它来做CRUD操作时,Mybatis是利用了动态代理的技术帮我们生成代理类。那么动态代理内部的实现细节到底是怎么的呀?XXXMapper.java类和XXXMapper.xml到底是如何关联起来的呀?本篇文章就来详细剖析下MyBatis的动态代理的具体实现机制。
全栈程序员站长
2022/09/22
2K0
MyBatis的通俗理解:SqlSession.getMapper()源码分析
直接看官方文档:https://mybatis.org/mybatis-3/zh/index.html。
全栈程序员站长
2022/07/02
5340
mybatis的工厂与代理实现
在文前中已经提到过, 除了使用XML配置的方式, 还可以使用包扫描和接口的方式配置mapper. 其中包扫描的方式底层也是封装了接口配置方式实现的. 今天就一起看接口配置方式是如何实现的? 一. ma
一个架构师
2022/06/20
2730
mybatis的工厂与代理实现
Mybatis源码学习(二)Mapper动态代理
上一篇分析了SqlSessionFactoryBuilder是如何解析mapper的,并且mapper的核心在于会将所有的mapper接口注册到MapperRegistry中(key-mapper接口Class类,value-mapper的代理工厂)。
虞大大
2020/09/01
3960
MyBatis源码解析(二)——动态代理实现函数调用
如果我们要使用MyBatis进行数据库操作的话,大致要做两件事情: 1. 定义DAO接口 在DAO接口中定义需要进行的数据库操作。 2. 创建映射文件 当有了DAO接口后,还需要为该接口创建映射文件。映射文件中定义了一系列SQL语句,这些SQL语句和DAO接口一一对应。 MyBatis在初始化的时候会将映射文件与DAO接口一一对应,并根据映射文件的内容为每个函数创建相应的数据库操作能力。而我们作为MyBatis使用者,只需将DAO接口注入给Service层使用即可。 那么MyBatis是
大闲人柴毛毛
2018/03/12
1.1K0
从 Spring 集成 MyBatis 到浅析 Java 动态代理
因为 MyBatis 的易上手性和可控性,使得它成为了 ORM框架中的首选。近日新起了一个项目,所以重新搭建了一下 Spring-mybatis, 下面是搭建笔记和从 Spring-mybatis源码分析其如何使用 Java动态代理,希望对大家有帮助。
Bug开发工程师
2019/06/03
5330
从 Spring 集成 MyBatis 到浅析 Java 动态代理
因为 MyBatis 的易上手性和可控性,使得它成为了 ORM框架中的首选。近日新起了一个项目,所以重新搭建了一下 Spring-mybatis, 下面是搭建笔记和从 Spring-mybatis源码分析其如何使用 Java动态代理,希望对大家有帮助。
Java技术江湖
2019/09/24
4630
从 Spring 集成 MyBatis 到浅析 Java 动态代理
MyBatis源码解读(2)——MapperProxy
SqlSession可以说是整个MyBatis的重中之重,在SqlSession中涉及到前一篇四大对象:Executor、StatementHandler、ParameterHandler、ResultHandler,所以在此先只对SqlSession有一个大概的了解。 在代码中我们可以看到当我们构造出一个SqlSession实例过后,可以通过SqlSession构造出Mappper映射器。UserMapper是一个接口,那么我们可以肯定的是,它一定是用了Java的动态代理生成了一个代理类。 SqlSess
用户1148394
2018/01/09
1.1K0
MyBatis源码解读(2)——MapperProxy
Mybatis源码解析(八):Mapper代理原理
冬天vs不冷
2025/01/21
940
Mybatis源码解析(八):Mapper代理原理
Mybatis【11】-- Mybatis Mapper动态代理怎么写?
上面的代码是在接口实现类里面自己去执行id,查找并执行mapper文件里面的sql,那么我们想是不是可以减少一步呢?
秦怀杂货店
2020/12/26
3420
Mybatis【11】-- Mybatis Mapper动态代理怎么写?
上面的代码是在接口实现类里面自己去执行id,查找并执行mapper文件里面的sql,那么我们想是不是可以减少一步呢?
秦怀杂货店
2022/02/15
6240
Mybatis【11】-- Mybatis Mapper动态代理怎么写?
为啥mybatis的mapper只有接口没有实现类,但它却能工作?
说起mybatis,大伙应该都用过,有些人甚至底层源码都看过了。在mybatis中,mapper接口是没有实现类的,取而代之的是一个xml文件。也就是说我们调用mapper接口,其实是使用了mapper.xml中定义sql完成数据操作。
java思维导图
2019/10/25
5.9K0
Mybatis Mapper 接口源码解析
在使用 Mybatis 的时候,我们只需要写对应的接口,即dao层的Mapper接口,不用写实现类,Mybatis 就能根据接口中对应的方法名称找到 xml 文件中配置的对应SQL,方法的参数和 SQL 的参数一一对应,在 xml 里面的 SQL 中,我们可以通过 #{0},#{1},来绑定参数,也可以通过 #{arg0},#{arg1}来绑定参数,还可以通过方法中真正的参数名称如 name,age之类的进行绑定,此外还可通过 #{param1},#{param2}等来绑定,接下来看下 Mybatis的源码是如何实现的。
Java技术编程
2020/05/21
2.2K0
深入浅出MyBatis:MyBatis解析和运行原理
上一篇介绍了反射和动态代理基础,主要是为本篇文章做个铺垫,反射使配置和灵活性大大提高,可以给很多配置设置参数,动态代理可以在运行时创建代理对象,做一些特殊的处理。
情情说
2018/04/27
1.3K0
深入浅出MyBatis:MyBatis解析和运行原理
源码分析(1.4万字) | Mybatis接口没有实现类为什么可以执行增删改查
MyBatis 是一款非常优秀的持久层框架,相对于IBatis更是精进了不少。与此同时它还提供了很多的扩展点,比如最常用的插件;语言驱动器,执行器,对象工厂,对象包装器工厂等等都可以扩展。那么,如果想成为一个有深度的男人(程序猿),还是应该好好的学习一下这款开源框架的源码,以此可以更好的领会设计模式的精髓(面试?)。其实可能平常的业务开发中,并不会去深究各个框架的源代码,也常常会听到即使不会也可以开发代码。但!每个人的目标不同,就像;代码写的好工资加的少(没有bug怎么看出你工作嘞!),好!为了改变世界,开始分析喽!
小傅哥
2020/07/14
1K0
源码分析(1.4万字) | Mybatis接口没有实现类为什么可以执行增删改查
推荐阅读
相关推荐
《Mybatis 手撸专栏》第2章:创建简单的映射器代理工厂
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档