【mybatis源码解析】(一)mybatis执行流程以及实现原理

8 篇文章 4 订阅
7 篇文章 4 订阅

系列文章目录

第一章 mybatis执行流程以及实现原理



前言


不多说,直接开始

提示:以下是本篇文章正文内容,下面案例可供参考

一、先搭建样例demo测试,这里参考了另一篇文章简单搭建测试环境

https://mp.weixin.qq.com/s/x8UKwuOFg5laSGrJH1L2tQ

二、开始解析

1.先看这段代码执行过程

在这里插入图片描述
第一步就不用说了,加载配置文件

打断点开始源码,从第二步开始,创建SqlSessionFactoryBuilder工厂类

那么这个SqlSessionFactoryBuilder工厂类是干什么的么
这里用到了工厂模式用来创建SqlSessionFactory,我们看下SqlSessionFactoryBuilder源码

/**
 * Builds {@link SqlSession} instances.
 *
 * @author Clinton Begin
 */
public class
SqlSessionFactoryBuilder {

  public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
  }
	
  public SqlSessionFactory build(Reader reader, String environment) {
    return build(reader, environment, null);
  }

  public SqlSessionFactory build(Reader reader, Properties properties) {
    return build(reader, null, properties);
  }

  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment) {
    return build(inputStream, environment, null);
  }

  public SqlSessionFactory build(InputStream inputStream, Properties properties) {
    return build(inputStream, null, properties);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

}

其实是提供了几种不同的创建SqlSessionFactory的方法
来看一下相关uml图,有助于后面理解
在这里插入图片描述

下面我们看build(in)方法都做了什么,进入断点

在这里插入图片描述
到这一步,又调了其他build方法,继续进入
在这里插入图片描述
发现创建了通过xml方式生成的XMLConfigBuilder类,里面存的有相关配置,继续往下,看build(parser)方法
在这里插入图片描述
发现有点用了下面的方法
在这里插入图片描述
到这里我们可以看出来最终通过配置创建了DefaultSqlSessionFactory对象,也就是mybatis默认生成的工厂
在这里插入图片描述

factory.openSession()方法做了什么

进入该方法
在这里插入图片描述
这里的configuration.getDefaultExecutorType()返货默认的执行器类型为SIMPLE
继续往下
在这里插入图片描述
这里创建了Transaction,然后最主要的是创建了DefaultSqlSession对象,这个对象比较关键。
Transaction留到后面文章讲解
在这里插入图片描述
这是DefaultSqlSession对象的一些属性

session.getMapper(IUserDao.class)方法做了什么

首先我们应该知道IUserDao是一个接口,没有实现类
进入该方法
在这里插入图片描述
发现调用的configuration的方法,继续往下
在这里插入图片描述
发现configuration又掉用了mapperRegistry.getMapper。并且mapperRegistry属性上有个knownMappers
,其实configuration和mapperRegistry都是再上一步加载配置文件时初始化过,所以这里都有相关属性,初始化加载这里不做说明了
knownMappers是一个Map
进入该方法
在这里插入图片描述
这里knownMappers通过key(就是接口类对象) 来获取MapperProxyFactory,若果没有就抛出异常
继续往下,查看mapperProxyFactory.newInstance方法
在这里插入图片描述
发现这里通过sqlSession, mapperInterface, methodCache创建了MapperProxy对象sqlSession就是上面创建的DefaultSqlSession。mapperInterface就是接口类对象
查看DefaultSqlSession源码

/**
 * @author Clinton Begin
 * @author Eduardo Macarron
 */
