《Mybatis|《Mybatis 手撸专栏》第2章(创建简单的映射器代理工厂)
作者:小傅哥
博客: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|《Mybatis 手撸专栏》第2章(创建简单的映射器代理工厂)】
文章图片
- 目前这个 Mybatis 框架的代理操作实现的还只是最核心的功能,相当于是光屁股的娃娃,还没有添加衣服。不过这样渐进式的实现可以让大家先了解到最核心的内容,后续我们在陆续的完善。
- MapperProxy 负责实现 InvocationHandler 接口的 invoke 方法,最终所有的实际调用都会调用到这个方法包装的逻辑。
- MapperProxyFactory 是对 MapperProxy 的包装,对外提供实例化对象的操作。当我们后面开始给每个操作数据库的接口映射器注册代理的时候,就需要使用到这个工厂类了。
源码详见:
cn.bugstack.mybatis.binding.MapperProxy
public class MapperProxy implements InvocationHandler, Serializable {private static final long serialVersionUID = -6424540398559729838L;
private Map sqlSession;
private final Class mapperInterface;
public MapperProxy(Map sqlSession, Class 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())
判断。
源码详见:
cn.bugstack.mybatis.binding.MapperProxyFactory
public class MapperProxyFactory {private final Class mapperInterface;
public MapperProxyFactory(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}public T newInstance(Map sqlSession) {
final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface);
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}}
- 工厂操作相当于把代理的创建给封装起来了,如果不做这层封装,那么每一个创建代理类的操作,都需要自己使用
Proxy.newProxyInstance
进行处理,那么这样的操作方式就显得比较麻烦了。 - 另外如果你对代理不是太熟悉,可以着重把 JDK Proxy 的内容做几个案例补充下这块的内容。
cn.bugstack.mybatis.test.dao.IUserDao
public interface IUserDao {String queryUserName(String uId);
Integer queryUserAge(String uId);
}
- 首先提供一个 DAO 接口,并定义2个接口方法。
@Test
public void test_MapperProxyFactory() {
MapperProxyFactory factory = new MapperProxyFactory<>(IUserDao.class);
Map 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] INFOcn.bugstack.mybatis.test.ApiTest - 测试结果:你的被代理了!模拟执行 Mapper.xml 中 SQL 语句的操作:查询用户姓名Process finished with exit code 0
- 从测试结果可以看到的,我们的接口已经被代理类实现了,同时我们可以在代理类中进行自己的操作封装。那么在我们后续实现的数据库操作中,就可以对这部分内容进行扩展了。
- 本章节我们初步对 Mybatis 框架中的数据库 DAO 操作接口和映射器通过代理类的方式进行链接,这一步也是 ORM 框架里非常核心的部分。有了这块的内容,就可以在代理类中进行自己逻辑的扩展了。
- 在框架实现方面引入简单工厂模式包装代理类,屏蔽创建细节,这些也是大家在学习过程中需要注意的设计模式的点。
- 目前内容还比较简单的,可以手动操作练习,随着我们内容的增加,会有越来越多的包和类引入,完善 ORM 框架功能。
- 《Spring 手撸专栏》第 1 章:开篇介绍,我要带你撸 Spring 啦!
- 重学 Java 设计模式:实战工厂方法模式「多种类型商品不同接口,统一发奖服务搭建场景」
- 方案设计:基于库表分段扫描和数据Redis预热,优化分布式延迟任务触达时效性
- 工作两三年了,整不明白架构图都画啥?
- 半年招聘筛选了400+份简历,告诉你怎么写容易被撩!
推荐阅读
- C语言|库函数《qsort》的模拟实现,原来如此简单
- C++分布式系统——《开题》
- 毕马威发布《2022金融科技十大趋势展望》,腾讯云微搭低代码入选
- k8s使用volume将ConfigMap作为文件或目录直接挂载_《蹲坑学kubernetes》之17-19(ConfigMap)
- ios开发技术|《App Store创赢艺术(Apple开发的赚钱机密》这本书)
- 看《App|看《App Store创赢艺术:Apple开发的赚钱机密》这本书
- 《Java多线程编程核心技术》知识梳理
- 开放报名丨《音视频社交新风口》线上峰会,聚焦海外社交生态升级
- 《电商数据分析与数据化运营》读书笔记
- 《C++要笑着学》|【C++要笑着学】从零开始实现日期类 | 体会代码的复用 | 提供完整代码