Mybatis配置文件解析原理

Mybatis通过加载配置文件流构建一个DefaultSqlSessionFactory,是通过SqlSessionFactoryBuilder的build方法进行解析,包括属性配置别名配置拦截器配置、环境(数据源和事务管理器)、Mapper配置等;解析完这些配置后会生成一个Configration对象,该对象中包含了Mybatis需要的所有配置,然后用Configration对象创建SqlSessionFactory对象。

1
2
3
4
5
String resource = "mybatis-config.xml";
// 将XML配置文件构建为Configuration配置类
Reader reader = Resources.getResourceAsReader(resource);
// 通过加载配置文件流构建一个SqlSessionFactory DefaultSqlSessionFactory
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);

build方法内部会使用XMLConfigBuilder解析属性configLocation中配置的路径,首先解析<configuration>标签,所有XML文件中的内容以及注解中的内容都会解析到Configuration中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {}
}
}
}
public class XMLConfigBuilder extends BaseBuilder {
public Configuration parse() {
if (parsed) { // 若已经解析过则抛出异常
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true; // 设置解析标志位
parseConfiguration(parser.evalNode("/configuration")); // 解析mybatis-config.xml的节点<configuration>...</configuration>
return configuration;
}
private void parseConfiguration(XNode root) {
try {
// 解析properties节点<properties resource="mybatis/db.properties" />,解析到XPathParser#variables,Configuration#variables
propertiesElement(root.evalNode("properties"));
// 解析setting标签的配置
Properties settings = settingsAsProperties(root.evalNode("settings"));
// VFS是虚拟文件系统,主要是通过程序能方便读取本地文件系统、FTP文件系统等系统中的文件资源,解析到Configuration#vfsImpl
loadCustomVfs(settings); // 配置了vfsImpl属性
// 指定Mybatis所用日志的具体实现,未指定时将自动查找,解析到Configuration#logImpl
loadCustomLogImpl(settings); // 配置了logImpl属性
// 解析别名标签数据<typeAliases>,配置后就可以用别名来替代全限定名,解析到Configuration#typeAliasRegistry.typeAliases
typeAliasesElement(root.evalNode("typeAliases"));
// 解析拦截器和拦截器的属性,如分页插件,解析到Configuration#interceptorChain.interceptors,允许在已映射语句执行过程中的某一点进行拦截调用
// 允许使用插件来拦截的方法调用包括Executor(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed),
// ParameterHandler(getParameterObject, setParameters),ResultSetHandler (handleResultSets, handleOutputParameters)
// StatementHandler(prepare, parameterize, batch, update, query)
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings); // 设置settings若没有则设置默认值
//解析环境信息,包括事物管理器和数据源,SqlSessionFactoryBuilder在解析时需要指定环境id,如果不指定的话,会选择默认的环境
environmentsElement(root.evalNode("environments"));
// 解析数据库厂商<databaseIdProvider type="DB_VENDOR">,解析到Configuration#databaseId
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 无论是在预处理语句PreparedStatement中设置参数,还是从结果集中取值时,都会用类型处理器将获取值以合适的方式转换成Java类型
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers")); // 解析mapper
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
}

具体的Mapper文件解析是通过XMLConfigBuildermapperElement来遍历解析mappers节点下的所有mapper子节点。这里有四种配置方式,可通过package标签批量注册Mapper接口,通过mapper标签的resource属性配置具体的xml文件,通过mapper标签的url属性配置网络上的xml文件,,通过mapper标签的class属性配置具体的Mapper接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class XMLConfigBuilder extends BaseBuilder {
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) { // 遍历解析mappers节点下的所有mapper子节点
// 判断mapper是否通过批量注册的<package name="com.eleven.mapper"></package>
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
// 从classpath下读取mapper <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
String resource = child.getStringAttribute("resource");
// 判断是否从网络或本地磁盘读取<mapper url="D:/mapper/EmployeeMapper.xml"/>
String url = child.getStringAttribute("url");
// class属性配置要求接口和xml在同一个包下)<mapper class="com.tuling.mapper.DeptMapper"></mapper>
String mapperClass = child.getStringAttribute("class");
// mappers节点只配置了<mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource); // 把文件读取出一个流
// 创建读取XmlMapper构建器对象,用于来解析mapper.xml文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 真正的解析mapper.xml配置文件,就是解析sql
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.");
}
}
}
}
}
}

