Mybatis source code analysis (3) -- Mapper file analysis

Mybatis source code analysis (3) -- Mapper file analysis

The parsing we analyzed earlier <mappers>is finally parsed by XMLMapperBuilder and MapperAnnotationBuilder. XMLMapperBuilder is used to parse the xml configuration file, and MapperAnnotationBuilder is used to parse the annotations in the interface.

Mapper Xml file parsing

Before analyzing XMLMapperBuilder, let's take a look at the Mapper Xml configuration file, how the xml is configured, and then how the source code is parsed.

The Xml configuration file is as follows:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.qdlc.p2p.dal.mybatis.model.mapper.AccountCashMapper" >
  <resultMap id="BaseResultMap" type="com.qdlc.p2p.dal.dto.AccountCash" >
	<id column="id" property="id" jdbcType="INTEGER" />
	<result column="user_id" property="userId" jdbcType="INTEGER" />
	<result column="status" property="status" jdbcType="BIT" />
	<result column="bank_no" property="bankNo" jdbcType="VARCHAR" />
	<result column="bank" property="bank" jdbcType="VARCHAR" />
	<result column="branch" property="branch" jdbcType="VARCHAR" />
	<result column="money" property="money" jdbcType="DECIMAL" />
  </resultMap>
  
  <resultMap id="ModelResultMap" type="com.qdlc.p2p.dal.model.AccountCashModel" extends="BaseResultMap">
	<result column="user_name" property="userName" jdbcType="VARCHAR" />
	<result column="real_name" property="realName" jdbcType="VARCHAR" />
  </resultMap>
  
  <sql id="Base_Column_List" >
	id, user_id, status, bank_no, bank, branch, money, amount, fee, fee_bear, type, order_no, 
	add_time, add_ip, verify_time, verify_user_name, verify_remark, loan_no
  </sql>
  
  <!-- 根据ID查找 -->
  <select id="findById" resultMap="BaseResultMap" parameterType="java.lang.Long" >
	select <include refid="Base_Column_List" /> from qd_account_cash where id = #{id}
  </select>
  
  <!-- 根据id,修改记录状态 -->
  <update id="updateStatus">
	update qd_account_cash set status = #{status} where id = #{id} and status = #{preStatus}
  </update>
</mapper>

Mapper Xml files are generally set in this way, and various SQL statements to be executed are defined in Xml.

After understanding the structure of the xml file, we start to analyze the XMLMapperBuilder. According to the analysis in the previous article, the analysis we know <mappers>is to call the following code:

XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();

So, let's start with the XMLMapperBuilder constructor, and then analyze the parse() method. Here is the constructor:

public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
	this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
		configuration, resource, sqlFragments);
}

private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
	//设置Configuration到父类,顺便也设置了TypeAliasRegistry和TypeHandlerRegistry
	super(configuration);
	//MapperBuilderAssistant用于构建相关联的对象
	this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
	//XPathParser解析器很重要,负责解析xml内容
	this.parser = parser;
	this.sqlFragments = sqlFragments;
	this.resource = resource;
}

Let's look at the parse() method again:

public void parse() {
	if (!configuration.isResourceLoaded(resource)) {
	  //开始解析<mapper>根节点
	  configurationElement(parser.evalNode("/mapper"));
	  configuration.addLoadedResource(resource);
	  bindMapperForNamespace();
	}

	parsePendingResultMaps();
	parsePendingChacheRefs();
	parsePendingStatements();
}

private void configurationElement(XNode context) {
	try {
		//获取命名空间namespace属性
		String namespace = context.getStringAttribute("namespace");
		if (namespace.equals("")) {
			throw new BuilderException("Mapper's namespace cannot be empty");
		}
		//设置当前节点的命名空间
		builderAssistant.setCurrentNamespace(namespace);
		//开始解析各种子节点
		cacheRefElement(context.evalNode("cache-ref"));
		cacheElement(context.evalNode("cache"));
		parameterMapElement(context.evalNodes("/mapper/parameterMap"));
		resultMapElements(context.evalNodes("/mapper/resultMap"));
		sqlElement(context.evalNodes("/mapper/sql"));
		buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
	} catch (Exception e) {
		throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
	}
}

<resultMap>Since there are many types of nodes, we will not analyze them one by one. The most commonly used sums are analyzed here <select>. That is:

