mybatis源码阅读

Mybatis源码解析

mybatis的使用主要有一下步骤:

  1. 创建SqlSessionFactory工厂对象

    // 加载mybatis配置文件
    InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
    // 创建SqlSessionFactory
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
    

    整个流程的关键在于build()方法,build方法中对配置文件进行解析XMLConfigBuilder,并创建对象DefaultSqlSessionFactory,mybatis默认的SqlSessionFactory为DefaultSqlSessionFactory,有两个重要的方法:

    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    var5 = this.build(parser.parse());
    

    通过XMLConfigBuilder来解析mybatis配置文件:

    		public Configuration parse() {
            if (this.parsed) {
                throw new BuilderException("Each XMLConfigBuilder can only be used once.");
            } else {
                this.parsed = true;
              // 获取configuration标签下的内容
                this.parseConfiguration(this.parser.evalNode("/configuration"));
                return this.configuration;
            }
        }
    

    在parseConfiguration方法中:

    private void parseConfiguration(XNode root) {
        try {
          // 首先读取外部配置文件,解析properties标签
          propertiesElement(root.evalNode("properties"));
          // 解析settings标签
          Properties settings = settingsAsProperties(root.evalNode("settings"));
          loadCustomVfs(settings);
          // 解析typeAliases标签
          typeAliasesElement(root.evalNode("typeAliases"));
          // 解析plugins标签
          pluginElement(root.evalNode("plugins"));
          // 解析objectFactory标签
          objectFactoryElement(root.evalNode("objectFactory"));
          // 解析objectWrapperFactory标签
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          // 解析feflectorFactory
          reflectorFactoryElement(root.evalNode("reflectorFactory"));
          settingsElement(settings);
          // 解析environments标签
          environmentsElement(root.evalNode("environments"));
          // 解析datasourceIdProvider标签
          databaseIdProviderElement(root.evalNode("databaseIdProvider"));
          // 解析typeHandlers标签
          typeHandlerElement(root.evalNode("typeHandlers"));
          // 解析mappers标签
          mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
      }
    
    • 解析properties标签

      private void propertiesElement(XNode context) throws Exception {
          if (context != null) {
            // 首先获取<properties>标签下的子标签,取出<property>标签配置的变量信息
            Properties defaults = context.getChildrenAsProperties();
            String resource = context.getStringAttribute("resource");
            String url = context.getStringAttribute("url");
            if (resource != null && url != null) {
              throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
            }
            if (resource != null) {
              defaults.putAll(Resources.getResourceAsProperties(resource));
            } else if (url != null) {
              defaults.putAll(Resources.getUrlAsProperties(url));
            }
            Properties vars = configuration.getVariables();
            if (vars != null) {
              defaults.putAll(vars);
            }
            parser.setVariables(defaults);
            configuration.setVariables(defaults);
          }
        }
      

      在解析properties标签时,优先加载标签中的内容,然后加载resource属性配置的路径或url配置的路径,两个路径只能存在一个,如果两个都存在会抛出异常。而且加载外部文件中的属性会覆盖标签中配置的相同名变量。加载完成后将变量信息保存如全局配置。

    变量的替换

    在mybatis的配置文件中,使用${}引用properties导入的环境变量,环境变量的替换发生在root.evalNode("")期间。进入此方法:

    public XNode evalNode(Object root, String expression) {
        Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
        if (node == null) {
          return null;
        }
        return new XNode(this, node, variables);
      }
    

    可以看到调用了new XNode(this,node,variables),而在此构造器中:

    public XNode(XPathParser xpathParser, Node node, Properties variables) {
        this.xpathParser = xpathParser;
        this.node = node;
        this.name = node.getNodeName();
        this.variables = variables;
        this.attributes = parseAttributes(node);
        this.body = parseBody(node);
      }
    

    调用了parseAttributes(node),进入此方法:

    private Properties parseAttributes(Node n) {
        Properties attributes = new Properties();
        NamedNodeMap attributeNodes = n.getAttributes();
        if (attributeNodes != null) {
          for (int i = 0; i < attributeNodes.getLength(); i++) {
            Node attribute = attributeNodes.item(i);
            String value = PropertyParser.parse(attribute.getNodeValue(), variables);
            attributes.put(attribute.getNodeName(), value);
          }
        }
        return attributes;
      }
    

    调用了PropertyParser.parse(attribute.getNodeValue(), variables);方法,进入此方法:

      public static String parse(String string, Properties variables) {
        VariableTokenHandler handler = new VariableTokenHandler(variables);
        GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
        return parser.parse(string);
      }
    

    可以看到就是在这里发生了变量的替换。

    其他的步骤都是解析标签然后替换变量信息并解析标签的内容,如果没有值则设置为默认值。在最后mapperElement()方法中加载了mapper.xml文件并解析放入configuration中。

    至此第一步方法分析完毕,主要工作就是:

    1. 加载mybatis-config.xml配置文件
    2. 替换环境变量并解析配置文件及mapper.xml文件。
    3. 创建DefaultSqlSessionFactory对象并返回。
  2. 开启一个会话。

    通过SqlSession sqlSession = sqlSessionFactory.openSession();可以开启一个session会话。进入openSession方法中:

    // 从方法名可以看出此方法为:从数据源中打开一个session会话
    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
        try {
          // 首先获取环境信息
          final Environment environment = configuration.getEnvironment();
          // 通过环境信息获取事物工厂:
          //<environments default="mysql">
          //<environment id="mysql">
          //      <transactionManager type="JDBC"/>
          //      <dataSource type="POOLED">
           //         ....
           //     </dataSource>
          //  </environment>
        //</environments>
          // 在配置文件中配置的是JDBC事物,然后就会返回相应的事物工厂。
          final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
          // 通过事物工厂创建一个事物
          tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
          // 通过配置信息获取执行器,执行器是mybatis的核心
          final Executor executor = configuration.newExecutor(tx, execType);
          // 通过执行器创建一个DefaultSqlSession
          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();
        }
      }
    

    进入getTransactionFactoryFromEnvironment()方法:

      private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
        if (environment == null || environment.getTransactionFactory() == null) {
          return new ManagedTransactionFactory();
        }
        return environment.getTransactionFactory();
      }
    

    可以看到只是通过Environment获取到了事物工厂。

    进入transactionFactory.newTransaction()

    @Override
    public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
      return new JdbcTransaction(ds, level, autoCommit);
    }
    

    查看newExecutor方法

    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
      // 默认的ExecutorType为 SIMPLE
      executorType = executorType == null ? defaultExecutorType : executorType;
      executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
      Executor executor;
      if (ExecutorType.BATCH == executorType) {
        executor = new BatchExecutor(this, transaction);
      } else if (ExecutorType.REUSE == executorType) {
        executor = new ReuseExecutor(this, transaction);
      } else {
        executor = new SimpleExecutor(this, transaction);
      }
      // 默认cacheEnabled属性为true,使用cachingExecutor装饰器装饰Executor
      if (cacheEnabled) {
        executor = new CachingExecutor(executor);
      }
      // 使用自定义插件中的Executor插件装饰执行器
      executor = (Executor) interceptorChain.pluginAll(executor);
      return executor;
    }
    

    通过上面的分析,我们可以得出结论:

    • 在openSession阶段,并没有跟数据库建立连接。
    • 创建了事物,执行器,并根据执行器创建了DefaultSqlSession。
  3. 获取Mapper接口:

    当获取到sqlSession后,就会通过ActorDao mapper = sqlSession.getMapper(ActorDao.class);来获取一个Mapper接口。进入getMapper方法:

    public <T> T getMapper(Class<T> type) {
      return configuration.<T>getMapper(type, this);
    }
    

    继续进入方法:

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

    通过mapperRegistry获取Mapper接口。在前面构建sqlSessionFactory阶段解析mapper.xml时,mapperElement方法中向MapperRegistry中注册了此mapper信息以及关联的mapper接口。那么就可以通过这个接口查到对应的Mapper信息。

    继续进入getMapper方法:

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
      // 通过knowMappers获取接口对应的mapper代理工厂
      final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
      if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
      }
      try {
        // 通过代理工厂创建一个mapper接口的代理类。
        return mapperProxyFactory.newInstance(sqlSession);
      } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
      }
    }
    

    进入newInstance方法

      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);
    }
    

    可以看到通过Proxy代理也就是jdk动态代理创建了Mapper接口的代理类。代理类的类型为MapperProxy。

    至此我们通过ActorDao mapper = sqlSession.getMapper(ActorDao.class);获取到的接口mapper就是一个代理类。

    总结这一阶段的事情:

    • 通过制定的mapper接口类型,到mapperregistry中查找到对应的MapperProxyFactory,然后在通过MapperProxyFactory创建一个代理类。
    • 目前为止仍未与数据库建立连接
  4. 调用接口的方法:

    Actor actor = mapper.selectByPrimaryKey(1L);
    

    通过断点进入此方法:

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // 直接进入了MapperProxy的invoke方法
      try {
        // 判断调用的方法是否是Object累的方法:equals,hashcode等,如果是,则直接调用
        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);
      }
      // 缓存次方法并返回MapperMethod
      final MapperMethod mapperMethod = cachedMapperMethod(method);
      // 执行方法
      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;
    }
    