对于Mapper通过XML配置文件的方法配置的,首先会通过parse方法先去解析mapper.xml文件内容,然后再通过调用bindMapperForNamespace从而调用configuration#addMapper去触发解析Mapper接口中的注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public void parse() {
if (!configuration.isResourceLoaded(resource)) {// 判断当前的Mapper XML是否被加载过
configurationElement(parser.evalNode("/mapper")); // 真正的解析mapper.xml文件内容
configuration.addLoadedResource(resource); // 把mapper.xml全限定名保存到已被加载列表
bindMapperForNamespace(); // 解析Mapper接口中SQL注解
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
}

对于Mapper接口的配置以及上面的bindMapperForNamespace,都是通过MapperAnnotationBuilder对注解进行解析,解析注解前会先去判断XML文件是否被解析,若没有被解析会先解析XML文件,若解析过了则直接解析SQL注解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) { // 判断传入进来的type类型是不是接口
if (hasMapper(type)) { // 判断的缓存中有没有该类型
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 创建一个MapperProxyFactory把的Mapper接口保存到工厂类中
knownMappers.put(type, new MapperProxyFactory<>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse(); // 解析mapper.xml以及Mapper接口中的SQL注解
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}

对于XML文件的解析是通过XMLMapperBuilder#parse来完成的,最终通过configurationElement来完成各个子节点的解析工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private void loadXmlResource() {
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
if (inputStream == null) {
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e2) {
}
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace"); // 解析namespace属性
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace); // 保存当前的namespace
cacheRefElement(context.evalNode("cache-ref")); // 解析缓存引用,说明当前的缓存引用和DeptMapper的缓存引用一致
cacheElement(context.evalNode("cache")); // 解析cache节点
parameterMapElement(context.evalNodes("/mapper/parameterMap")); // 解析paramterMap节点
resultMapElements(context.evalNodes("/mapper/resultMap")); // 解析resultMap节点
sqlElement(context.evalNodes("/mapper/sql")); // 解析sql标签节点
buildStatementFromContext(context.evalNodes("select|insert|update|delete")); // 解析select | insert |update |delete节点
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}

对于缓存cache节点的解析,这里其实是对二级缓存的构建,二级缓存在结构设计上采用装饰器+责任链模式。从最内层到最外层依次是PerpetualCacheLruCacheScheduledCacheSerializedCacheLoggingCacheSynchronizedCacheBlockingCache

