Mapper映射文件解析
关联类:
- XMLMapperBuilder
- XMLStatementBuilder
- MappedStatement
0、入口:XMLConfigBuilder.mapperElement(…)
1、 XMLMapperBuilder
-
两种配置方式:
- 配置package,会遍历该包下所有的类
- 指定mapper文件的路径resource/url/class
-
解析过程(以常用的配置xml文件路径为列):
- 具体解析逻辑通过XMLMapperBuilder类来完成,解析xml文件中的<mapper>节点
- cacheRefElement()/cacheElement():解析缓存cache-ref和cache(二级缓存配置)
解析/mapper/parameterMap- resultMapElement():解析/mapper/resultMap
- sqlElement():解析/mapper/sql
- buildStatementFromContext():从“select|insert|update|delete”配置语句构建Statement
- 具体解析逻辑通过XMLMapperBuilder类来完成,解析xml文件中的<mapper>节点
2、resultMapElement(建造者模式)
数据库字段与Java对象属性字段之间的映射
两个关联的类:ResultMapping
和ResultMap
- ResultMapping:记录数据字段一列与Java对象中的一个属性之间建立的映射关系。
- ResultMap:每个节点都会被解析成一个ResultMap对象,其中每个节点所定义的映射关系,则用ResultMapping对象来记录。
- 最终节点数据会以:key=currentNamespace+"."+id;value=ResultMap保存到
Configuration的resultMaps
中。
2.1、<resultMap>节点解析
每个<resultMap>节点会被解析成一个ResultMap对象,<resultMap>节点中每一行属性配置会被解析成一个ResultMapping对象。多个ResultMapping构成了一个ResultMap对象。
//☆☆--XMLMapperBuilder: 构建ResultMap
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception {
//获取id和type
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
//NOTE: 解析type,表示一个JavaBean的完全限定名或者一个类型别名
Class<?> typeClass = resolveClass(type);
if (typeClass == null) {
typeClass = inheritEnclosingType(resultMapNode, enclosingType);
}
Discriminator discriminator = null;
//NOTE: 遍历解析<resultMap>里所有的行,一行配置用一个ResultMapping来记录
List<ResultMapping> resultMappings = new ArrayList<>();
resultMappings.addAll(additionalResultMappings);
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 {
//NOTE: 基本类型的字段
List<ResultFlag> flags = new ArrayList<>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
// 解析id 和property节点,并生成相应的ResultMapping对象
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
//NOTE: <resultMap>的id属性值,也将作为记录到Configuration中resultMaps的一个key
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
//通过ResultMapResolver构建ResultMap对象
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
//调用resultMapResolver.resolve方法构建ResultMap对象
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
//如果抛异常IncompleteElementException,则将resultMapResolver放入incompleteResultMaps集合中
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
- 解析每一行,构建ResultMapping对象
//解析id 和property节点,并生成相应的ResultMapping对象
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
String property;
if (flags.contains(ResultFlag.CONSTRUCTOR)) {
property = context.getStringAttribute("name");
} else {
property = context.getStringAttribute("property");
}
String column = context.getStringAttribute("column");
String javaType = context.getStringAttribute("javaType");
String jdbcType = context.getStringAttribute("jdbcType");
String nestedSelect = context.getStringAttribute("select");
/**
* NOTE: 解析resultMap属性中出现<association/>和<collection/>节点,表示有嵌套内容,需通过processNestedResultMappings作进一步解析
*/
String nestedResultMap = context.getStringAttribute("resultMap",
processNestedResultMappings(context, Collections.emptyList(), resultType));
String notNullColumn = context.getStringAttribute("notNullColumn");
String columnPrefix = context.getStringAttribute("columnPrefix");
String typeHandler = context.getStringAttribute("typeHandler");
String resultSet = context.getStringAttribute("resultSet");
String foreignColumn = context.getStringAttribute("foreignColumn");
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
/**
* NOTE: 解析javaType、typeHandler的类型以及枚举类型JdbcType
*/
Class<?> javaTypeClass = resolveClass(javaType);
Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
// MapperBuilderAssistant的buildResultMapping方法
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
2.2、构建ResultMap
通过ResultMapResolver
封装<resultMap>节点解析后的信息,并完成ResultMap对象的构建。
//XMLMapperBuilder
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
resultMapResolver.resolve();
//ResultMapResolver
public ResultMap resolve() {
return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
}
3、buildStatementFromContext
对每条<select />、<insert />、<update />、<delete /> 等标签,通过XMLStatementBuilder
来构建MappedStatement。
3.1、 关联的类
XMLStatementBuilder、XMLIncludeTransformer、XMLScriptBuilder、SqlSourceBuilder、SqlSource、MappedStatement
3.2、XMLStatementBuilder
- 解析<include>,将include替换成<sql>;
- 对于${key}这种从属性配置文件中获取值的进行解析替换。
- GenericTokenParser
- 解析SQL节点,将定义的SQL节点信息构建成
MappedStatement
对象。
3.2.1、MappedStatement
记录SQL节点信息,包含了很多属性,平时常见的属性有:
private String id;
private List<ResultMap> resultMaps;
private boolean useCache;
/**
* 记录缓存对象(二级缓存)
*/
private Cache cache;
/**
* sql片段集
*/
private SqlSource sqlSource;
private KeyGenerator keyGenerator;
除这些还有SqlCommandType、StatementType等等。
3.2.2、开启SQL节点解析
- 入口:
parseStatementNode
。
public void parseStatementNode() {
//NOTE. 节点id属性,对应Mapper接口中的方法名
String id = context.getStringAttribute("id");
//指定databaseId
String databaseId = context.getStringAttribute("databaseId");
//NOTE. 获取对应的SqlCommandType(select、insert、update、delete)
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//NOTE: 是否刷新缓存
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
//NOTE: 是否使用二级缓存(select 语句默认使用缓存)
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
//NOTE: <include>语句解析
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
//NOTE: 解析parameterType(如传入一个对象)
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
//NOTE:一般不会配置,使用默认的XMLLanguagerDriver
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
//NOTE: 解析<update/>等操作内部 的<SelectKey>内置节点
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
KeyGenerator keyGenerator;
//NOTE: 定义MappedStatement的id,区别于其他SQL节点
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;
}
//NOTE: 创建SqlSource
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
//NOTE: Statement 默认PreparedStatement(有缓存)
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
//NOTE: resultType
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
//NOTE: resultMap
String resultMap = context.getStringAttribute("resultMap");
String resultSetType =
//NOTE: resultSetType,定义结果集的"滚动"模式
context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
//NOTE:update语句相关属性
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
//构建MappedStatement对象
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
3.2.3、XMLIncludeTransformer: <include/>节点解析
解析<include>节点,该过程会将其替换成<sql>节点中定义的SQL片段,并将其中的”${xxx}“占位符替换为真实的参数,主要过程在XMLIncludeTransformer.applyIncludes
方法内完成。
3.2.3.1
-------TODO
3.3、SqlSourceBuilder创建 SqlSource
见SqlSource篇
SqlSourceBuilder、SqlSource、XMLScriptBuilder:构建动态SQL,SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
3.4、构建MappedStatement对象
- MappedStatement:
- 映射的语句,每个<select />、<insert />、<update />、<delete /> 对应一个 MappedStatement 对象。
- 最后以
key=currentNamespce+"."+id属性值
和value=MappedStatement
记录在Configuration中的mappedStatements中。 - 关联的方法1:
getStatementResultMaps()
- 分为两种情况:配置了resultMap时,优先以resultMap为主
- 当配置resultMap时,会根据resultMap的全限定名曲Configuration中找之前已经解析并记录好的数据。
- 当未配置resultMap且配置了resultType时,会根据resultType构建一个
-inline
的ResultMap对象。
- 关联的方法2:
getBoundSql()
- 创建MappedStatement,记录到Configuration的mappedStatements中
- key=currentNamespace+"."+id; value=MappedStatement。
//--☆☆--MapperBuilderAssistant
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
......
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean useCache,
......
LanguageDriver lang) {
//等着缓存配置解析完成(配置了cacheRef)
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//调用MappedStatement 内部静态类构建MappedStatement对象
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
//通过resultMap的名称、resultType解析出ResultMap对象
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
.......
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
3.4.1、getStatementResultMaps
- 获得 ResultMap 集合。
private List<ResultMap> getStatementResultMaps(String resultMap, Class<?> resultType, String statementId) {
//NOTE: 补全resultMap的全限定名
resultMap = applyCurrentNamespace(resultMap, true);
List<ResultMap> resultMaps = new ArrayList<>();
//NOTE: 有配置resultMap属性,配置多个时,以','分开,并从configuration中找到对应的ResultMap
if (resultMap != null) {
String[] resultMapNames = resultMap.split(",");
for (String resultMapName : resultMapNames) {
try {
resultMaps.add(configuration.getResultMap(resultMapName.trim()));
} catch (IllegalArgumentException e) {
......
}
}
} else if (resultType != null) {
//配置resultType,构建“statementId + "-Inline"为id的ResultMap
ResultMap inlineResultMap = new ResultMap.Builder(configuration, statementId + "-Inline", resultType, new ArrayList<>(), null).build();
resultMaps.add(inlineResultMap);
}
return resultMaps;
}
3.4.2、MappedStatement.build()
校验配置有效性
assert mappedStatement.configuration != null;
assert mappedStatement.id != null;
assert mappedStatement.sqlSource != null;
assert mappedStatement.lang != null;
3.4.3、保存MappedStatement
记录MappedStatement到Configuration的mappedStatements Map中。
//记录MappedStatement到缓存中
configuration.addMappedStatement(statement);
//☆☆--Configuration: mappedStatements记录所有配置的MappedStatement
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
//☆☆--Configuration
protected static class StrictMap<V> extends HashMap<String, V> {
private static final long serialVersionUID = -4950446264854982944L;
private final String name;
private BiFunction<V, V, String> conflictMessageProducer;
public StrictMap(String name) {
super();
this.name = name;
}
/**
* Assign a function for producing a conflict error message when contains value with the same key.
* 若存在相同的key 时,返回错误信息
*/
public StrictMap<V> conflictMessageProducer(BiFunction<V, V, String> conflictMessageProducer) {
this.conflictMessageProducer = conflictMessageProducer;
return this;
}
@SuppressWarnings("unchecked")
public V put(String key, V value) {
//NOTE: 若key已经存在则会保错
if (containsKey(key)) {
throw new IllegalArgumentException(name + " already contains value for " + key
+ (conflictMessageProducer == null ? "" : conflictMessageProducer.apply(super.get(key), value)));
}
//NOTE: 将key按照"."拆分,取数组最后一个string为key
if (key.contains(".")) {
final String shortKey = getShortName(key);
if (super.get(shortKey) == null) {
//不存在则调用HashMap原生的方法进行保存
super.put(shortKey, value);
} else {
//NOTE: 若已经存在,则封装成Ambiguity再保存(存在的情况为:包路径不同,但Mapper接口名称一样): shortKey存在二义性
super.put(shortKey, (V) new Ambiguity(shortKey));
}
}
return super.put(key, value);
}
......
}