作者:小傅哥


博客:https://bugstack.cn

沉淀、分享、成长,让自己和他人都能有所收获!

一、前言

着急和快,是最大的障碍!

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

在小傅哥编写技术文章的过程中,也会遇到这样的情况,不少读者更喜欢看;一个系列内容的开头、一段成长故事的分享、一天成为架构的秘籍。当然我也能理解这种喜欢,毕竟大多数人都喜欢走捷径,就像冬天买了运动健身装备,夏天过去了还没有拆封。

好了,接下来咱们干正事!

二、目标

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

比如我们使用 JDBC 的时候,需要手动建立数据库链接、编码 SQL 语句、执行数据库操作、自己封装返回结果等。但在使用 ORM 框架后,只需要通过简单配置即可对定义的 DAO 接口进行数据库的操作了。

那么本章节我们就来解决 ORM 框架第一个关联对象接口和映射类的问题,把 DAO 接口使用代理类,包装映射操作。

三、设计

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

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

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

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

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

四、实现

1. 工程结构

mybatis-step-01
└── src
├── main
│ └── java
│ └── cn.bugstack.mybatis.binding
│ ├── MapperProxy.java
│ └── MapperProxyFactory.java
└── test
└── java
└── cn.bugstack.mybatis.test.dao
├── dao
│ └── IUserDao.java
└── ApiTest.java

工程源码:https://t.zsxq.com/bmqNFQ7

Mybatis 映射器代理类关系,如图 2-2

  • 目前这个 Mybatis 框架的代理操作实现的还只是最核心的功能,相当于是光屁股的娃娃,还没有添加衣服。不过这样渐进式的实现可以让大家先了解到最核心的内容,后续我们在陆续的完善。

    • MapperProxy 负责实现 InvocationHandler 接口的 invoke 方法,最终所有的实际调用都会调用到这个方法包装的逻辑。
    • MapperProxyFactory 是对 MapperProxy 的包装,对外提供实例化对象的操作。当我们后面开始给每个操作数据库的接口映射器注册代理的时候,就需要使用到这个工厂类了。

2. 映射器代理类

源码详见cn.bugstack.mybatis.binding.MapperProxy

public class MapperProxy<T> implements InvocationHandler, Serializable {

    private static final long serialVersionUID = -6424540398559729838L;

    private 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());
}
} }
  • 通过实现 InvocationHandler#invoke 代理类接口,封装操作逻辑的方式,对外接口提供数据库操作对象。
  • 目前我们这里只是简单的封装了一个 sqlSession 的 Map 对象,你可以想象成所有的数据库语句操作,都是通过接口名称+方法名称作为key,操作作为逻辑的方式进行使用的。那么在反射调用中则获取对应的操作直接执行并返回结果即可。当然这还只是最核心的简化流程,后续不断补充内容后,会看到对数据库的操作
  • 另外这里要注意如果是 Object 提供的 toString、hashCode 等方法是不需要代理执行的,所以添加 Object.class.equals(method.getDeclaringClass()) 判断。

3. 代理类工厂

源码详见cn.bugstack.mybatis.binding.MapperProxyFactory

public class MapperProxyFactory<T> {

    private final Class<T> mapperInterface;

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

五、测试

1. 事先准备

cn.bugstack.mybatis.test.dao.IUserDao

public interface IUserDao {

    String queryUserName(String uId);