查看execute方法:

public Object execute(SqlSession sqlSession, Object[] args) {
    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()) {
          // 执行结果处理器并返回null
          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()) {
          // 返回游标
          result = executeForCursor(sqlSession, args);
        } else {
          // 转换参数为通用参数
          Object param = method.convertArgsToSqlCommandParam(args);
          // 执行查询方法
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        // 刷新statement
        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;
  }

主要查看的方法是:

Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);

进入method.convertArgsToSqlCommandParam(args);方法,最终调用方法为ParamNameResolver

public Object getNamedParams(Object[] args) {
  // 获取参数的数组大小
  final int paramCount = names.size();
  if (args == null || paramCount == 0) {
    // 没有参数
    return null;
  } else if (!hasParamAnnotation && paramCount == 1) {
    // 参数只有一个直接取出第一个
    return args[names.firstKey()];
  } else {
    // 参数有多个,默认为param1, param2
    final Map<String, Object> param = new ParamMap<Object>();
    int i = 0;
    for (Map.Entry<Integer, String> entry : names.entrySet()) {
      param.put(entry.getValue(), args[entry.getKey()]);
      // add generic param names (param1, param2, ...)
      final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
      // ensure not to overwrite parameter named with @Param
      // 如果参数有@param注解,那么注解的值会被缓存到names中,通过names替换param1,param2
      if (!names.containsValue(genericParamName)) {
        param.put(genericParamName, args[entry.getKey()]);
      }
      i++;
    }
    return param;
  }
}