resultMapElements(context.evalNodes("/mapper/resultMap"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

Parse resultMap

<resultMap>The parsing is done by the following method, which is responsible for <resultMap>parsing the child nodes into ResultMapping objects, then constructing all ResultMapping objects into a complete ResultMap object, and finally storing them in Configuration.

private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
	ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
	//获取id属性
	String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
	//获取最终的结果类型
	String type = resultMapNode.getStringAttribute("type",
		resultMapNode.getStringAttribute("ofType",
			resultMapNode.getStringAttribute("resultType",
				resultMapNode.getStringAttribute("javaType"))));
	//获取extends属性
	String extend = resultMapNode.getStringAttribute("extends");
	//获取autoMapping属性
	Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
	//通过别名获取真实的类型
	Class<?> typeClass = resolveClass(type);
	Discriminator discriminator = null;
	List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
	resultMappings.addAll(additionalResultMappings);
	//开始遍历子节点,例如<id>、<result>
	List<XNode> resultChildren = resultMapNode.getChildren();
	for (XNode resultChild : resultChildren) {
	  if ("constructor".equals(resultChild.getName())) {
		processConstructorElement(resultChild, typeClass, resultMappings);
	  } else if ("discriminator".equals(resultChild.getName())) {
		discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
	  } else {
		ArrayList<ResultFlag> flags = new ArrayList<ResultFlag>();
		if ("id".equals(resultChild.getName())) {
		  flags.add(ResultFlag.ID);
		}
		//解析获得ResultMapping对象放入到resultMappings中去
		resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
	  }
	}
	//由ResultMapResolver来解析出ResultMap对象
	ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
	try {
	  return resultMapResolver.resolve();
	} catch (IncompleteElementException  e) {
	  configuration.addIncompleteResultMap(resultMapResolver);
	  throw e;
	}
}

Obtaining ResultMapping

XMLMapperBuilder#buildResultMappingFromContext()方法:

This method is responsible for getting the properties in the node and then delegating to the MapperBuilderAssistant to create the ResultMapping.

private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, ArrayList<ResultFlag> flags) throws Exception {
	//获取各种属性,判断是否有嵌入的<resultMap>
	String property = context.getStringAttribute("property");
	String column = context.getStringAttribute("column");
	String javaType = context.getStringAttribute("javaType");
	String jdbcType = context.getStringAttribute("jdbcType");
	String nestedSelect = context.getStringAttribute("select");
	String nestedResultMap = context.getStringAttribute("resultMap",
	processNestedResultMappings(context, Collections.<ResultMapping> emptyList()));
	String notNullColumn = context.getStringAttribute("notNullColumn");
	String columnPrefix = context.getStringAttribute("columnPrefix");
	String typeHandler = context.getStringAttribute("typeHandler");
	String resulSet = context.getStringAttribute("resultSet");
	String foreignColumn = context.getStringAttribute("foreignColumn");
	Class<?> javaTypeClass = resolveClass(javaType);
	@SuppressWarnings("unchecked")
	Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
	JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
	//委派给builderAssistant去创建ResultMapping
	return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resulSet, foreignColumn);
}

MapperBuilderAssistant #buildResultMapping () : :

This method actually calls MapperBuilderAssistant#assembleResultMapping(). The following is the method to see how ResultMapping is created.

private ResultMapping assembleResultMapping(
	Class<?> resultType,
	String property,
	String column,
	Class<?> javaType,
	JdbcType jdbcType,
	String nestedSelect,
	String nestedResultMap,
	String notNullColumn,
	String columnPrefix,
	Class<? extends TypeHandler<?>> typeHandler,
	List<ResultFlag> flags,
	String resultSet,
	String foreignColumn) {
	//解析出javaTypeClass,如果javaType没有设置,则通过反射获取
	Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
	//获取类型句柄类型
	TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
	//解析组合列名
	List<ResultMapping> composites = parseCompositeColumnName(column);
	if (composites.size() > 0) column = null;
	//构建出ResultMapping对象
	ResultMapping.Builder builder = new ResultMapping.Builder(configuration, property, column, javaTypeClass);
	builder.jdbcType(jdbcType);
	builder.nestedQueryId(applyCurrentNamespace(nestedSelect, true));
	builder.nestedResultMapId(applyCurrentNamespace(nestedResultMap, true));
	builder.resultSet(resultSet);
	builder.typeHandler(typeHandlerInstance);
	builder.flags(flags == null ? new ArrayList<ResultFlag>() : flags);
	builder.composites(composites);
	builder.notNullColumns(parseMultipleColumnNames(notNullColumn));
	builder.columnPrefix(columnPrefix);
	builder.foreignColumn(foreignColumn);
	return builder.build();
}