构建好的Cache缓存对象会被设置到MapperBuilderAssistantcurrentCache属性,最后Mapper解析完成后通过MapperBuilderAssistantaddMappedStatement方法将缓存对象设置到对应的MappedStatementcache属性中。在执行查询操作时会通过判断MappedStatementcache属性是否为null从而判断是否走二级缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
private void cacheElement(XNode context) {
if (context != null) {
String type = context.getStringAttribute("type", "PERPETUAL");// 解析cache节点的type属性
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);// 根据type的String获取class类型
String eviction = context.getStringAttribute("eviction", "LRU"); // 获取缓存过期策略:默认是LRU
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
// flushInterval刷新间隔,可被设置为任意正整数,设置的值应该是一个以毫秒为单位的合理时间量。默认不设置即没有刷新间隔,缓存仅在调用语句时刷新
Long flushInterval = context.getLongAttribute("flushInterval");
// size引用数目,可被设置为任意正整数,要注意缓存对象的大小和运行环境中可用的内存资源。默认值是1024
Integer size = context.getIntAttribute("size");
// readOnly只读,可被设置为true或false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。
// 而可读写的缓存会通过序列化返回缓存对象的拷贝,速度上会慢一些,但更安全默认值false
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
// 把缓存节点加入到Configuration中
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
public Cache useNewCache(Class<? extends Cache> typeClass, Class<? extends Cache> evictionClass, Long flushInterval, Integer size, boolean readWrite, boolean blocking, Properties props) {
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
configuration.addCache(cache);
currentCache = cache;
return cache;
}
public Cache build() {
setDefaultImplementations();
Cache cache = newBaseCacheInstance(implementation, id);
setCacheProperties(cache);
if (PerpetualCache.class.equals(cache.getClass())) {
for (Class<? extends Cache> decorator : decorators) { // 通过LruCache对PerpetualCache进行装饰
cache = newCacheDecoratorInstance(decorator, cache);
setCacheProperties(cache);
}
cache = setStandardDecorators(cache); // 剩余集中Cache对LruCache依次装饰
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
cache = new LoggingCache(cache);
}
return cache;
}
private Cache setStandardDecorators(Cache cache) {
try {
MetaObject metaCache = SystemMetaObject.forObject(cache);
if (size != null && metaCache.hasSetter("size")) {
metaCache.setValue("size", size);
}
if (clearInterval != null) {
cache = new ScheduledCache(cache);//ScheduledCache:调度缓存,负责定时清空缓存
((ScheduledCache) cache).setClearInterval(clearInterval);
}
if (readWrite) { // 将LRU 装饰到Serialized
cache = new SerializedCache(cache); //SerializedCache:缓存序列化和反序列化存储
}
cache = new LoggingCache(cache);
cache = new SynchronizedCache(cache);
if (blocking) {
cache = new BlockingCache(cache);
}
return cache;
} catch (Exception e) {
throw new CacheException("Error building standard cache decorators. Cause: " + e, e);
}
}

对于selectdeleteinsertupdate等标签的解析是通过buildStatementFromContext中创建XMLStatementBuilder来完成解析的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) { // 循环select|delte|insert|update节点
// 创建一个XMLStatementBuilder的构建器对象
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
public void parseStatementNode() {
String id = context.getStringAttribute("id"); // insert|delete|update|select标签的Id
String databaseId = context.getStringAttribute("databaseId"); // 判断insert|delete|update|select节点是否配置了数据库厂商标注
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return; // 匹配当前的数据库厂商id是否匹配当前数据源的厂商id
}
String nodeName = context.getNode().getNodeName(); // 获得节点名称:select|insert|update|delete
// 根据nodeName获得SqlCommandType枚举
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT; // 判断是不是select语句节点
// 获取flushCache属性,默认值为isSelect的反值:查询:默认flushCache=false,增删改:默认flushCache=true
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
// 获取useCache属性,默认值为isSelect:查询:默认useCache=true,增删改:默认useCache=false
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
// resultOrdered:是否需要处理嵌套查询结果group by(使用极少)可将比如30条数据拆分成三组组成一个嵌套的查询结果
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// 解析sql公用片段,将<include refid="selectInfo"></include>解析成sql语句放到<select>Node的子节点中
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
String parameterType = context.getStringAttribute("parameterType"); // 解析sql节点的参数类型
Class<?> parameterTypeClass = resolveClass(parameterType); // 把参数类型字符串转化为class
String lang = context.getStringAttribute("lang"); // 查看sql是否支撑自定义语言
// 获取自定义sql脚本语言驱动,默认:class org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
LanguageDriver langDriver = getLanguageDriver(lang);
// 解析insert语句的的selectKey节点, 一般在oracle里面设置自增id
processSelectKeyNodes(id, parameterTypeClass, langDriver);
KeyGenerator keyGenerator; // insert语句用于主键生成组件
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; //selectById!selectKey id+!selectKey
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); // 把命名空间拼接到keyStatementId中
if (configuration.hasKeyGenerator(keyStatementId)) { // 判断全局的配置类configuration中是否包含以及解析过的组件生成器对象
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
// 若配置了useGeneratedKeys则去除useGeneratedKeys配置值,否者查找mybatis-config.xml中是否配置了<setting name="useGeneratedKeys" value="true"></setting> 默认是false
// 并且判断sql操作类型是否为insert若是则使用Jdbc3KeyGenerator.INSTANCE生成策略,否则使用NoKeyGenerator.INSTANCE生产策略
keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
// 通过XMLLanguageDriver来解析sql脚本对象,只是解析成一个个的SqlNode,并不会完全解析sql,因为这时参数都没确定,动态sql无法解析
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
// STATEMENT,PREPARED或CALLABLE中的一个。这会让MyBatis分别使用Statement,PreparedStatement或CallableStatement,默认值:PREPARED
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
// 这是一个给驱动的提示,尝试让驱动程序每次批量返回的结果行数和这个设置值相等。 默认值为未设置(unset)(依赖驱动)
Integer fetchSize = context.getIntAttribute("fetchSize");
// 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为unset未设置,依赖驱动
Integer timeout = context.getIntAttribute("timeout");
// 将会传入这条语句的参数类的完全限定名或别名。可选属性,可通过类型处理器TypeHandler推断出具体传入语句的参数,默认值为unset未设置
String parameterMap = context.getStringAttribute("parameterMap");
// 从这条语句中返回的期望类型的类的完全限定名或别名,注意若返回的是集合,则应设置为集合包含的类型,而不是集合本身,可使用resultType或resultMap,但不能同时使用
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType); // 解析查询结果集返回的类型
// 外部resultMap的命名引用,结果集的映射是Mybatis最强大的特性,可使用resultMap或resultType,但不能同时使用。
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
// 解析keyProperty keyColumn仅适用于insert和update
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
// 为insert|delete|update|select节点构建成mappedStatment对象
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