public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -4724728412955527868L;
  private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
      | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
  private static final Constructor<Lookup> lookupConstructor;
  private static final Method privateLookupInMethod;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  static {
    Method privateLookupIn;
    try {
      privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
    } catch (NoSuchMethodException e) {
      privateLookupIn = null;
    }
    privateLookupInMethod = privateLookupIn;

    Constructor<Lookup> lookup = null;
    if (privateLookupInMethod == null) {
      // JDK 1.8
      try {
        lookup = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
        lookup.setAccessible(true);
      } catch (NoSuchMethodException e) {
        throw new IllegalStateException(
            "There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.",
            e);
      } catch (Exception e) {
        lookup = null;
      }
    }
    lookupConstructor = lookup;
  }

  @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 {
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

  private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
      return MapUtil.computeIfAbsent(methodCache, method, m -> {
        if (m.isDefault()) {
          try {
            if (privateLookupInMethod == null) {
              return new DefaultMethodInvoker(getMethodHandleJava8(method));
            } else {
              return new DefaultMethodInvoker(getMethodHandleJava9(method));
            }
          } catch (IllegalAccessException | InstantiationException | InvocationTargetException
              | NoSuchMethodException e) {
            throw new RuntimeException(e);
          }
        } else {
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
      });
    } catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
  }

  private MethodHandle getMethodHandleJava9(Method method)
      throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
    final Class<?> declaringClass = method.getDeclaringClass();
    return ((Lookup) privateLookupInMethod.invoke(null, declaringClass, MethodHandles.lookup())).findSpecial(
        declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()),
        declaringClass);
  }

  private MethodHandle getMethodHandleJava8(Method method)
      throws IllegalAccessException, InstantiationException, InvocationTargetException {
    final Class<?> declaringClass = method.getDeclaringClass();
    return lookupConstructor.newInstance(declaringClass, ALLOWED_MODES).unreflectSpecial(method, declaringClass);
  }

  interface MapperMethodInvoker {
    Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
  }

  private static class PlainMethodInvoker implements MapperMethodInvoker {
    private final MapperMethod mapperMethod;

    public PlainMethodInvoker(MapperMethod mapperMethod) {
      super();
      this.mapperMethod = mapperMethod;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      return mapperMethod.execute(sqlSession, args);
    }
  }

  private static class DefaultMethodInvoker implements MapperMethodInvoker {
    private final MethodHandle methodHandle;

    public DefaultMethodInvoker(MethodHandle methodHandle) {
      super();
      this.methodHandle = methodHandle;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      return methodHandle.bindTo(proxy).invokeWithArguments(args);
    }
  }
}

发现它实现了InvocationHandler。也就是这里用到的就是java动态代理相关的知识
在这里插入图片描述
关键方法先简单看看,后面执行要用到

继续往下
在这里插入图片描述
可以看到创建了mapper接口的代理对象
在这里插入图片描述

userDao.findAll()方法怎么执行的

上一步我们知道了userDao其实就是个代理对象
我们打断点继续往下

在这里插入图片描述
发现他来到了这一步,其实就是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 {//cachedInvoker返回MapperMethodInvoker队形用于执行
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

我们看下MapperMethodInvoker源码

private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
      return MapUtil.computeIfAbsent(methodCache, method, m -> {
        if (m.isDefault()) {//如果是default方法
          try {
            if (privateLookupInMethod == null) {
              return new DefaultMethodInvoker(getMethodHandleJava8(method));
            } else {
              return new DefaultMethodInvoker(getMethodHandleJava9(method));
            }
          } catch (IllegalAccessException | InstantiationException | InvocationTargetException
              | NoSuchMethodException e) {
            throw new RuntimeException(e);
          }
        } else {//不是的话就创建PlainMethodInvoker
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
      });
    } catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
  }

可以看出对于非default方法cachedInvoker(method)返回了一个PlainMethodInvoker
所以继续往下就进入了PlainMethodInvoker的invoke方法
在这里插入图片描述
继续往下
在这里插入图片描述
在这里插入图片描述
如果是查询多个就到了这个方法,继续
在这里插入图片描述
这里解释一下

  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);//将 Args 转换为 Sql 命令参数
    if (method.hasRowBounds()) {//如果有分页之类的
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.selectList(command.getName(), param);//执行sqlSession的selectlist方法
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

进入sqlSession.selectList方法
在这里插入图片描述
继续
在这里插入图片描述
继续
在这里插入图片描述
MappedStatement对象封装了执行的sql语句,具体实现后面文章讲解
继续往下
在这里插入图片描述
在这里插入图片描述
这里判断是否走缓存
在这里插入图片描述
最终到了BaseExecutor的query方法
在这里插入图片描述
判断是否有缓存,没有就到了这个方法,进入
在这里插入图片描述
又调用了子类SimpleExecutor的doquery方法,继续

在这里插入图片描述
继续往下
在这里插入图片描述
继续往下
在这里插入图片描述
这里实际上就是JDBC的相关查询调用了
resultSetHandler.handleResultSets(ps)处理结果转化为实体类返回,具体实现篇幅原因这里先不介绍了
,后面再说

总结

至此一次完整的查询流程大概就这么多步骤吧,要详细讲完估计后面还得几篇文章

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值