Obtaining ResultMap

ResultMapResolver#resolve() method

This method returns a ResultMap object, which is the final parsed <resultMap>node object.

public ResultMap resolve() {
	return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
}

This method is very simple, or delegate to the MapperBuilderAssistant#addResultMap() method. Let's look at this method again. MapperBuilderAssistant#addResultMap() method. This method is responsible for building the ResultMapping collection into a ResultMap object and storing it in Configuration.

public ResultMap addResultMap(String id, Class<?> type, String extend, Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping) {
	//获取id,这里的id是namespace + . + id
	id = applyCurrentNamespace(id, false);
	extend = applyCurrentNamespace(extend, true);
	ResultMap.Builder resultMapBuilder = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping);
	if (extend != null) {
		//判断是否有父类resultMap
		if (!configuration.hasResultMap(extend)) {
			throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
		}
		//获取extends指定的resultMap
		ResultMap resultMap = configuration.getResultMap(extend);
		List<ResultMapping> extendedResultMappings = new ArrayList<ResultMapping>(resultMap.getResultMappings());
		extendedResultMappings.removeAll(resultMappings);
		//如果该resultMap使用了constructor,则需要删除父类的constructor
		boolean declaresConstructor = false;
		for (ResultMapping resultMapping : resultMappings) {
			if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
			  declaresConstructor = true;
			  break;
			}
		}
		if (declaresConstructor) {
			Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
			while (extendedResultMappingsIter.hasNext()) {
			  if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
				extendedResultMappingsIter.remove();
			  }
			}
		}
		resultMappings.addAll(extendedResultMappings);
	}
	resultMapBuilder.discriminator(discriminator);
	
	//最终创建出ResultMap,然后存放到Configuration中去
	ResultMap resultMap = resultMapBuilder.build();
	configuration.addResultMap(resultMap);
	return resultMap;
}

Parse select, insert, update, delete tags

private void buildStatementFromContext(List<XNode> list) {
	if (configuration.getDatabaseId() != null) {
	  buildStatementFromContext(list, configuration.getDatabaseId());
	}
	buildStatementFromContext(list, null);
}

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
	//List<XNode>参数就是解析出来的节点集合
	for (XNode context : list) {
	  //每一个节点都由XMLStatementBuilder去解析
	  final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
	  try {
		statementParser.parseStatementNode();
	  } catch (IncompleteElementException e) {
		configuration.addIncompleteStatement(statementParser);
	  }
	}
}

As can be seen from the above code, the <select>node is parsed into a List<XNode> collection, and then each XNode object is handed over to XMLStatementBuilder for processing. The processing method is XMLStatementBuilder#parseStatementNode(). Next, let's see how this method works Dequeue the SQL statement.

public void parseStatementNode() {
	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");
	Class<?> parameterTypeClass = resolveClass(parameterType);
	String resultMap = context.getStringAttribute("resultMap");
	String resultType = context.getStringAttribute("resultType");
	String lang = context.getStringAttribute("lang");
	LanguageDriver langDriver = getLanguageDriver(lang);
	
	//获取返回的类型
	Class<?> resultTypeClass = resolveClass(resultType);
	String resultSetType = context.getStringAttribute("resultSetType");
	//获取SQL语句类型,默认是PREPARED
	StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
	ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
	
	//获取Sql语句类型
	String nodeName = context.getNode().getNodeName();
	SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));

	//是否是select语句,如果是select则使用缓存(flushCache=false,useCache=true)
	boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
	boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
	boolean useCache = context.getBooleanAttribute("useCache", isSelect);
	boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
	
	//解析<inclide>节点
	XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
	includeParser.applyIncludes(context.getNode());
	
	//解析<selectKey>节点
	processSelectKeyNodes(id, parameterTypeClass, langDriver);
	
	//解析Sql语句,封装成SqlSource对象 (<selectKey>和<include>已经被解析和删除了)
	SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
	String resultSets = context.getStringAttribute("resultSets");
	String keyProperty = context.getStringAttribute("keyProperty");
	String keyColumn = context.getStringAttribute("keyColumn");
	KeyGenerator keyGenerator;

	//获取语句id,并选择key生成器
	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))
		  ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
	}
	
	//开始构建MappedStatement对象
	builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
		fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
		resultSetTypeEnum, flushCache, useCache, resultOrdered, 
		keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