在这里会通过XMLIncludeTransformer#applyIncludes递归目标标签,将其中的include标签引用的sql替换成文本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public void applyIncludes(Node source) {
Properties variablesContext = new Properties();
Properties configurationVariables = configuration.getVariables(); // 拿到之前配置文件解析的<properties>
Optional.ofNullable(configurationVariables).ifPresent(variablesContext::putAll); // 放入到variablesContext中
applyIncludes(source, variablesContext, false); // 替换Includes标签为对应的sql标签里面的值
}
private void applyIncludes(Node source, final Properties variablesContext, boolean included) {
if (source.getNodeName().equals("include")) {
Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext); // 拿到之前解析的<sql>
Properties toIncludeContext = getVariablesContext(source, variablesContext);
applyIncludes(toInclude, toIncludeContext, true); // 递归, included=true
if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
toInclude = source.getOwnerDocument().importNode(toInclude, true);
}
// include的父节点=select将<select>里面的<include>替换成<sql>,那<include>.getParentNode就为Null了
source.getParentNode().replaceChild(toInclude, source);
while (toInclude.hasChildNodes()) {// <sql>.getParentNode()=select,在<sql>的前面插入<sql>中的sql语句
toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);
}
toInclude.getParentNode().removeChild(toInclude); // <sql>.getParentNode()=select, 移除select中的<sql>
int i = 0;
} else if (source.getNodeType() == Node.ELEMENT_NODE) { // 0
if (included && !variablesContext.isEmpty()) {
// replace variables in attribute values
NamedNodeMap attributes = source.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Node attr = attributes.item(i);
attr.setNodeValue(PropertyParser.parse(attr.getNodeValue(), variablesContext));
}
}
NodeList children = source.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
applyIncludes(children.item(i), variablesContext, included); // 递归
}// included=true 说明是从include递归进来的
} else if (included && (source.getNodeType() == Node.TEXT_NODE || source.getNodeType() == Node.CDATA_SECTION_NODE) && !variablesContext.isEmpty()) {
source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext)); // 替换sql片段中的 ${<properties解析到的内容>}
}
}

对于selectdeleteinsertupdate等标签内容具体解析是通过LanguageDriver#createSqlSource中创建XMLScriptBuilder来完成的,最终将解析出的sqlNode根据是否是动态SQL将其封装成DynamicSqlSourceRawSqlSource。判断是否是动态SQL是根据SQL中是否包含${}以及是否包含动态标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
public SqlSource parseScriptNode() {
/**
* 递归解析selectById这个sql元素会解析成
* 1层 MixedSqlNode <SELECT>
* 2层 WhereSqlNode <WHERE>
* 2层 IfSqlNode <IF> test="条件表达式"
* contexts= sql语句分: 1.TextSqlNode 带${} 2.StaticTextSqlNode
*/
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode); // 动态Sql源
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); // 静态Sql源,会在这里解析
}
return sqlSource;
}

首先会获取当前节点的所有子节点列表,判断子节点类型是否是动态标签节点,若不是则直接获取节点SQL文本,且判断文本中是否包含${}若包含这标识为动态SQL,将SQL文本封装到TextSqlNode中添加到结果列表中,否则将其封装为StaticTextSqlNode添加到结果列表中。

