likes
comments
collection
share

Mybatis流程分析(四):Mybatis构建Mapper背后的故事

作者站长头像
站长
· 阅读数 17

本系列文章皆在从细节着手,由浅入深的分析Mybatis框架内部的处理逻辑,带你从一个全新的角度来认识Mybatis的工作原理。

思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。 作者:毅航😜


前言

通过分析我们知道,在MybatisSqlSession 是一个用于执行 SQL 操作的核心接口。它提供了与数据库的交互方法,可以执行查询、插入、更新、删除等操作。

截止到目前,我们从使用Mybatis的四行简单代码开始,依次分析了Mybatis中的 配置文件解析、SqlSessionFactory构建、会话管理等内容。

使用Mybatis的四行代码

  //<1> 加载配置文件
  InputStream is = Resources.getResourceAsStream("mybatis.xml");
  //<2> 创建sessionFactory对象
  sessionFactory = new SqlSessionFactoryBuilder().build(is);
  //<3> 获取sqlSession对象信息
  SqlSession session = factoy.openSqlSession()
  //<4> 构建映射器的代理对象
  UserMapper mapper = session.getMapper(UserMapper.class);
  // .....调用UserMapper中定义的方法,执行相关方法信息

接下来,我们将分析其中getMapper的逻辑,也即图中绿色方框中的内容。

Mybatis流程分析(四):Mybatis构建Mapper背后的故事

在开始分析getMapper开始之前我们先来看看getMapper到底为我们做了哪些事情。

getMapper背后的逻辑

通过观察上述代码,我们注意到在<4> 处调用SqlSession中的getMapper方法,并将一个接口对象的类信息传入,然后就会获得一个该接口的实例对象mapper。进而通过调用UserMapper接口中定义的方法来实现对数据库的操作

(注:其中UserMapper数据持久化层中定义的一个接口)

这一现象背后getMapper帮我们完成哪些逻辑呢?事实上,大致概括为如下几点:

  1. 构建一个实现UserMapper接口的实例对象
  2. 可以实现sql语句与UserMapper中方法的绑定
  3. 执行与UserMapper中方法绑定的sql语句

构建Mapper的逻辑

进一步,上述代码调用的getMapper也会在DefaultSqlSession中进行定义,其逻辑如下:

DefaultSqlSession getMapper的相关逻辑

public class DefaultSqlSession implements SqlSession {

    // .. 省略其他无关代码
    public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }
}

可以看到DefaultSqlSession中的getMapper方法逻辑非常简单。概括来看就是将构建Mapper的逻辑委托给configuration中的getMapper进行实现。而ConfigurationgetMapper的逻辑如下所示:

Configuration # getMapper

    public <T> T getMapper(Class<T> type, 
                            SqlSession  sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

可以看到,ConfigurationgetMapper的方法又将逻辑委托于mapperRegistry进行实现。

如下这张图对前面不断跳转getMapper逻辑进行一个简短的总结。

Mybatis流程分析(四):Mybatis构建Mapper背后的故事

更进一步,MapperRegistry中方法getMapper的逻辑如下所示:

public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, 
              MapperProxyFactory<?>> knownMappers = new HashMap<>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }


  public <T> T getMapper(Class<T> type, 
                                  SqlSession sqlSession) {
    // 根据传入的class,返回一个MapperProxyFactory对象
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    
    // 利用得到的MapperProxyFactory对象返回一个实例对象
    return mapperProxyFactory.newInstance(sqlSession);
   
  }

可以看到在MapperRegistry中,持有一个Map结构,用以实现classMapperProxyFactory之间的映射。其中getMapper的逻辑也非常简洁,大致逻辑可总结为如下所述:

  1. 根据传入的class信息获取class对应的MapperProxyFactory对象
  2. 调用MapperProxyFactory中的newInstance方法返回一个实例对象

此时,虽然我们不知道newInstance背后做了那些工作,但是我们可以肯定其一定会返回一个实现UserMapper的实例对象。 因为如果此处不进行返回一个实例对象的话,getMapper的调用链还会一直持续下去。但事实并非如此,所以此处一定会返回一个实例对象.

事实上,如果你对Java动态代理熟悉的话,我们可以知道当我们利用Jdk中的Proxy生成动态代理类时会调用Proxy.newInstance()这样一个方法从而获取到接口的代理类信息。

此时我们不免有这样的疑问,Mybatis根据传入的Mapper可以生成一个实现该Mapper的对象。那这一切背后的逻辑是不是也是基于Jdk的动态代理?

带着这样的疑问,我们不妨进入到MapperProxyFactory内部,看看其内部是如何根据接口来构建一个是个实例对象的。

MapperProxyFactory的相关内容

public class MapperProxyFactory<T> {

  // 待实现的接口信息
  private final Class<T> mapperInterface;
  
  // ....省略其他无关代码


  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    // 动态代理的逻辑
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

可以看到MapperProxyFactory中的newInstance方法背后的逻辑与我们猜测的如出一辙。其生成代理对象逻辑就是通过Jdk中的Proxy来完成的。

至此,我们应该明白getMapper背后的逻辑就是:通过传入Mapper的接口信息,然后利用Jdk动态代理类的方式生成一个实现该Mapper接口的代理对象。

进一步,getMapper中的调用链可总结为如图所示内容。

Mybatis流程分析(四):Mybatis构建Mapper背后的故事

总结

本章我们以getMapper出发点,分析了getMapper背后的逻辑。基于此我们分析出在Mybatis中构建Mapper时应该考虑如下三点:

Mybatis流程分析(四):Mybatis构建Mapper背后的故事 本章我们将主要分析了Mybatis中如何根据传入的接口构建实例对象背后的逻辑。一言以概之,其本质就是通过Jdk动态代理的方式返回一个实现接口的实例对象。 只不过在Mybatis中为了实现这一逻辑执行了一系列方法的调用逻辑。而当你明白了Mybatis这一精髓,再回看本文,相信一定会有一种轻舟已过万重山的感觉。

至此,我们就讲清楚了Mybaits根据传入接口返回一个实例对象 背后的逻辑。 下一章,我们将主要分析Mybaits是如何将sql语法与接口中方法进行绑定。