前面有对Configuration配置文件的解析源码,其中有个Mapper节点的解析没有学习,现在这里就对Mapper的解析代码学习以下
Configuration.xml中的mapper节点的处理
mapper节点在configuration配置文件的配置方式为下面所列,有两种方式可以指定mapper,一种是通过扫描指定包下面的所有mapper类,一种是指定莫个mapper
<configuration>
<mappers>
<mapper resource="mybatismapper.xml"/>
<package name="org.apache.ibatis.mybatistestcustom.TestDomain"/>
</mappers>
</configuration>
源码对这个节点解析代码如下,代码逻辑为:
1.首先校验该节点是否为空,为空就直接退出方法
2.如果节点内容不为空,就循环处理子节点。
3.如果子节点是package,就去获取子节点的name属性的属性值,根据属性值获取该值对应包下面所有符合要求的都保存到Configuration类的MapperRegistry mapperRegistry = new MapperRegistry(this)属性当中,最终是保存到mapperRegistry类的一个 Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>()属性当中,以后如果拿mapper对象,也是从这个属性当中拿取,其中类在put进这个属性的时候,value选择的new MapperProxyFactory<>(type) 这个代理工厂,保存完以后还会去扫描以下是否有mapper的注解,并解析这些注解。
4,如果子节点不是package,就会分别获取resource,url,class三种属性的值,并且这个三个属性不能同时存在,只能有一个有值,然后根据这个值去获取这个值的inputStream流,再通过这个流把Mapper.xml文件解析成document,最后来解析这个document.
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
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);
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.");
}
}
}
}
}
把xxmapper.xml文件解析成document文件
这里其实就上面根据resource,url获取的值调用到下面这个方法,利用XPathParser类去解析xml文件,最后把解析好的文件保存到XPathParser类中的document属性,转换完xml后就是解析document里面的/mapper节点了的所有内容了。
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) {
super(configuration);
this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
this.parser = parser;
this.sqlFragments = sqlFragments;
this.resource = resource;
}
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
Mapper文件的 cache-ref 节点解析
cache-ref可以用来当前的mapper缓存使用已经定义的cache的地址,主要配置如下:
<mapper namespace="org.apache.ibatis.mybatistestcustom.MybatisMapper">
<cache-ref namespace="已经存在的cache的地址"/>
</mapper>
源码对这个节点解析代码如下,代码逻辑为:
1.如果该节点的内容为空就退出方法
2.不为空就获取该节点的na’mespace属性的值作为value,当前mapper的namespace做为key保存到Configuration类的 Map<String, String> cacheRefMap = new HashMap<>()属性中
3.通过namespace属性的值作为key去获取Configuration类中的Map<String, Cache> caches = new StrictMap<>(“Caches collection”)属性的值,如果获取不到就抛异常,如果获取到就返回该值。
private void cacheRefElement(XNode context) {
if (context != null) {
configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
try {
cacheRefResolver.resolveCacheRef();
} catch (IncompleteElementException e) {
configuration.addIncompleteCacheRef(cacheRefResolver);
}
}
}
Mapper文件的 cache 节点解析
cache用来开启当前mapper的缓存也就是说的二级缓存,主要配置如下:
<mapper namespace="org.apache.ibatis.mybatistestcustom.MybatisMapper">
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
</mapper>
源码对这个节点解析代码如下,代码逻辑为:
1.该节点的内容是否为空,为空就退出方法
2.获取type属性的值默认是PERPETUAL别名对应的类这个type是cache缓存的实现类,接着获取cache缓存的清理算法,默认是LRU别名对应的类,还有获取cache缓存的刷新间隔单位为毫秒,获取cache可以设置的大小,获取是否只读属性readOnly的属性,默认是false,还有最后一个是否阻塞blocking属性的值,默认也是false,还有获取cache节点设置的字节点的内容。
3.获取完所有属性之后通过CacheBuilder类传入获取的所有属性值来新建一个Cache对象,在新建cache对象的过程当中,会对cache对象进行装饰,不同的cache对象有着不同的功能,最后返回对象,然后把这个cache对象保存到Configuration类的Map<String, Cache> caches = new StrictMap<>(“Caches collection”) 属性当中,其中的key是当前的命名空间,value就是这个cache缓存对象,最后把当前缓存属性currentCache设置成新建的这个cache对象,
private void cacheElement(XNode context) {
if (context != null) {
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
Mapper文件的 /mapper/resultMap 节点解析
resultMap这个节点主要使用来对结果集做映射的,子节点有很多(result,collection,Construct…),,这里只给出了简单的配置如下:
<mapper namespace="org.apache.ibatis.mybatistestcustom.MybatisMapper">
<resultMap id="dsa" type="org.apache.ibatis.mybatistestcustom.TestDomain">
<result property="name" column="name"/>
</resultMap>
<select id="selectById" parameterType="map" resultMap="dsa">
select name from user where id = ${id} and name = #{name}
</select>
</mapper>
复杂一点的配置如下:
<!-- 非常复杂的结果映射 -->
<resultMap id="detailedBlogResultMap" type="Blog">
<constructor>
<idArg column="blog_id" javaType="int"/>
</constructor>
<result property="title" column="blog_title"/>
<association property="author" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
<result property="favouriteSection" column="author_favourite_section"/>
</association>
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<association property="author" javaType="Author"/>
<collection property="comments" ofType="Comment">
<id property="id" column="comment_id"/>
</collection>
<collection property="tags" ofType="Tag" >
<id property="id" column="tag_id"/>
</collection>
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost"/>
</discriminator>
</collection>
</resultMap>
源码对这个节点解析代码如下,代码逻辑为:
1.获取type属性的值,根据这个值去获取对应的类
2.根据该节点的子节点的名称去获取对应的ResultMapping,ResultMapping就是通过对应的属性值传入到builderAssistant.buildResultMapping()方法去新建的,其中还会对TypeHanlder类型处理器做处理,根据javaType获取系统对应的Typehandler类型处理器。
3. 把上面获取到的ResultMapping对象转换成ResultMap,在转换过程当中会对结合做只读的处理,并保存到Configuration类的 Map<String, ResultMap> resultMaps = new StrictMap<>(“Result Maps collection”)属性当中
private void resultMapElements(List<XNode> list) {
for (XNode resultMapNode : list) {
try {
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
private ResultMap resultMapElement(XNode resultMapNode) {
return resultMapElement(resultMapNode, Collections.emptyList(), null);
}
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
Class<?> typeClass = resolveClass(type);
if (typeClass == null) {
typeClass = inheritEnclosingType(resultMapNode, enclosingType);
}
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<>(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 {
List<ResultFlag> flags = new ArrayList<>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
Mapper文件的 /mapper/sql 节点解析
sql主要是把一些公共的sql片段提取出来可以公用,主要配置如下:
<mapper namespace="org.apache.ibatis.mybatistestcustom.MybatisMapper">
<sql id="common">
select name from user where
</sql>
<resultMap id="dsa" type="org.apache.ibatis.mybatistestcustom.TestDomain">
<result property="name" column="name"/>
</resultMap>
<select id="selectById" parameterType="map" resultMap="dsa">
<include refid="common"/> id = ${id} and name = #{name}
</select>
</mapper>
源码对这个节点解析代码如下,代码逻辑为:
1.循环处理该节点的内容,获取databaseId,和id的值,并且给id添加当前命名空间的字符前缀,然后去Configuration类中去匹配是否有当前命名空间,如果有就把这个sql片段保存到Map<String, XNode> sqlFragments 属性中,如果没有就退出方法。
private void sqlElement(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
id = builderAssistant.applyCurrentNamespace(id, false);
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
sqlFragments.put(id, context);
}
}
}
Mapper文件的 select|insert|update|delete 节点解析
这个select|insert|update|delete 就是我们平时写的db语句,具体如下:
<select
id="selectPerson"
parameterType="int"
parameterMap="deprecated"
resultType="hashmap"
resultMap="personResultMap"
flushCache="false"
useCache="true"
timeout="10"
fetchSize="256"
statementType="PREPARED"
resultSetType="FORWARD_ONLY"></select>
<insert
id="insertAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
keyProperty=""
keyColumn=""
useGeneratedKeys=""
timeout="20"></insert>
<update
id="updateAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20"></update>
<delete
id="deleteAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20"></delete>
源码对这个节点解析代码如下,代码逻辑为:
1.对该节点循环处理,使用XMLStatementBuilder类传入必要的参数新建一个statementParser对象出来调用parseStatementNode()方法去解析这些节点的内容
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
2.首先获取id和databaseId两个属性的值,然后拿这两个值去Configuration类中匹配是否有这个命名空间,没有就直接退出方法
3.后续就是对每个节点上面设置的属性进行获取,获取完成后就会通过工具类去新建一个mapperstatement对象,对这个对象设置参数一些东西,最后会把mapperStatement对象保存到Configuraion类的 Map<String, MappedStatement> mappedStatements = new StrictMap(“Mapped Statements collection”)
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource()) 属性当中。
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
String nodeName = context.getNode().getNodeName();
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
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
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;
}
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
至此整个/Mapper节点和/Configuration节点的所有内容都解析完成,最后还有对三个对象在解析过程中存在依赖关系的节点没有新建成功的,再重新解析一次,具体代码如下,这样整个mybatis就解析完成。
public void parse() {
.....
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}