乐趣区

关于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 框架的代理操作实现的还只是最外围的性能,相当于是光屁股的娃娃,还没有增加衣服。不过这样渐进式的实现能够让大家先理解到最外围的内容,后续咱们在陆续的欠缺。

    • 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 框架性能。

七、系列举荐

  • 《Spring 手撸专栏》第 1 章:开篇介绍,我要带你撸 Spring 啦!
  • 重学 Java 设计模式:实战工厂办法模式「多种类型商品不同接口,对立发奖服务搭建场景」
  • 方案设计:基于库表分段扫描和数据 Redis 预热,优化分布式提早工作触达时效性
  • 工作两三年了,整不明确架构图都画啥?
  • 半年招聘筛选了 400+ 份简历,通知你怎么写容易被撩!
退出移动版