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

前言

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

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

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

设计

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

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

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

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

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

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

实现

工程结构

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

/**
 * 映射器代理类
 *
 * @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

/**
 * 代理类工厂
 * @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:

public interface IUserDao {
    String queryUserName(String uId);

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

测试用例

top.it6666.test:

/**
 * @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 中取值了。

测试结果:

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

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

总结

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

结束语

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

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

posted @   BNTang  阅读(31)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
历史上的今天:
2023-06-16 Docker 安装 Nacos 注册中心
  1. 1 总会有人离开 王巨星
  2. 2 月亮 孟凡明
  3. 3 迟里乌布
  4. 4 我只能离开 颜人中
  5. 5 达尔文 蔡健雅
  6. 6 夜色滚烫 叶明净
  7. 7 你的星环 路飞文
  8. 8 不再说话 三块木头
  9. 9 黄昏 粥粥和小伙/粥粥
  10. 10 爱不单行 刘大拿
  11. 11 心动贩卖机 PIggy
  12. 12 别来无恙 苏星婕
  13. 13 我们的歌 刘大拿
  14. 14 一直很安静 王贰浪
  15. 15 去有风的地方 清音
  16. 16 雪 Distance Capper/罗言
  17. 17 坏女孩 徐良/小凌
  18. 18 乐园 沧桑Cang333/虎皮蛋/曲甲
  19. 19 Ayo(Explicit) Chris Brown/Tyga
  20. 20 我的美丽feat.海洋Bo 海洋Bo/高睿
  21. 21 世事可爱 粥粥和小伙/粥粥
  22. 22 我记得 赵雷
  23. 23 我想牵着你的手 许嵩
  24. 24 人们都不懂 刘诺然
  25. 25 寻一个你(电视剧《苍兰诀》温情主题曲) TTTTTeehom
  26. 26 子莫格尼 杉和
  27. 27 Cat Cafe Shoffy
  28. 28 风停了雨停了我们还拥抱着 Superluckyqi
  29. 29 寂寞沙洲冷 于潼
  30. 30 三国恋 王巨星
  31. 31 达尔文 林俊杰
  32. 32 有些 颜人中
  33. 33 小模样 张小只ya
  34. 34 是否 程响
  35. 35 楼顶上的小斑鸠 队长
  36. 36 笑场 薛之谦
  37. 37 还是分开 张叶蕾
  38. 38 修炼爱情 林俊杰
  39. 39 二零三 毛不易
  40. 40 雅俗共赏 许嵩
  41. 41 Serendipity 古瑞斯Graps/Zakiya晴子
  42. 42 就让这大雨全都落下·2023 刘大拿
  43. 43 老男孩 筷子兄弟
  44. 44 有何不可 许嵩
  45. 45 缓缓 杜宣达
  46. 46 好久不见 陈奕迅
  47. 47 爱的魔法(Cover 金莎) 封茗囧菌
  48. 48 在你的身边 盛哲
  49. 49 带我去找夜生活 告五人
  50. 50 假面舞会 很美味
  51. 51 STAY The Kid LAROI/Justin Bieber
  52. 52 我好想睡觉的 无敌西红柿
  53. 53 日不落(温柔版)
  54. 54 恋爱画板 锦零
  55. 55 7710 好乐无荒/尹露浠
  56. 56 给你呀(又名:for ya) 蒋小呢
  57. 57 Love Story Taylor Swift
  58. 58 Plain Jane(Remix 13z) 鱼幼微
  59. 59 晚风 7opy/BT07
  60. 60 拜托 孙晨
  61. 61 乌梅子酱 李荣浩
  62. 62 南半球与北海道 范倪Liu
  63. 63 星河万里 Rom邢锐
有些 - 颜人中
00:00 / 00:00
An audio error has occurred, player will skip forward in 2 seconds.

作词 : 李天阳

作曲 : 郑国锋

编曲 : 余竑龙

房间还是乱糟糟

外套还丢在一角

你搬走之后 我也没改变多少

藏着关心的打扰

简讯平静的语调

你说还好 抱着枕头也能睡着

Oh 好像是我的错

再多一个拥抱

我不再放掉

难过夹带陌生 的感觉 有一些

已模糊的镜头 拉不回 那从前

明明不是下雨天

却淋湿双眼

与你有关的泪点

又闪回到昨天 的错觉 有一些

你占据我世界 却不在 身边

我试着把抱歉 再说一遍

你 还是说了再见

房间还是乱糟糟

外套还丢在一角

你搬走之后 我也没改变多少

藏着关心的打扰

简讯平静的语调

你说还好 抱着枕头也能睡着

Oh 好像是我的错

再多一个拥抱

我不再放掉

难过夹带陌生 的感觉 有一些

已模糊的镜头 拉不回 那从前

明明不是下雨天

却淋湿双眼

与你有关的泪点

又闪回到昨天 的错觉 有一些

你占据我世界 却不在 身边

我试着把抱歉 再说一遍

你 还是说了再见

和你的照片还留着

笑中带泪是我输了

剩下弃权的选择

难过夹带陌生 的感觉 有一些

已模糊的镜头 拉不回 那从前

明明不是下雨天

却淋湿双眼

与你有关的泪点

又闪回到昨天 的错觉 有一些

你占据我世界 却不在 身边

我试着把抱歉 再说一遍

你 还是说了再见

制作人:余竑龙

执行制作:卢昌平

配唱:余竑龙

合声编写:郁采真

吉他:黄德霖/卢昌平

貝斯:卢昌平

混音:余竑龙

点击右上角即可分享
微信分享提示