若节点是动态标签的节点即whereifforeach等标签节点,若是则获取对应处理的handler,handler列表是在XMLScriptBuilder构造函数中调用initNodeHandlerMap就已初始化完成。对于各种动态标签因为可以嵌套,所以handleNode内部也是递归处理的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
protected MixedSqlNode parseDynamicTags(XNode node) { // 解析${}和动态节点
List<SqlNode> contents = new ArrayList<>();
NodeList children = node.getNode().getChildNodes(); //获得<select>的子节点
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody(""); // 获得sql文本
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) { // 判断sql文本中是否有${},有则isDynamic = true
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlerMap.get(nodeName); // 根据当前标签名称获取当前动态节点的NodeHandler,构造方法中调用initNodeHandlerMap进行初始化
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
handler.handleNode(child, contents); // 不同动态节点有不用的实现
isDynamic = true;
}
}
return new MixedSqlNode(contents);
}
public class XMLScriptBuilder extends BaseBuilder {
private final Map<String, NodeHandler> nodeHandlerMap = new HashMap<>();
private void initNodeHandlerMap() {
nodeHandlerMap.put("trim", new TrimHandler());
nodeHandlerMap.put("where", new WhereHandler());
nodeHandlerMap.put("set", new SetHandler());
nodeHandlerMap.put("foreach", new ForEachHandler());
nodeHandlerMap.put("if", new IfHandler());
nodeHandlerMap.put("choose", new ChooseHandler());
nodeHandlerMap.put("when", new IfHandler());
nodeHandlerMap.put("otherwise", new OtherwiseHandler());
nodeHandlerMap.put("bind", new BindHandler());
}
}
private class WhereHandler implements NodeHandler {
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
targetContents.add(where);
}
}
private class IfHandler implements NodeHandler {
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle); //递归解析
String test = nodeToHandle.getStringAttribute("test"); // 得到表达式
IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
targetContents.add(ifSqlNode);
}
}

将每个增删改查SQL解析完成后都封装成一个MappedStatement并添加到Configuration#mappedStatements中,依次遍历解析完所有的XML文件中的SQL,到此XML解析完成。

解析完XML后就改解析Mapper接口中的注解了,首先解析@CacheNamespace@CacheNamespaceRef注解。然后解析方法上的SQL注解。在parseStatement中通过getSqlSourceFromAnnotations然后通过LanguageDriver去将注解上的SQL解析成SQLNode列表封装成DynamicSqlSourceRawSqlSource

对于@SelectProvider@InsertProvider@UpdateProvider@DeleteProvider注解的方法解析最终会封装成一个ProviderSqlSource

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) { // 是否已经解析mapper接口
loadXmlResource(); // 根据mapper接口名获取xml文件并解析,解析<mapper></mapper>里面所有东西放到configuration
configuration.addLoadedResource(resource); // 添加已解析的标记
assistant.setCurrentNamespace(type.getName());
parseCache(); // 解析接口上@CacheNamespace注解
parseCacheRef(); // 解析接口上@CacheNamespaceRef注解
Method[] methods = type.getMethods(); // 获取所有方法看是否使用了注解
for (Method method : methods) {
try {
if (!method.isBridge()) {
parseStatement(method); // 是不是用了注解,用了注解会将注解解析成MappedStatement
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
try {
Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
if (sqlAnnotationType != null) {
if (sqlProviderAnnotationType != null) {
throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
}
Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
} else if (sqlProviderAnnotationType != null) {
Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method);
}
return null;
} catch (Exception e) {
throw new BuilderException("Could not find value method on SQL annotation. Cause: " + e, e);
}
}
private SqlSource buildSqlSourceFromStrings(String[] strings, Class<?> parameterTypeClass, LanguageDriver languageDriver) {
final StringBuilder sql = new StringBuilder();
for (String fragment : strings) {
sql.append(fragment);
sql.append(" ");
}
return languageDriver.createSqlSource(configuration, sql.toString().trim(), parameterTypeClass);
}
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
// issue #3
if (script.startsWith("<script>")) {
XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
} else {
// issue #127
script = PropertyParser.parse(script, configuration.getVariables());
TextSqlNode textSqlNode = new TextSqlNode(script);
if (textSqlNode.isDynamic()) {
return new DynamicSqlSource(configuration, textSqlNode);
} else {
return new RawSqlSource(configuration, script, parameterType);
}
}
}