美文网首页
15分钟--帮你分析mybatis源码重点!

15分钟--帮你分析mybatis源码重点!

作者: 爱编程的凯哥 | 来源:发表于2019-06-08 17:39 被阅读0次

本文主旨

方便你快速分析mybatis源码,梳理其流程,掌握主要知识点.

官方简介:

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

本文介绍

mybatis相信大家都用的多了,本文主要介绍mybatis的整体设计思想、核心源码实现,并从中体悟面向对象思维的设计理念.-----哈哈,正所谓,技近乎艺,艺近乎道!

架构介绍

mybatis结构

其结构可以分为三层

  1. 配置初始化:负责mybatis的初始化,环境、数据、插件配置组装工作
  2. 调用会话: 一次查询的调用过程
  3. 映射转换: java对象、数据库结果间相互映射处理

源码流程梳理

层级如此,我们再来看看调用时序:

mybati流程图
  1. Configuration作为mybatis的顶层配置类,管理一切mybatis的环境、缓存信息

2.当你调用getMapper方法获取对应mapper类时(或通过spring注入mapper类时)其实最后调用了Configuration里的getMapper方法, 看下getMapper对应代码:

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

其实调用的是对应的缓存类MapperRegistry管理Mapper代理对象

  1. 打开MapperRegistry类,熟悉的代理模式方法
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

此处获取对应的MapperProxy代理类,看下此类核心invoker方法:

 @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
    //声明对应代理内部方法直接调用,如object的方法
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
//最后实际执行方法,还是回到了SqlSession接口类上
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }
  1. 看完MapperProxy代理类,我们知道mybatis最终返回给用户使用的是MapperProxy类,但实际干活的还是SqlSession对象,但SqlSession实际是会话级对象,即其scope作用域为线程级别,那此处这个代理类中的成员变量是怎么回事呢?

  2. 答案是此处的SqlSession实现类为SqlSessionTemplate类,其是SqlSession默认实现的模版方法类,属于application级别,看下其构造方法

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
  //此处生成对应的sqlSession代理执行类,每个线程将在SqlSessionInterceptor创建单独的SqlSession
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
  }
  1. 原来又是一层动态代理啊!继续看其invoker方法,实际分为下面三部
    1. 获取实际会话对象,此时注意同一个事务中可以获取相同的SqlSession
    2. 调用实际会话对象相应方法
    3. 关闭资源
private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取线程级别SqlSession会话对象,同一个事务中可以获取相同的SqlSession,进行统一事务和缓存、延迟加载等处理
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
//调用实际sqlSession会话对象
        Object result = method.invoke(sqlSession, args);
        ............
  //关闭资源
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        
      }
    }
  }
  1. 经过多层代理,终于找到了我们会话级别的SqlSession,当然还有一些细节流程,如事务处理、 TransactionSynchronizationManager(便于用户定义事务提交后处理流程) 处理器管理等等

8.下面SqlSession的实现类,默认为DefaultSqlSession,在mybatis中认为,任何查询返回结果都概括为一个list,单个结果只是特殊的list,所以selectList方法为其核心查询方法

 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

9.首先MappedStatement为定义的方法对应的sql信息,映射信息等等数据,executor又是一种重要概念,代表了mybatis中最终的执行器.

  1. 继续,BaseExecutor明细的模版方法,执行query,跟代码注释梳理下核心逻辑:
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
//ErrorContext存储了对应的myatis调用栈信息,便于排查错误
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
//查询堆栈,
      queryStack++;
// 查看一级缓存是否存在缓存结果
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
//没有数据从数据库获取数据
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
    //对于延时加载的处理
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      c.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

此处,我们看到了熟悉的mybatis一级缓存概念,对于关联查询等操作,一级缓存还是很有用的,懒加载此处也有实现,在结果集映射时也会操作deferredLoads此对象,进行懒加载操作.
顺便此处说下二级缓存.mybatis的二级缓存通过CachingExecutor实现,其是对BaseExecutor的包装类,当缓存中没有时,一样会调用BaseExecutor相应方法进行从数据库中获取数据.

  1. 下面核心落到了queryFromDatabase方法中,
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
//通过占位符方式,标注是否有缓存数据存入
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
//调用实际查询方法
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
//一级缓存数据
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }
  1. 下面进入SimpleExecutor类的doQuery方法
 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
//获取数据库链接
      Configuration configuration = ms.getConfiguration();
//包装 Statement对象,此处一样用委派模式,进行包装不同的处理类,默认实现为PreparedStatementHandler,并且对于mybatis的plugins也是在此处进行封装
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//处理参数验证,TypeHandler等转换在此前置处理
      stmt = prepareStatement(handler, ms.getStatementLog());
//最后通过此PreparedStatementHandler执行query方法,并通过resultHandler进行结果映射
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
  1. 下面就简单了
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//熟悉的jdbc操作,获取db数据
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
//结果集映射处理
    return resultSetHandler.<E> handleResultSets(ps);
  }
  1. 在resultSetHandler中,会通过反射创建对应的对象实体,赋值到结果集中,如果有懒加载需要,会添加deferredLoad对列,等待用的时候加载对应属性.

总结

好了,mybatis主要远嘛流程梳理完了.

  1. mybatis多处用到了动态代理模式,方便使用者,sql和接口定义分离模式

  2. 其中对于SqlSession的代理Template封装很巧妙,充分学习了模版类的神奇之处

  3. ErrorContext实现对于异常排查问题,方便使用

  4. 代理无处不在,handler方便扩展,mybatis主旨在于参数映射,半封装处理,面向对象思维将各模块组装到一起.

相关文章

网友评论

      本文标题:15分钟--帮你分析mybatis源码重点!

      本文链接:https://www.haomeiwen.com/subject/qpqlpqtx.html