深入浅出Mybatis源码解析——获取Mapper代理对象流程

前言

在上一篇文章深入浅出Mybatis源码解析——SqlSource的创建流程中,说了SqlSource的创建流程这样一个完整的创建流程,在这个流程中包含了:动态SQL标签处理器、解析动态SQL、创建MappedStatement对象。这样给我们对SQLSource这样的整个流程有了一个大概的了解。

而这个过程的作用其实就是把Mapper文件中的SQL语句进行相关的解析和封装,一遍后续的执行,那今天就来说说SQL执行的第一步,SqlSession的执行流程,这个过程的代码看似很简单,但是却涉及大量的类。说到这里那就开始吧!

一、获取Mapper代理对象流程

在没说SQLSession的执行流程之前,我们先来看看,关于Mapper代理对象这样的一个获取的过程,这个过长还是很简单的,来先找出它的入口:DefaultSqlSession#getMapper 。代码如下:

@Override
public <T> T getMapper(Class<T> type) {
	// 从Configuration对象中,根据Mapper接口,获取Mapper代理对象
	return configuration.<T>getMapper(type, this);
}


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

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 根据Mapper接口的类型,从Map集合中获取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 {
      // 通过MapperProxyFactory生产MapperProxy,通过MapperProxy产生Mapper代理对象
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

上面的代码中,核心部分就在最后的那部分:getMapper(Class<T> type, SqlSession sqlSession),在这部分中大体就是先通过knownMappers来获得一个MapperProxyFactory对象,这是mapper的代理工厂,在获得这个工厂后,再由它来创建一个实例,就是Mapper代理对象。

二、SqlSession执行主流程

万剑归宗,还是要先找入口,没有入口,那就是连门儿都没有了,所以先看看入口代码如何。DefaultSqlSession#selectList()

	@Override
	public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
		try {
			// 根据传入的statementId,获取MappedStatement对象
			MappedStatement ms = configuration.getMappedStatement(statement);
			// 调用执行器的查询方法
			// RowBounds是用来逻辑分页(按照条件将数据从数据库查询到内存中,在内存中进行分页)
			// wrapCollection(parameter)是用来装饰集合或者数组参数
			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();
		}
	}

2.1获取MappedStatement对象

从上面代码中,我们看到MappedStatement对象的获取就只有一行代码,不过如果细看,会发现这个步骤的代码还是相当复杂,那就系好安全带好好准备观赏了。

	// 1.
    public MappedStatement getMappedStatement(String id) {
		return this.getMappedStatement(id, true);
	}

    // 2.
    public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
		if (validateIncompleteStatements) {
			buildAllStatements();
		}
		return mappedStatements.get(id);
	}

        // 3.
	/*
	 * Parses all the unprocessed statement nodes in the cache. It is recommended to
	 * call this method once all the mappers are added as it provides fail-fast
	 * statement validation.
	 * 解析缓存中所有未处理的语句节点。建议在添加所有映射器后调用此方法,因为它提供了快速失败的声明验证。
	 */
	protected void buildAllStatements() {
		// 处理configurationElement中解析失败的<resultMap>节点
		parsePendingResultMaps();
		if (!incompleteCacheRefs.isEmpty()) {
			// 用同步锁来保证线程安全
			synchronized (incompleteCacheRefs) {
				// 处理不完整的缓存引用
				incompleteCacheRefs.removeIf(x -> x.resolveCacheRef() != null);
			}
		}
		if (!incompleteStatements.isEmpty()) {
			synchronized (incompleteStatements) {
				// 处理incompleteStatements
				incompleteStatements.removeIf(x -> {
					x.parseStatementNode();
					return true;
				});
			}
		}
		if (!incompleteMethods.isEmpty()) {
			// 处理incompleteMethods
			synchronized (incompleteMethods) {
				incompleteMethods.removeIf(x -> {
					x.resolve();
					return true;
				});
			}
		}
	}

上面代码中的第一步、第二步很简单,第三步中相对复杂些,从源代码注释中可以得出,这里主要是为了快速失败的处理,对于parsePendingResultMaps()的代码这里就不贴了,它里面只是简单的对incompleteResultMaps进行了迭代,在迭代的同时将其remove掉。

