版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wxy540843763/article/details/80738574
上回说到,加载mybatis-config.xml.今天说说,加载Mapper文件。
在org.apache.ibatis.builder这个包中,是全部的Builder。这里还有两个子包。xml和annotion,就是xml的加载和注解的加载了。
接下来就看下这个加载Mapper文件的builder吧。XMLMapperBuilder.java
其构造方法有五个,两个已经过时了。
@Deprecated public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) { this(reader, configuration, resource, sqlFragments); this.builderAssistant.setCurrentNamespace(namespace); } @Deprecated public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { this(new XPathParser(reader, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration, resource, sqlFragments); } public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) { this(inputStream, configuration, resource, sqlFragments); this.builderAssistant.setCurrentNamespace(namespace); } 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; }
接下来就是本类中最重要的方法:parse()
首先说一下Mapper xml文件吧。
Sql映射文件,只有很少的几个元素:
- cache – 给定命名空间的缓存配置。
- cache-ref – 其他命名空间缓存配置的引用。
- resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
parameterMap– 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。- sql – 可被其他语句引用的可重用语句块。
- insert – 映射插入语句
- update – 映射更新语句
- delete – 映射删除语句
- select – 映射查询语句
还是先放一个比较完整的mapper.xml文件。
<?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.cyou.falsework.request.mapper.DailyTodoMapper">
<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM PERSON WHERE ID = #{id}
</select>
<select id="selectPerson" parameterType="int" parameterMap="deprecated"
resultType="hashmap" resultMap="personResultMap" flushCache="false" useCache="true"
timeout="10000" fetchSize="256" statementType="PREPARED" resultSetType="FORWARD_ONLY">
SELECT * FROM PERSON WHERE ID = #{id}
</select>
<insert
id="insertAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
keyProperty=""
keyColumn=""
useGeneratedKeys=""
timeout="20">
<update
id="updateAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20">
<delete
id="deleteAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20">
<selectKey
keyProperty="id"
resultType="int"
order="BEFORE"
statementType="PREPARED">
select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
</selectKey>
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name"/>
<result property="password" column="hashed_password"/>
</resultMap>
<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<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"/>
</association>
</resultMap>
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
</mapper>
来吧,看一下parse()这个方法:
public void parse() { // 判断该配置文件(resource)是否已经被加载。 if (!configuration.isResourceLoaded(resource)) { // 如果没有被加载。先读取整个mapper文件。 configurationElement(parser.evalNode("/mapper")); // 把这个已经加载的配置文件(资源,resource)放到configuration这个Set中。 configuration.addLoadedResource(resource); // 为这个命名空间绑定映射。 bindMapperForNamespace(); } // 解析ResultMaps parsePendingResultMaps(); // 解析缓存 parsePendingCacheRefs(); // 解析Statements(Sql语句) parsePendingStatements(); }
第一次加载的时候,肯定会进入这个if里面。那么它就会 解析所有的mapper文件。并且把所有解析过的 resource,也就是mapper文件放入configuration的一个Set(loaderResources)中,s说明这个mapper文件已经被加载过了。那么来看一下是如何加载mapper这个文件的。
/** * 加载**Mapper.xml文件。将该配置文件中的节点放入configuration中。 * @param context */ private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); // 解析cache-ref节点 cacheRefElement(context.evalNode("cache-ref")); // 解析cache节点 cacheElement(context.evalNode("cache")); // 解析parameterMap节点 parameterMapElement(context.evalNodes("/mapper/parameterMap")); // 解析resultMap节点 resultMapElements(context.evalNodes("/mapper/resultMap")); // 解析sql节点 sqlElement(context.evalNodes("/mapper/sql")); // 解析select,insert,update,delete buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); } }
回到parse()方法中:看到一个重要的方法:parsePendingResultMaps().
/** * 解析ResultMaps * 在parse()方法中,已经将mapper文件中的所有放入了configuration中, * 接下来就是具体的从configuration中取出来,进行进一步的解析。 */ private void parsePendingResultMaps() { // 从configuration中取出待解析的resultMap Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps(); // synchronized (incompleteResultMaps) { Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator(); // 逐个解析ResultMap while (iter.hasNext()) { try { // 具体的解析方法是:org.apache.ibatis.builder.MapperBuilderAssistant.addResultMap()方法。 iter.next().resolve(); iter.remove(); } catch (IncompleteElementException e) { // ResultMap is still missing a resource... } } } }
跟着看一下:MapperBuilderAssistant.addResultMap方法:
public ResultMap addResultMap(String id, Class<?> type, String extend, Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping) { id = applyCurrentNamespace(id, false); extend = applyCurrentNamespace(extend, true); if (extend != null) { if (!configuration.hasResultMap(extend)) { throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'"); } ResultMap resultMap = configuration.getResultMap(extend); List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings()); extendedResultMappings.removeAll(resultMappings); // Remove parent constructor if this resultMap declares a 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); } ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping) .discriminator(discriminator) .build(); configuration.addResultMap(resultMap); return resultMap; }