Creation of MappedStatement

MappedStatement is the encapsulation of sql statement, which encapsulates <mappers>the definition of each statement label in it. MappedStatement contains the defined parameter types, return result types, SQL statements, and more.

public MappedStatement addMappedStatement(
  String id,
  SqlSource sqlSource,
  StatementType statementType,
  SqlCommandType sqlCommandType,
  Integer fetchSize,
  Integer timeout,
  String parameterMap,
  Class<?> parameterType,
  String resultMap,
  Class<?> resultType,
  ResultSetType resultSetType,
  boolean flushCache,
  boolean useCache,
  boolean resultOrdered,
  KeyGenerator keyGenerator,
  String keyProperty,
  String keyColumn,
  String databaseId,
  LanguageDriver lang,
  String resultSets) {

	if (unresolvedCacheRef) throw new IncompleteElementException("Cache-ref not yet resolved");
	
	id = applyCurrentNamespace(id, false);
	boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
	
	//开始构建MappedStatement对象
	MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType);
	statementBuilder.resource(resource);
	statementBuilder.fetchSize(fetchSize);
	statementBuilder.statementType(statementType);
	statementBuilder.keyGenerator(keyGenerator);
	statementBuilder.keyProperty(keyProperty);
	statementBuilder.keyColumn(keyColumn);
	statementBuilder.databaseId(databaseId);
	statementBuilder.lang(lang);
	statementBuilder.resultOrdered(resultOrdered);
	statementBuilder.resulSets(resultSets);
	setStatementTimeout(timeout, statementBuilder);
	
	//设置参数Map
	setStatementParameterMap(parameterMap, parameterType, statementBuilder);
	
	//设置结果Map
	setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);
	
	//设置是否需要使用缓存
	setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);
	
	//构建出MappedStatement对象,存放到Configuration中去(存在Map中,以statement.getId()为键)
	MappedStatement statement = statementBuilder.build();
	configuration.addMappedStatement(statement);
	return statement;
}

Mapper interface analysis

The parsing of the Mapper interface is handled by MapperAnnotationBuilder, which is responsible for parsing the annotation types @Select, @Insert, @Update, @Delete, etc. in the interface. Let's take a look at how to write annotations in the interface.

public interface GoodsMapper {
	@Select("select * from table_goods where id = #{id}")
	Goods selectGood(int id);
}

After understanding the way of writing the annotations in the interface, the following calls are made in MapperRegistry#addMapper(Class type).

MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();

That is to say, the parsing of the interface is handed over to MapperAnnotationBuilder for processing. So, how to parse the annotations on the interface? Let's start to analyze the MapperAnnotationBuilder class, first analyze the construction method, and then look at the parse() method.

public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
	//定义一个资源名称
	String resource = type.getName().replace('.', '/') + ".java (best guess)";
	//又是MapperBuilderAssistant对象,这和XMLMapperBuilder中是一样的
	this.assistant = new MapperBuilderAssistant(configuration, resource);
	this.configuration = configuration;

	//Mapper接口类型
	this.type = type;
	
	//mybatis提供的注解
	sqlAnnotationTypes.add(Select.class);
	sqlAnnotationTypes.add(Insert.class);
	sqlAnnotationTypes.add(Update.class);
	sqlAnnotationTypes.add(Delete.class);
	
	sqlProviderAnnotationTypes.add(SelectProvider.class);
	sqlProviderAnnotationTypes.add(InsertProvider.class);
	sqlProviderAnnotationTypes.add(UpdateProvider.class);
	sqlProviderAnnotationTypes.add(DeleteProvider.class);
}

The construction method is very simple, look at the parse() method.