然后 就是执行方法sqlSession.selectOne()方法,最终调用的是selectList:

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    // 通过配置获取MappedStatement
    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();
  }
}

最终执行query方法为:

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
  // 从缓存中获取-- 二级缓存
  Cache cache = ms.getCache();
  if (cache != null) {
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
      ensureNoOutParams(ms, boundSql);
      @SuppressWarnings("unchecked")
      List<E> list = (List<E>) tcm.getObject(cache, key);
      if (list == null) {
        // 真正执行查询数据库的方法, delegate为真正工作的Executor
        list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        tcm.putObject(cache, key, list); // issue #578 and #116
      }
      return list;
    }
  }
  return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

查看query方法:

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++;
      //一级缓存
      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;
  }

继续进入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 {
      // doQuery方法: 真正执行查询的方法
      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;
  }

进入doQuery方法,本次查询使用的是SimpleExecutor执行器,进入方法:

  @Override
  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();
      // 获取一个statementHandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // 预处理StatementHandler
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }

上面这个方法比较重要,主要做了以下事情:

  • 获取StatementHandler,在newStatementHandler方法中,创建了一个RoutingStatementHandler方法,查看父类的构造方法可以看到包装了ParameterHandler,ResultSetHandler等处理器,然后又包装了StatementHandler

  • prepareStatement方法,真正与数据库建立连接的方法

    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
      // 创建数据库连接
        Connection connection = getConnection(statementLog);
      // 通过数据库连接创建一个Statement
        stmt = handler.prepare(connection, transaction.getTimeout());
        handler.parameterize(stmt);
        return stmt;
      }
    
  • 使用Statement执行sql,查看handler.query()方法

      @Override
      public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        String sql = boundSql.getSql();
        // 执行sql语句
        statement.execute(sql);
        // 处理结果集
        return resultSetHandler.<E>handleResultSets(statement);
      }
    

    至此,mybatis源码的整体流程梳理完毕。

posted @ 2021-03-22 23:38  Zs夏至  阅读(43)  评论(1编辑  收藏  举报