纸上得来终觉浅,绝知此事要躬行!
目录
1.加载流
2.解析xml得到一个XMLConfigBuilder
3.调用XMLConfigBuilder的parser方法解析
4.进入parseConfiguration 解析Configuration下的子节点
5.调用mapperElement(root.evalNode("mappers"));方法进入mapper解析
6. mapperParser.parse();
7.再进入configurationElement(parser.evalNode("/mapper"));
8.进入到 buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
9.再看到:statementParser.parseStatementNode();进入每个标签下的内容解析:
10.接下来我们再看,是如何生成SqlSessionFactory的:
总结:
流程图:
1.加载流
Reader configReader = Resources.getResourceAsReader("org/apache/ibatis/submitted/dynsql/MapperConfig.xml")
2.解析xml得到一个XMLConfigBuilder
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
Configuration configuration = parser.parse();
return build(configuration);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
3.调用XMLConfigBuilder的parser方法解析
4.进入parseConfiguration 解析Configuration下的子节点
private void parseConfiguration(XNode root) {
try {
//解析<Configuration>下的节点
//issue #117 read properties first
//<properties>
propertiesElement(root.evalNode("properties"));
//<settings>
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
//别名<typeAliases>解析
// 所谓别名 其实就是把你指定的别名对应的class存储在一个Map当中
typeAliasesElement(root.evalNode("typeAliases"));
//插件 <plugins>
pluginElement(root.evalNode("plugins"));
//自定义实例化对象的行为<objectFactory>
objectFactoryElement(root.evalNode("objectFactory"));
//MateObject 方便反射操作实体类的对象
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
//<environments>
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// typeHandlers
typeHandlerElement(root.evalNode("typeHandlers"));
//主要 <mappers> 指向我们存放SQL的xxxxMapper.xml文件
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
5.调用mapperElement(root.evalNode("mappers"));方法进入mapper解析
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
//遍历解析mappers下的节点
for (XNode child : parent.getChildren()) {
//首先解析package节点
if ("package".equals(child.getName())) {
//获取包名
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
//如果不存在package节点,那么扫描mapper节点
//resource/url/mapperClass三个值只能有一个值是有值的
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
//优先级 resource>url>mapperClass
if (resource != null && url == null && mapperClass == null) {
//如果mapper节点中的resource不为空
ErrorContext.instance().resource(resource);
//那么直接加载resource指向的XXXMapper.xml文件为字节流
InputStream inputStream = Resources.getResourceAsStream(resource);
//通过XMLMapperBuilder解析XXXMapper.xml,可以看到这里构建的XMLMapperBuilde还传入了configuration,所以之后肯定是会将mapper封装到configuration对象中去的。
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
//解析
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
//如果url!=null,那么通过url解析
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) {
//如果mapperClass!=null,那么通过加载类构造Configuration
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.");
}
}
}
}
6. mapperParser.parse();
7.再进入configurationElement(parser.evalNode("/mapper"));
//解析mapper文件里面的节点
// 拿到里面配置的配置项 最终封装成一个MapperedStatemanet
private void configurationElement(XNode context) {
try {
//获取命名空间 namespace,这个很重要,后期mybatis会通过这个动态代理我们的Mapper接口
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
//如果namespace为空则抛一个异常
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
//解析缓存节点
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
//解析parameterMap(过时)和resultMap <resultMap></resultMap>
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析<sql>节点
//<sql id="staticSql">select * from test</sql> (可重用的代码段)
//<select> <include refid="staticSql"></select>
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);
}
}
8.进入到 buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
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) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
该方法专门用于mapper里面的所有节点。 所以我们可以知道当程序启动时,通过配置文件找到所有的mapper并将所有的mapper从磁盘中读取后放入内存中,并解析。
我们可以看到这段代码,解析每个mappper下的所有<select> <insert> <update> <delete> 标签,并将封装成一个list集合。
//解析增删改查节点<select> <insert> <update> <delete>
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
进入这段代码:
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);
}
}
}
9.再看到:statementParser.parseStatementNode();进入每个标签下的内容解析:
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);
}
我们可以得出结论,每一个<select> <insert> <update> <delete> 都会被解析成一个statement,sqlSource专门用来处理sql。
我们不妨进入 SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);看一下到到底是怎么回事:
我们可以看到:
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
//--------------***********------------
return builder.parseScriptNode();
}
我们再继续进入: builder.parseScriptNode()
可以看到:
public SqlSource parseScriptNode() {
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
这样我们就最终拿到sqlSource 并且保存在MappedStatement中,MappedStatement被保存在Configuration中
至此我们得到了一个完整的configuration。
10.接下来我们再看,是如何生成SqlSessionFactory的:
当我们调用 Configuration configuration = parser.parse();拿到configured以后,继续调用:build(configuration);
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
然后就这样我们拿到了一个SqlSessionFactory。
总结:
a.每一个<select> <insert> <update> <delete> 都会被解析成一个statement
对应:
<mapper namespace="org.apache.ibatis.submitted.dynsql.DynSqlMapper">
<select id="selectDescription" resultType="string">
<bind name="condition" value="p" />
SELECT description
FROM ibtest.names
<if test="condition == null">
WHERE id = 3
</if>
</select>
<select id="selectDescriptionById" resultType="string">
SELECT description
FROM ibtest.names
<if test="id != null">
WHERE id = #{id}
</if>
</select>
<!-- Specify a property name as variable name (Valid always) -->
<select id="selectDescriptionByConditions" resultType="string">
SELECT description
FROM ibtest.names
<if test="id != null">
WHERE id = #{id}
</if>
</select>
<!-- Specify a any name(object name) as variable name (Valid if exists type handler) -->
<select id="selectDescriptionByConditions2" resultType="string">
SELECT description
FROM ibtest.names
<if test="conditions != null">
WHERE id = #{conditions}
</if>
</select>
<!-- Specify a any name(object name) and nested property name (Valid if exists type handler) -->
<select id="selectDescriptionByConditions3" resultType="string">
SELECT description
FROM ibtest.names
<if test="conditions != null and conditions.id != null">
WHERE id = #{conditions.id}
</if>
</select>
</mapper>
b.程序启动时所有的mapper文件会全部被读取到内存中并解析成一个个的mappedStatement
c.每一个mappedStatement下的sql语句都会通过sqlSource去解析。
流程图: