4

《Mybatis 手撸专栏》第2章:创建简单的映射器代理工厂_源码分析_小傅哥_InfoQ写作平...

 2 years ago
source link: https://xie.infoq.cn/article/42720e885cc82628efa0b96cc
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

  • 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. 测试用例

@Testpublic 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 框架功能。

七、系列推荐


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK