【Mybatis源码探索】 --- Mybatis查询过程核心源码解读 --- mapper调用方式


1 源码入口

本篇文章的源码入口如下(可参考文章《【Mybatis源码探索】 — 开篇 • 搭建一个最简单的Mybatis框架》):

/***
 * mybatis的方式
 */
@Test
public void quickStart2() {
    // 2.获取sqlSession
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 3.获取对应mapper
    TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
    // 4.执行查询语句并返回结果
    TUser user = mapper.selectByPrimaryKey(1);
    List<TUser> tUsers = mapper.selectListByIdList(Arrays.asList(1, 2, 3));
    //5.关闭session
    sqlSession.close();
    log.info("user222:" + user);
    log.info("user222:" + tUsers);
}

在《【Mybatis源码探索】 — Mybatis查询过程核心源码解读 — 先聊聊selectOne方法》这篇文章里已经分析了sqlSessionFactory.openSession()方法,本篇文章将从getMapper(…)方法开始。


2 sqlSession.getMapper(…)方法核心源码解读

2.1 不看源码也应该分析出的内容

其实不看getMapper(…)方法的底层源码我觉得也可以分析出来getMapper到底干了什么,通过前面两篇文章《【Mybatis源码探索】 — Mybatis配置文件解析核心源码解读》、《【Mybatis源码探索】 — Mybatis查询过程核心源码解读 — 先聊聊selectOne方法》的分析可以知道:

(1)sqlSession对像里已经封装了Configuration对象 — 第二篇博客
(2)Configuration对象里有一个MapperRegistry对象,而MapperRegistry对象里有一个Map对象 — knownMappers,但该Map对象的key并不是String,而是一个class类型,Value是MapperProxyFactory对象 —> 即该class类型的代理工厂对象。 — 第一篇博客

getMapper(...)方法的调用者是sqlSession对象,参数又是一个class类型(栗子里对应的class类型为:TUserMapper.class) —> 那就可以很自然的想到getMapper(…)方法 肯定会从Configuration对象中获取到该class类型对应的代理工厂对象 — MapperProxyFactory。

继续分析可知TUserMapper是一个接口,且在我的项目里该接口并没有具体的实现类,但通过mapper却可以调用TUserMapper接口对应的方法,这说明通过getMapper(…)方法生成的mapper肯定是一个TUserMapper接口的具体实现类 — 这是如何实现的呢?相信很多人应该都知道用到了动态代理,但具体是如何实现的呢? 被代理的对象到底是谁呢(我觉得看过我前面两篇文章《【Mybatis源码探索】 — Mybatis查询过程核心源码解读 — 先聊聊selectOne方法》和《【设计模式】— 装饰器模式、静态代理模式和动态代理模式》的人肯定至少可以猜出这个问题的真实答案 — SqlSession对象)?


2.2 【源码分析】获取TUserMapper对应的MapperProxyFactory对象

从语句TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);打断点会依次进入到如下方法:

(1)sqlSession的getMapper方法
所在类DefaultSqlSession

@Override
public <T> T getMapper(Class<T> type) {
  //调用configuration的getMapper方法 --- this肯定指的就是当前sqlSession对象本身
  return configuration.getMapper(type, this);
}

(2)configuration的getMapper方法
所在类Configuration

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  //调用mapperRegistry的getMapper方法
  return mapperRegistry.getMapper(type, sqlSession); 
}

(3)mapperRegistry的getMapper方法
所在类MapperRegistry

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  //从knownMappers里获取T对应的MapperProxyFactory对象
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  if (mapperProxyFactory == null) {
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  }
  try {
  	//调用mapperProxyFactory方法的newInstance方法生成具体的代理对象 --- 即T接口对应的实现类
    return mapperProxyFactory.newInstance(sqlSession);
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}

可以看到源码和我分析的一样正是先从SqlSession中获取Configuration对象--->从Configuration对象里获取MapperRegistry对象 ---> 从MapperRegistry对象里获取Map对象 --- knownMappers ---> 从knownMappers里获取在项目启动时放进去的T(在我的栗子中T就是TUserMapper)对应的MapperProxyFactory对象。


2.3 【源码分析】使用动态代理机制生成并获取TUserMapper的代理对象

继续跟踪mapperProxyFactory.newInstance(sqlSession);方法的源码,依次会进入到如下方法:

2.3.1 获取InvocationHandler的具体实现类

继续跟踪 mapperProxyFactory.newInstance(sqlSession)的源码,会进入到如下方法:
所在类MapperProxyFactory

public T newInstance(SqlSession sqlSession) {
  //这句话是生成InvocationHandler的具体实现类 --- 写了上篇文章之后再看这里觉得好简单
  final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  //拿着InvocationHandler对象去生成动态代理类
  return newInstance(mapperProxy);
}

这里先看一下MapperProxy对象的构造方法和其invoke()方法:

  • MapperProxy的构造方法
  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;//sqlSession对象
    this.mapperInterface = mapperInterface; //当前接口
    this.methodCache = methodCache;//方法缓存
  }
  • MapperProxy的invoke()方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  // -----------------不细究了------------------------------------
  try {
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    } else if (method.isDefault()) {
      if (privateLookupInMethod == null) {
        return invokeDefaultMethodJava8(proxy, method, args);
      } else {
        return invokeDefaultMethodJava9(proxy, method, args);
      }
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
   // -----------------不细究了------------------------------------
 
  //构建MapperMethod对象
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  //执行mapperMethod的execute方法调用真正的对象,并获取到返回结果进行返回 --- 底层调用的是sqlSession对象的SelectOne等方法
  return mapperMethod.execute(sqlSession, args);
}

