mybatis源码分析

一世执手 提交于 2020-03-12 16:30:23

Mybatis源码分析

@Author:zxw

@School:吉首大学


 

1.源码启动剖析

先来看看基础的代码

// 获取SqlsessionFactory工厂
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
// 获取sqlsession
SqlSession sqlSession = build.openSession();
// 获取我们的Mapper对象
NewMapper mapper = sqlSession.getMapper(NewMapper.class);
New byId = mapper.findById();
New byId2 = mapper.findById();
System.out.println(byId == byId2);

1.1 SqlSessionFactory构建

我们先来看看SqlSessionFactory对象里有什么,其实只有一个Configuration对象,Configuration对象就是我们的核心了,里面有我们配置文件的信息以及缓存相关等等。

image-20200312133723948

// build()方法构造DefaultSqlSessionFactory对象
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        // 这个主要用来解析我们的mybatis-config.xml配置文件里的东西
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        // 两层方法,先进行parser.parse();
        // 通过parser.parse()获取Configuration对象
        // 随后将Configuration传入build方法构造DefaultSqlSessionFactory对象
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
        // 作用不明,里面有TreadLocalMap,推测和当前线程有关
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

接下来看看parse.parse()方法里面做了什么

public Configuration parse() {
    // 根据异常,应该用来判断是否注册过
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // evalNode就是用来解析mybatis-config.xml配置文件中的configuration标签
    // mybatis的配置文件中的配置都是在configuration标签下,因此这步获取了所有的标签
    // 然后进行解析
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

获取完节点后要开始解析配置文件了,既然是解析配置文件,那么就应该是拿到我们在配置文件中写的所有配置

// 通过源码我们就能知道配置文件中能够配置的标签有哪些,就不要每次上网查资料了
// 此代码位于XMLConfigBuilder类中103行
private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
        // 加载别名<properties>
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
        // 加载别名<typeAliases>
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
        // 此处加载我们配置的数据库环境
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
        // 此处重点内容,加载我们的xxxMapper.xml文件中的内容
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

让我们继续往下看看加载xxxMapper.xml的代码里是如何实现的

 // 此代码位于XMLConfigBuilder类中359行
private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        // 遍历<mappers>下的所有<mapper>
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
            // 一般别人会问你mapper里面有几种加载方式?通过源码查看有3种,resources,url,class
            // 3种方式的加载优先级如何?
            // rousece -> url > class
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
              // 构建mapper,这个对象主要存了我们单个mapper里的一些信息,example:Alias,type,parser
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
              // 这一步非常重要,设计缓存相关概念,我们后面在绕回来看,先在这里插个眼
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

自此,我们的Configuration对象就构建完成了,剩下的就是传入DefaultSqlSessionFactory中而已

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

1.2 SqlSession对象

SqlSession对象主要是我们用来操作mapper的对象

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        // 数据库环境
      final Environment environment = configuration.getEnvironment();
        // 事务工厂
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        // 如果未指定事务处理器,mybatis会使用默认的
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        // 这个Executor对象我感觉功能上很牛逼,但是目前未知作用,应该是底层调用的时候会使用到
      final Executor executor = configuration.newExecutor(tx, execType);
        // 直接构建就完事了
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

自此构建已经结束,省下的就是实际操作的部分了

2.Mybatis一级缓存原理

2.1 JDK动态代理

现在开始讲mybatis的一级缓存内容,在mybaits操作数据库中,使用的是jdk动态代理方式,使用代理对象对数据库进行操作,我们先来看看代理对象何时生成的

NewMapper mapper = sqlSession.getMapper(NewMapper.class);我们有一句这样的代码,是用来获取我们的mapper对象,然后通过mapper对象操作接口的方法进行调用,我们看看这段代码底层发生了什么。

通过一路的追踪,终于在MapperRegistry类中发现相关代码,当然具体的实现不再这个类,在MapperProxyFactory中。

个人理解:MapperRegistry就是用来存储我们的mapper类和代理类之间的关系

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 从代理工厂中获取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 {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
// jdk自带的代理对象生成方法,不多说
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<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

在mapperProxyFacotry中又有一个map,用来建立我们具体的方法和方法操作的关系

private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

可能有人有疑问,我们第一次调用mapperFactoryProxy为什么得到的值不是空的?答案就在我们先前插眼的地方,我们来看看插眼的地方具体做了什么。

mapperParser.parse();

public void parse() {
    // 这里应该只是判断我们的mapper是否已加载
    if (!configuration.isResourceLoaded(resource)) {
        // 第一次的时候未加载,进行存到Set集合中
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
         // 这里会加载你在<mapper>中配置的类的路径,通过classForName加载你的Mapper类,见下
      bindMapperForNamespace();
    }
    // 加载resultMap
    parsePendingResultMaps();
    // 加载Cache
    parsePendingCacheRefs();
    // 加载statement
    parsePendingStatements();
  }
private void bindMapperForNamespace() {
    // 获取你配置的路径名称
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
          // 加载你的类
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
        // 如果类加载成功
      if (boundType != null) {
          // 判断是否有mapper
        if (!configuration.hasMapper(boundType)) {
          // Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          configuration.addLoadedResource("namespace:" + namespace);
           // 此处就是我们之前提到的MapperRegistry对象了,所以我们明明第一次getMapper()却有数据的原因就在这
            // 这个注入的方法我找了一个小时才找到,菜B默默流下了泪水
          configuration.addMapper(boundType);
        }
      }
    }
  }

2.2 代理对象执行过程

代理对象就是我们的MethodProxy对象了,让我们看看它的属性有哪些

 private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession; 
  private final Class<T> mapperInterface; // 接口类
  private final Map<Method, MapperMethod> methodCache; // 这里就是我们的缓存重点了
@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);
      // 方法执行
    return mapperMethod.execute(sqlSession, args);
  }
 private MapperMethod cachedMapperMethod(Method method) {
     // 会从mapper属性中获取mapperMethod
     // 我们现在是第一次调用,所以为null
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
        // 创建这个对象的时候会解析xxxMapper.xml中的<select>标签等等
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }
// 代理对象执行方法
public Object execute(SqlSession sqlSession, Object[] args) {
    // 获取该方法的标签类型,example: select -> insert -> update -> delete
    Object result;
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
            // 获取参数
          Object param = method.convertArgsToSqlCommandParam(args);
            // 大概上就是拿到我们的sql语句进行解析,编译,执行的过程
          result = sqlSession.selectOne(command.getName(), param);
        }
        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;
  }
@Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        // 获取MappedStatement对象,在我们生成mapper的时候会构建一次这个对象
      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();
    }
  }
@Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
      // 生成我们BoundSql对象,里面包含我们的sql语句以及参数
    BoundSql boundSql = ms.getBoundSql(parameter);
      // 缓存key
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }
@SuppressWarnings("unchecked")
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    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++;
        //  从缓存获取数据,HashMap,PerprtualCache对象
      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
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

自此缓存分析结束,还有一些地方没有分析完全,可以自行研究,比如sql语句执行的方式和时机等等

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!