我们还是来看看三个带同步锁的代码中的处理方法(当然parsePendingResultMaps方法中也是有同步锁的),先看第一个resolveCacheRef()方法:

  public Cache resolveCacheRef() {
    return assistant.useCacheRef(cacheRefNamespace);
  }

  public Cache useCacheRef(String namespace) {
    if (namespace == null) {
      throw new BuilderException("cache-ref element requires a namespace attribute.");
    }
    try {
      unresolvedCacheRef = true;
      Cache cache = configuration.getCache(namespace);
      if (cache == null) {
        throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
      }
      currentCache = cache;
      unresolvedCacheRef = false;
      return cache;
    } catch (IllegalArgumentException e) {
      throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
    }
  }

上面的代码逻辑很简单,只是单纯的通过namespace拿到cache,如果cache等于null,则直接抛出异常,否则继续处理。那继续看看parseStatementNode,代码如下:

  /**
   * 解析<select>\<insert>\<update>\<delete>子标签
   */
  public void parseStatementNode() {
	// 获取statement的id属性(特别关键的值)
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    // 获取入参类型
    String parameterType = context.getStringAttribute("parameterType");
    // 别名处理,获取入参对应的Java类型
    Class<?> parameterTypeClass = resolveClass(parameterType);
    // 获取ResultMap
    String resultMap = context.getStringAttribute("resultMap");
    // 获取结果映射类型
    String resultType = context.getStringAttribute("resultType");
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);
    
    // 别名处理,获取返回值对应的Java类型
    Class<?> resultTypeClass = resolveClass(resultType);
    String resultSetType = context.getStringAttribute("resultSetType");
    
    // 设置默认StatementType为Prepared,该参数指定了后面的JDBC处理时,采用哪种Statement
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

    String nodeName = context.getNode().getNodeName();
    // 解析SQL命令类型是什么?确定操作是CRUD中的哪一种
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    //是否查询语句
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    // <include>标签解析
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes and remove them.
    // 解析<selectKey>标签
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    // 创建SqlSource,解析SQL,封装SQL语句(未参数绑定)和入参信息
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
   
    String resultSets = context.getStringAttribute("resultSets");
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    // 通过构建者助手,创建MappedStatement对象
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

看到这段代码是不是很惊讶,是不是很惊喜,这不是前面调用的嘛,这个可能是mybatis作者有所其他考虑的,既然代码是前面看到过的,那这里就不在说了,我们继续看最后一个:

  void parseStatement(Method method) {
	// 获取Mapper接口的形参类型
    Class<?> parameterTypeClass = getParameterType(method);
    // 解析Lang注解
    LanguageDriver languageDriver = getLanguageDriver(method);
    // 
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    if (sqlSource != null) {
      Options options = method.getAnnotation(Options.class);
      // 组装mappedStatementId
      final String mappedStatementId = type.getName() + "." + method.getName();
      Integer fetchSize = null;
      Integer timeout = null;
      StatementType statementType = StatementType.PREPARED;
      ResultSetType resultSetType = null;
      // 获取该mapper接口中的方法是CRUD操作的哪一种
      SqlCommandType sqlCommandType = getSqlCommandType(method);
      // 是否是SELECT操作
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
      boolean flushCache = !isSelect;
      boolean useCache = isSelect;

      // 主键生成器,用于主键返回
      KeyGenerator keyGenerator;
      String keyProperty = null;
      String keyColumn = null;
      if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
        // first check for SelectKey annotation - that overrides everything else
        SelectKey selectKey = method.getAnnotation(SelectKey.class);
        if (selectKey != null) {
          keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
          keyProperty = selectKey.keyProperty();
        } else if (options == null) {
          keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        } else {
          keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
          keyProperty = options.keyProperty();
          keyColumn = options.keyColumn();
        }
      } else {
        keyGenerator = NoKeyGenerator.INSTANCE;
      }

      if (options != null) {
        if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
          flushCache = true;
        } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
          flushCache = false;
        }
        useCache = options.useCache();
        fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
        timeout = options.timeout() > -1 ? options.timeout() : null;
        statementType = options.statementType();
        resultSetType = options.resultSetType();
      }

      // 处理ResultMap注解
      String resultMapId = null;
      ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
      if (resultMapAnnotation != null) {
        String[] resultMaps = resultMapAnnotation.value();
        StringBuilder sb = new StringBuilder();
        for (String resultMap : resultMaps) {
          if (sb.length() > 0) {
            sb.append(",");
          }
          sb.append(resultMap);
        }
        resultMapId = sb.toString();
      } else if (isSelect) {
        resultMapId = parseResultMap(method);
      }

      // 通过Mapper构建助手,创建一个MappedStatement对象,封装信息
      assistant.addMappedStatement(
          mappedStatementId,
          sqlSource,
          statementType,
          sqlCommandType,
          fetchSize,
          timeout,
          // ParameterMapID
          null,
          parameterTypeClass,
          resultMapId,
          getReturnType(method),
          resultSetType,
          flushCache,
          useCache,
          // TODO gcode issue #577
          false,
          keyGenerator,
          keyProperty,
          keyColumn,
          // DatabaseID
          null,
          languageDriver,
          // ResultSets
          options != null ? nullOrEmpty(options.resultSets()) : null);
    }
  }