看到这里再联系一下上篇关于动态代理的文章,肯定就可以百分之百的确定了:生成的代理对象真正代理的其实是SqlSession对象。


2.3.2 获取获取具体的代理对象

InvocationHandler对象一拿到,那生成代理对象,也就一句话的事了,继续跟踪mapperMethod.execute(sqlSession, args);的源码,可以进入到如下方法:

所在类MapperProxyFactory

protected T newInstance(MapperProxy<T> mapperProxy) {
  //读了上篇文章这句话看起来就很简单了
  //就是拿着当前接口的类加载器 + 当前接口 + InvocationHandler对象创建代理对象,
  //并将创建的代理对象强转为T类型(对应于本文的栗子,即强转为TUserMapper接口的一个具体实现类)
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

这里要注意:在《【Mybatis源码探索】 — Mybatis配置文件解析核心源码解读》文章讲到过,mapperInterface在Mybatis配置文件解析的过程中就已经被放到当前MapperProxyFactory对象里了


2.4 一点小感悟

弄懂了动态代理的原理,再看Mybatis的源码真的会觉得好简单☺☺☺

3 mapper.selectByPrimaryKey(1)方法核心源码解读

通过上面的分析和具体的源码跟踪,可以看到生成的mapper实际上是一个代理类,且该代理类对应的InvocationHandler是MapperProxy对象,从上篇文章《【设计模式】— 装饰器模式、静态代理模式和动态代理模式》关于动态代理的原理介绍可知:mapper可以按照TUserMapper接口的方法调用方式进行方法调用,但是真正调用到的却是InvocationHandler对像里的invoke(…)方法 — 即MapperProxy对象里的invoke(…)方法 — 源码可看2.3.1。下面对其进行一一解析。


3.1 cachedMapperMethod(method)方法 — 生成MapperMethod对象

final MapperMethod mapperMethod = cachedMapperMethod(method);方法的底层源码不具体跟了,它主要做的工作是拿着当前方法、当前方法所对应的接口从Configuration对象中的mappedStatements属性(mapper.xml中的select、 insert、update、delete标签会被解析成一个个的MappedStatement对像,而mappedStatements是这些对象组成的一个Map,可参看文章《【Mybatis源码探索】 — Mybatis配置文件解析核心源码解读》)里获取到当前方法对应的类型 — 是INSERT、DELETE、UPDATE还是SELECT类型。

获取结果如下:
所在类MapperProxy
在这里插入图片描述


3.2 mapper调用方式底层大揭秘 — 本质就是利用sqlSession对象与数据库进行交互

跟进去mapperMethod.execute(sqlSession, args)方法,其源码如下:
所在类MapperMethod

public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  switch (command.getType()) {
    case INSERT: { //如果方法类型是“INSERT”的话调用sqlSession的insert方法
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
      break;
    }
    case UPDATE: { //如果方法类型是“UPDATE”的话调用sqlSession的UPDATE方法
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
      break;
    }
    case DELETE: {//如果方法类型是“DELETE”的话调用sqlSession的DELETE方法
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
      break;
    }
    case SELECT: //如果方法类型是“SELECT”的话调用sqlSession的SELECT方法
      if (method.returnsVoid() && method.hasResultHandler()) { //没有返回值的情况
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) { //返回值有多个的情况
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) { //返回值为Map的情况
        result = executeForMap(sqlSession, args);
      } else if (method.returnsCursor()) { //返回值为Cursor的情况
        result = executeForCursor(sqlSession, args);
      } else { //返回值为一个的情况
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param); //调用sqlSession的selectOne方法
        if (method.returnsOptional()
            && (result == null || !method.getReturnType().equals(result.getClass()))) {
          result = Optional.ofNullable(result);
        }
      }
      break;
    case FLUSH:
      result = sqlSession.flushStatements(); //清除当前会话
      break;
    default:
      throw new BindingException("Unknown execution method for: " + command.getName());
  }
  if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
    throw new BindingException("Mapper method '" + command.getName()
        + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
  }
  return result; //返回结果
}

可以看到其底层确实是调用的sqlSession对应的方法,即Mybatis使用mapper进行调用的方式其实底层就是封装了ibatis使用sqlSession+方法全额限定名+参数进行与数据库交互的方式。

接下来sqlsession的调用方式可参看文章《【Mybatis源码探索】 — Mybatis查询过程核心源码解读 — 先聊聊selectOne方法》。

4 不做总结了,说一下近期规划

近期还要写的几篇文章:

(1)spring+Mybatis整合原理
(2)Mybatis怎么使用的spring事务 — 这是一个同学问我的问题(::>_<::)
(3)双11前夕我们遇到的一个Mybatis问题 — 这是我下定决心整理Mybatis源码的原始动力

发布了189 篇原创文章 · 获赞 187 · 访问量 39万+

猜你喜欢

转载自blog.csdn.net/nrsc272420199/article/details/103882604
今日推荐