public void parse() {
	String resource = type.toString();
	if (!configuration.isResourceLoaded(resource)) {
	  //加载与这个接口相关联的xml文件,也就是说,例如接口为GoodsMapper.java,则会去找相应的GoodsMapper.xml文件
	  loadXmlResource();
	  configuration.addLoadedResource(resource);
	  //设置命名空间为接口名称
	  assistant.setCurrentNamespace(type.getName());
	  parseCache();
	  parseCacheRef();
	  //遍历接口中所有方法
	  Method[] methods = type.getMethods();
	  for (Method method : methods) {
		try {
		  //解析方法上的注解语句
		  parseStatement(method);
		} catch (IncompleteElementException e) {
		  configuration.addIncompleteMethod(new MethodResolver(this, method));
		}
	  }
	}
	parsePendingMethods();
}

So, how is the associated xml resource file loaded? This goes back to loading the Mapper Xml file we analyzed above. Let's take a look at the loadXmlResource() method.

private void loadXmlResource() {
	if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
	  //找到资源路径,所以这个资源路径需要与包名一致了,否则找不到
	  String xmlResource = type.getName().replace('.', '/') + ".xml";
	  InputStream inputStream = null;
	  try {
		inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
	  } catch (IOException e) {
		//资源没找到,忽略异常情况
	  }
	  //开始解析mapper xml文件
	  if (inputStream != null) {
		XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
		xmlParser.parse();
	  }
	}
}

Next, look at how annotations in interface methods are parsed. That is, the method parseStatement(method).

void parseStatement(Method method) {

	//获取方法的参数
	Class<?> parameterTypeClass = getParameterType(method);

	//解析出SqlSource
	LanguageDriver languageDriver = getLanguageDriver(method);
	SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
	if (sqlSource != null) {
	  //解析出@Options注解
	  Options options = method.getAnnotation(Options.class);
	  //语句Id为:接口名 + . + 方法名
	  final String mappedStatementId = type.getName() + "." + method.getName();
	  Integer fetchSize = null;
	  Integer timeout = null;
	  StatementType statementType = StatementType.PREPARED;
	  ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
	  //解析出方法上的语句注解@Select、@Insert、@Update、@Delete,只会解析到一个注解 
	  SqlCommandType sqlCommandType = getSqlCommandType(method);
	  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
	  boolean flushCache = !isSelect;
	  boolean useCache = isSelect;
	
	  KeyGenerator keyGenerator;
	  String keyProperty = "id";
	  String keyColumn = null;
	  if (SqlCommandType.INSERT.equals(sqlCommandType)) {
		//解析@SelectKey注解
		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() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
		  } else {
			keyGenerator = options.useGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
			keyProperty = options.keyProperty();
			keyColumn = options.keyColumn();
		  }
		}
	  } else {
		keyGenerator = new NoKeyGenerator();
	  }
	  
	  //如果设置了@Options注解,获取@Options中值
	  if (options != null) {
		flushCache = options.flushCache();
		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();
	  }
	
	  String resultMapId = null;
	  //获取@ResultMap注解
	  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) {
		//如果没有设置@ResultMap,则根据方法返回类型来创建ResultMap,然后存放到Configuration中去,返回一个resultMapId(这里很复杂,就不详细分析了)
		resultMapId = parseResultMap(method);
	  }
	
	  // 最后,会创建MappedStatement存放到Configuration中去
	  assistant.addMappedStatement(
		  mappedStatementId,
		  sqlSource,
		  statementType,
		  sqlCommandType,
		  fetchSize,
		  timeout,
		  null,                             // ParameterMapID
		  parameterTypeClass,
		  resultMapId,    // ResultMapID
		  getReturnType(method),
		  resultSetType,
		  flushCache,
		  useCache,
		  false,
		  keyGenerator,
		  keyProperty,
		  keyColumn,
		  null,
		  languageDriver,
		  null);
	}
}

Summarize

  1. Mybatis will <resultMap>finally parse it into a ResultMap object and store it in Configuration. The <resultMap>child nodes, such as <id>, <result>etc., are resolved into ResultMapping objects.
  2. Mybatis parses <select>, <inclue>, , <update>and <delete>these statement nodes into a MappedStatement object, which contains all statement definitions. The MappedStatement object contains ParameterMap, ResultMap, SqlSource and other objects.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325207125&siteId=291194637