    Integer queryUserAge(String uId);

}
  • 首先提供一个 DAO 接口,并定义2个接口方法。

2. 测试用例

@Test
public void test_MapperProxyFactory() {
MapperProxyFactory<IUserDao> factory = new MapperProxyFactory<>(IUserDao.class);
Map<String, String> sqlSession = new HashMap<>(); sqlSession.put("cn.bugstack.mybatis.test.dao.IUserDao.queryUserName", "模拟执行 Mapper.xml 中 SQL 语句的操作:查询用户姓名");
sqlSession.put("cn.bugstack.mybatis.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 中取值了。

测试结果

17:03:41.817 [main] INFO  cn.bugstack.mybatis.test.ApiTest - 测试结果:你的被代理了!模拟执行 Mapper.xml 中 SQL 语句的操作:查询用户姓名

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

六、总结

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

七、系列推荐

《Mybatis 手撸专栏》第2章:创建简单的映射器代理工厂的更多相关文章

  1. 《Mybatis 手撸专栏》第7章:SQL执行器的定义和实现

    作者:小傅哥 博客:https://bugstack.cn - <手写Mybatis系列> 一.前言 为什么,要读框架源码? 因为手里的业务工程代码太拉胯了!通常作为业务研发,所开发出来的 ...

  2. 《Mybatis 手撸专栏》第10章:使用策略模式,调用参数处理器

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 你这代码写的,咋这么轴呢! 说到轴,让我想起初中上学时老师说的话:"你那脑 ...

  3. 《Mybatis 手撸专栏》第1章:开篇介绍,我要带你撸 Mybatis 啦!

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 1. 为甚,撸Mybatis 我就知道,你会忍不住对它下手! 21年带着粉丝伙伴撸了一遍 Sp ...

  4. 《Mybatis 手撸专栏》第6章:数据源池化技术实现

    作者:小傅哥 博客:https://bugstack.cn - 手写Mybatis系列文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 码农,只会做不会说? 你有发现吗,其实很大一部分码农 ...

  5. 《Mybatis 手撸专栏》第8章:把反射用到出神入化

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 为什么,读不懂框架源码? 我们都知道作为一个程序员,如果想学习到更深层次的技术,就需 ...

  6. 《Mybatis 手撸专栏》第9章:细化XML语句构建器,完善静态SQL解析

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 你只是在解释过程,而他是在阐述高度! 如果不是长时间的沉淀.积累和储备,我一定也没有 ...

  7. 《Spring 手撸专栏》第 3 章:初显身手,运用设计模式,实现 Bean 的定义、注册、获取

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 你是否能预见复杂内容的设计问题? 讲道理,无论产品功能是否复杂,都有很大一部分程序员 ...

  8. 《Spring 手撸专栏》第 2 章:小试牛刀(让新手能懂),实现一个简单的Bean容器

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 上学时,老师总说:不会你就问,但多数时候都不知道要问什么! 你总会在小傅哥的文章前言 ...

  9. 手撸一个SpringBoot的Starter,简单易上手

    前言:今天介绍一SpringBoot的Starter,并手写一个自己的Starter,在SpringBoot项目中,有各种的Starter提供给开发者使用,Starter则提供各种API,这样使开发S ...

  10. 带码农《手写Mybatis》进度3:实现映射器的注册和使用

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获!

随机推荐

  1. 单位:MB、GB与MiB、GiB

    MB.GB与MiB.GiB的区别在于标准不同,MB使用的是十进制标准(SI标准),MiB使用的是二进制标准(IEC标准) 在工作中最好是先确定使用什么标准,防止出错 换算表: 1KB = 10^3 = ...

  2. DNS--安装&&配置文件

    1 下载 #下载服务yum -y install bind#下载解析工具yum -y install bind-utils 2 配置文件 主配置文件 /etc/named.conf 区配置文件 /va ...

  3. 从阿里云全球实时传输网络GRTN出发,浅谈QOE优化实践

    直播已深入每家每户,以淘宝的直播为例,在粉丝与主播的连麦互动中如何实现无感合屏或切屏?阿里云GRTN核心网技术负责人肖凯,在LVS2022上海站为我们分享了GRTN核心网的运作机制.运用方面以及QOE ...

  4. 如临现场的视觉感染力,NBA决赛直播还能这样看?

    在6月16日结束的NBA总决赛中,勇士4-2击败凯尔特人,问鼎总冠军!今年的NBA总决赛吸引了众多关注,互联网各大平台的赛事直播气氛也异常热烈. 平台如何既能展现专业的赛事解说,又能与球迷观众深入互动 ...

  5. Codeforces Round #704 (Div. 2) A~E

    比赛链接:https://codeforces.com/contest/1492 1492A.Three swimmers 题意: 有三名游泳的人,他们分别需要 \(a,b,c\) 分钟才能在一个游泳 ...

  6. 8、SpringBoot 事务

    系列导航 springBoot项目打jar包 1.springboot工程新建(单模块) 2.springboot创建多模块工程 3.springboot连接数据库 4.SpringBoot连接数据库 ...

  7. Eureka相关相关接口和代码位置

    1.服务注册:com.netflix.eureka.registry.AbstractInstanceRegistry#register(InstanceInfo registrant, int le ...

  8. 从0开发3D引擎(十三):使用领域驱动设计,从最小3D程序中提炼引擎(第四部分)

    目录 上一篇博文 下一篇博文 本文流程 回顾上文 解释基本的操作 如何在浏览器上运行index.html 开始实现 准备 建立代码的文件夹结构,约定模块文件的命名规则 模块文件的命名原则 一级和二级文 ...

  9. Java循环标签

    大家是否见过这种for循环,在for循环前加了个标记的: outerLoop: for (; ; ) { for (; ; ) { break outerLoop; } } 我之前有一次在公司业务代码 ...

  10. 搬运 |(有刀🔪慎入)kwdnx、谷雨、更漏与我

    本打算把文案投到官微,但官微说文案太长了,只能放删减版: 那就把文案放博客上吧 写于 癸卯年 戊午月 己亥日. 四年前的冬天我们初见,大家都是青涩的未成年人:在少年班团建的喧闹中,谷雨谈论高数竞赛,k ...