看到这段代码是不是还是很惊喜,惊喜的原因是代码看上去很长,从这段长长的代码中可以得知,它做了一些获取Mapper接口的形参类型、解析Lang注解、组装mappedStatementId、获取该mapper接口中的方法是CRUD操作类型、主键生成、处理ResultMap注解、通过Mapper构建助手,创建一个MappedStatement对象,封装信息等操作,我们还惊喜的发现这里面有一个issue,不知道有没有修复。

说到这里,貌似MappedStatement对象的获取已经结束了,是不是有点晕,当初的一个方法,却带出这么多代码。

2.2.wrapCollection(parameter)装饰集合或者数组参数

	/**
	 * 将Collection或者Array类型的参数,放入Map集合,并设置key的值
	 * 
	 * @param object
	 * @return
	 */
	private Object wrapCollection(final Object object) {
		// Collection集合类型参数
		if (object instanceof Collection) {
			StrictMap<Object> map = new StrictMap<>();
			// key为collection,value为集合参数
			map.put("collection", object);
			if (object instanceof List) {
				// key为list,value为集合参数
				map.put("list", object);
			}
			return map;
		} else if (object != null && object.getClass().isArray()) {
			StrictMap<Object> map = new StrictMap<>();
			// key为array,value为数组数
			map.put("array", object);
			return map;
		}
		return object;
	}

这个步骤还是相对简单,就是将Collection或者Array类型的参数,放入Map集合,并设置key的值而已,最后返回。

2.3.调用执行器的查询方法

在点这个方法的时候,它会弹出两个实现类,一个是CachingExecutor,一个是BaseExecutor,因为考虑到性能问题,会先到缓存中查询缓存中是否有数据,没有才会查询数据库。那就先看看CachingExecutor类中的实现:

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 获取绑定的SQL语句,比如“SELECT * FROM user WHERE id = ? ” 
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 生成缓存Key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

上面代码很简单,就是先获得boundSQL,然后创建缓存,最后做执行查询,说到这里今天的文章也快结束了,关于getBoundSql和query这些,留待下篇文章详解。有人可能会说BaseExecutor的query没说,我想说这两个方法基本一致,不知道作者为何如此写,应该是考虑到他们的查询有区别,那最后就来个总结啦!

总结

  • DefaultSqlSession
  • Executor
    • CachingExecutor
    • BaseExecutor
    • SimpleExecutor (此处为涉及)
  • StatementHandler
    • RoutingStatementHandler SimpleExecutor (此处为涉及)
    • PreparedStatementHandler
  • ResultSetHandler
    • DefaultResultSetHandler
  • |DefaultSqlSession#getMapper:获取Mapper代理对象
    • Configuration#getMapper:获取Mapper代理对象
      • MapperRegistry#getMapper:通过代理对象工厂,获取代理对象
        • MapperProxyFactory#newInstance:调用JDK的动态代理方式,创建Mapper代理
  • DefaultSqlSession#selectLIst
    • CachingExecutor#query
      • BaseExecutor#query
        • BaseExecutor#queryFromDatabase
          • SimpleExecutor#doQuery
            • Configuration#newStatementHandler:创建StatementHandler,用来执行MappedStatement对象
              • RoutingStatementHandler#构造方法:根据路由规则,设置不同的StatementHandlerSimpleExecutor (此处为涉及)
            • SimpleExecutor#prepareStatement:主要是设置PreparedStatement的参数SimpleExecutor (此处为涉及)
              • SimpleExecutor#getConnection:获取数据库连接(此处为涉及)
              • PreparedStatementHandler#prepare:创建PreparedStatement对象(此处为涉及)
              • PreparedStatementHandler#parameterize:设置PreparedStatement的参数(此处为涉及)
            • PreparedStatementHandler#query:主要是用来执行SQL语句,及处理结果集
              • PreparedStatement#execute:调用JDBCapi执行
              • StatementDefaultResultSetHandler#handleResultSets:处理结果集

 

发布了41 篇原创文章 · 获赞 8 · 访问量 4251

猜你喜欢

转载自blog.csdn.net/zfy163520/article/details/103171287