1、解析过程中,一些重要的类,如下:
1、XMLMapperBuilder,一个mapper文件一个builder
2、MapperBuilderAssistant,XMLMapperBuilder的助手
3、Cache,缓存对象
4、XMLStatementBuilder,一条SQL一个MapperStatement
5、KeyGenerator,主键生成策略
6、XMLScriptBuilder,SQL内容解析Builder
7、DynamicSqlSource,具有动态标签的SQL;RawSqlSource,不具有动态标签的SQL
8、GenericTokenParser,占位符解析${},#{}
2、入口看到XMLConfigBuilder.parseConfiguration(),解析Mybatis配置文件中的标签
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
3、next,看到mapperElement()方法,进入到方法里,可以看到有几种解析mapper标签的方式(url,resource,mapperClass)
4、重点看XMLMapperBuilder中的parse()方法,看到了builderAssistant助手,只是为了记录信息,方便在其它地方能获取到当前mapper的信息,比如说namespace,mapperstatement等等
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);
// 当前名称空间是否和其它namespace使用同一个缓存
cacheRefElement(context.evalNode("cache-ref"));
// 当前mapper是否开启缓存 使用namespace作为cacheId
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
// sql解析
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);
}
}
5、如果mapper.xml中有cache-ref或者cache,就会用到Mybatis中的二级缓存,这个另外再说,因为缓存又是一个模块。next,重点还是看buildStatementFromContext(),解析curd
6、点进去,就能看到XMLStatementBuilder了,Mybatis在解析中大量使用建造者模式,可能是因为属性比较多,用set方式太鸡肋;顾名思义,XMLStatementBuilder就是构建MapperStatement
7、看到XMLStatementBuilder中的parseStatementNode(),这个就是解析sql标签的方法,这里主要看SqlSource的解析:createSqlSource
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
8、XMLScriptBuilder,sql内容解析的builder;DynamicSqlSource和RawSqlSource都实现了SqlSource
public SqlSource parseScriptNode() {
// 解析动态标签
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource = null;
if (isDynamic) {
// sql中是否有动态标签 if where foreach ...
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
// 没有动态标签
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
9、parseDynamicTags,动态标签解析,里面可以看到不同的动态标签会有不同的handler,分别处理,最后解析成SqlNode添加到容器里,解析表达式会用到Ognl
10、上面是大概的mapper文件解析内容,最后说说主键生成策略KeyGenerator,是一个接口,里面有两个方法,
// 针对Sequence主键而言,在执行insert sql前必须指定一个主键值给要插入的记录
void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
// 针对自增主键的表,在插入时不需要主键,而是在插入过程自动获取一个自增的主键
void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);