目录
1.1 通过build方式创建SqlSessionFactory
1.3 properties标签的解析方法propertiesElement
1.4 typeAliases标签的解析方法typeAliasesElement
1.5 plugins标签的解析方法pluginElement
1.6 objectFactory等标签的解析方法objectFactoryElement
1.7 settings标签的解析方法settingsElement
1.8 environments标签的解析方法environmentsElement
1.9 databaseIdProvider标签的解析方法databaseIdProviderElement
1.10 typeHandlers标签的解析方法typeHandlerElement
2.2 XMLMapperBuilder构造器调用parse方法
2.3 解析mapper.xml文件方法configurationElement
2.4 解析cache-ref标签方法cacheRefElement
2.6 解析parameterMap标签方法parameterMapElement
2.7 解析resultMap标签方法resultMapElements
2.9 解析select|insert|update|delete标签方法buildStatementFromContext
2.10 XMLStatementBuilder构造器解析select|insert|update|delete标签
2.11 XMLScriptBuilder构造器创建SqlSource对象
注:观看本篇前请先阅读(二)Mybatis持久化框架原理之架构组成以完成基本框架的认识,且本篇较长,需要一步一步慢慢看并结合实际使用经验来理解。
一、对于mybatis配置文件的加载
1.对config.xml文件的加载
1.1 通过build方式创建SqlSessionFactory
mybatis对配置文件加载的入口便是
SqlSessionFactoryBuilder源码如下SqlSessionFactoryBuilder构造器,在这里面完成解析config.xml文件,并且将获得的configuration对象返回,并创建SqlSessionFactory对象。
SqlSessionFactoryBuilder关键源码如下:
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream) {
// 一般使用方法都是把文件流传入进来,直接进行解析
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream,
String environment, Properties properties) {
try {
// 创建config.xml文件解析构造器
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream,
environment, properties);
// parser.parse方法返回的是configuration对象
return build(parser.parse());
} catch (Exception e) {
...
} finally {
...
}
}
public SqlSessionFactory build(Configuration config) {
// 直接返回默认的SqlSessionFactory对象
return new DefaultSqlSessionFactory(config);
}
}
具体看到build方法中,先实例化XMLConfigBuilder,使用SAX读取XML文件内容,有兴趣的可以看下其大致读取流程。
1.2 XMLConfigBuilder调用parse方法
接下来看看XMLConfigBuilder类中如何一步一步的把XML文件中的节点解析出来,并形成Configuration对象的。
XMLConfigBuilder相关部分源码如下:
public class XMLConfigBuilder extends BaseBuilder {
private boolean parsed;
private XPathParser parser;
private String environment;
private ReflectorFactory localReflectorFactory =
new DefaultReflectorFactory();
private XMLConfigBuilder(XPathParser parser, String environment,
Properties props) {
// 直接实例化Configuration对象
super(new Configuration());
// 传进来的Properties如果不为空则在这里赋值
this.configuration.setVariables(props);
this.parsed = false;
// 传进来的environment如果不为空则在这里赋值
this.environment = environment;
this.parser = parser;
}
public Configuration parse() {
// 如果解析过再调用直接抛出异常
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used"+
" once.");
}
parsed = true;
// 开始解析Configuration节点下面的节点
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
// 开始一次解析十一种不同的节点
try {
Properties settings =
settingsAsPropertiess(root.evalNode("settings"));
propertiesElement(root.evalNode("properties"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
...
}
}
}
XMLConfigBuilder对象中的parse解析方法只能解析一次,第一次解析后parsed将会为true,后续再次调用这个方式会抛出BuilderException异常。
关于parser调用的方法evalNode其核心是使用XPath对其内部的节点进行查找,并将其解析为XNode对象,下面可以看看其对象内部封装的属性和后面将要使用到的方法。
public class XNode {
private Node node;
private String name;
private String body;
private Properties attributes;
private Properties variables;
private XPathParser xpathParser;
public Properties getChildrenAsProperties() {
Properties properties = new Properties();
for (XNode child : getChildren()) {
String name = child.getStringAttribute("name");
String value = child.getStringAttribute("value");
if (name != null && value != null) {
properties.setProperty(name, value);
}
}
// 返回的properties将会有name-value对
return properties;
}
public String getStringAttribute(String name, String def) {
String value = attributes.getProperty(name);
if (value == null) {
return def;
} else {
return value;
}
}
public List<XNode> getChildren() {
List<XNode> children = new ArrayList<XNode>();
NodeList nodeList = node.getChildNodes();
if (nodeList != null) {
for (int i = 0, n = nodeList.getLength(); i < n; i++) {
Node node = nodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
children.add(new XNode(xpathParser, node, variables));
}
}
}
return children;
}
}
该类里面有等下会经常用到的几个方法,现在详细介绍一下:
- getChildrenAsProperties方法:获得该节点property标签的name-value对;
- getStringAttribute方法:根据name来获得String类型的属性值;
- getChildren方法:获得该节点的所有子节点。
回到XMLConfigBuilder类的parseConfiguration方法中来,可以看到这个方法对Configuration节点的子节点分别设置了一个方法去解析,也可以看得出来Mybatis的xml配置文件具体有多少子节点,虽然前面一篇已经介绍过,但在这里也简单的带过一下依次介绍配置类中的各个节点作用:
- properties:用来将另外properties文件中的配置属性加入到Configuration对象的variables对象中,以便后续使用,需要注意的是只能有一个这样类型的标签,并且先读取resource属性,后读取url属性,且两者只能同时使用其中一种;
- settings:Mybatis框架的运行配置,诸如logImpl、cacheEnabled和defaultExecutorType这些常见的配置都是在这个标签中声明子标签完成的;
- typeAliases:用来手动添加一些类的别名,如有个XXX.XXX.XXX.NornalStudent类,便可以使用student来作为其类的别名;
- plugins:用来添加插件,可添加多个,内部的原理是拦截器链;
- objectFactory:确定objectFactory的类型,只能同时存在一个;
- objectWrapperFactory:同objectFactory;
- reflectorFactory:同objectFactory;
- environments:用来配置数据源和事务管理器,能有多个配置,但只会取其中的一个;
- databaseIdProvider:顾名思义,确定刚配置的数据源名称;
- typeHandlers:添加一些额外类型处理器,如自己实现了custom类型的类型处理,则在这个标签里面添加进去即可;
- mappers:多种配置方式,但大体上可分为两者,一种是直接确定mapper的接口包位置,然后将包下的添加进mapperRegistry对象中;第二种是决定单一的接口类或者指向一个XML文件,如果指定了单一的接口类,则直接添加进mapperRegistry对象中,如果是指向一个XML文件,则需要调用XMLMapperBuilder去解析这个XML文件,从而获得它的标签内容。
同时需要注意的是,在config.xml文件中,必须得按照properties->settings-> typeAliases-> typeHandlers->objectFactory->objectWrapperFactory->reflectorFactory->plugins-> environments->databaseIdProvider->mappers的顺序来创建标签内容,否则会抛出BuilderException异常。
1.3 properties标签的解析方法propertiesElement
方法源码如下:
public class XMLConfigBuilder extends BaseBuilder {
private void propertiesElement(XNode context) throws Exception {
// 如果properties节点不为空
if (context != null) {
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
// 如果url和resource属性同时存在则抛异常,只能同时存在一个
if (resource != null && url != null) {
throw new BuilderException();
}
// 两者取其一
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
// 将解析获得的Properties对象赋值给variables
configuration.setVariables(defaults);
}
}
}
properteis标签中可以有resource和url属性,并且可以同时存在,但存放的时候仅会存放其中一个,并且resource属性的值会被优先存放。在最开始的时候就会通过getChildrenAsProperties方法所有的name-value对,并形成初始的peroperties集合。
1.4 typeAliases标签的解析方法typeAliasesElement
方法源码如下:
public class XMLConfigBuilder extends BaseBuilder {
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry()
.registerAliases(typeAliasPackage);
} else {
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '"+
alias + "'. Cause: " + e, e);
}
}
}
}
}
}
typeAliases节点下可以有两种节点名字,一个是package,一个是typeAlias,此两种只能存在一种,package表示直接把包下面的所有实体都包括进来,用类的名字当做别名。而typeAlias标签中如果只有type,也把类的名字当成别名,否则使用alias当别名。
1.5 plugins标签的解析方法pluginElement
方法源码如下:
public class XMLConfigBuilder extends BaseBuilder {
private void pluginElement(XNode parent) throws Exception {
// 节点不为空
if (parent != null) {
// 获取里面的plugin节点
for (XNode child : parent.getChildren()) {
// interceptor对象将是class全路径
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance =
(Interceptor) resolveClass(interceptor).newInstance();
interceptorInstance.setProperties(properties);
// 将拦截器添加到configuration对象中
configuration.addInterceptor(interceptorInstance);
}
}
}
}
为mybatis注册插件,从官方给出的变量名也可以看出可以把插件当成mybatis的拦截器,其每个拦截器标签中都有property的name-value对。
1.6 objectFactory等标签的解析方法objectFactoryElement
对于objectFactory、objectWrapperFactory和reflectorFactory这三个节点的解析基本上流程都是一致的,因此只需要看其中一个便可。
解析objectFactory方法源码如下:
public class XMLConfigBuilder extends BaseBuilder {
private void objectFactoryElement(XNode context) throws Exception {
// 节点不为空
if (context != null) {
// 指定的type类型
String type = context.getStringAttribute("type");
Properties properties = context.getChildrenAsProperties();
ObjectFactory factory = (ObjectFactory) resolveClass(type)
.newInstance();
factory.setProperties(properties);
configuration.setObjectFactory(factory);
}
}
}
获得name-value对并设置objectFactory对象(如果是objectWrapperFactory就设置configuration中对应的)。
1.7 settings标签的解析方法settingsElement
方法源码如下:
public class XMLConfigBuilder extends BaseBuilder {
private Properties settingsAsPropertiess(XNode context) {
// 如果setting节点为空则返回空的Properties对象
if (context == null) {
return new Properties();
}
Properties props = context.getChildrenAsProperties();
MetaClass metaConfig = MetaClass.forClass(Configuration.class,
localReflectorFactory);
// 循环判断setting的属性值
for (Object key : props.keySet()) {
// 如果对应的setting配置对象没有set否则抛异常
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException();
}
}
return props;
}
private void settingsElement(Properties props) throws Exception {
// 获取setting节点中的属性,并set进configuration对应的对象中
// 具体有哪些这里忽略,看了也没多大意义,有兴趣的可以去看下源码
// 最常使用的也就四五个
...
}
}
该方法直接将settingsAsPropertiess获得的属性依次进行类型转换并设置进Configuration对象相应的成员属性。属性有很多,但一般最常使用的配置就几个,说明如下:
- localCacheScope:一级缓存,一共两种:SESSION,STATEMENT,默认SESSION,即启用一级缓存,STATEMENT则是不开启;
- cacheEnabled:是否开启二级缓存,默认是开启的;
- defaultExecutorType:默认的Executor类型,一共三种:SIMPLE、REUSE和BATCH;
- jdbcTypeForNull:对参数值为null的应该使用哪种类型的jdbcType类处理,默认OTHER,如果想要传入参数允许为null,这个参数设置为NULL类型即可;
- logImpl:指定mybatis的日志打印类。
1.8 environments标签的解析方法environmentsElement
方法源码如下:
public class XMLConfigBuilder extends BaseBuilder {
private void environmentsElement(XNode context) throws Exception {
// 节点不为空
if (context != null) {
if (environment == null) {
// 如果environment对象为空,则取节点的default默认配置
environment = context.getStringAttribute("default");
}
// environment配置可以有多个,因此循环
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
// 符合条件的id才会被解析:如果配置了environment,且id和environment
// 一致则进入if代码块
if (isSpecifiedEnvironment(id)) {
TransactionFactory txFactory = transactionManagerElement(
child.evalNode("transactionManager"));
DataSourceFactory dsFactory = dataSourceElement(
child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
// 获取到了数据源和对应的事务工厂,则构造一个Environment对象
// 保存数据源等信息,尽管environment标签可以配置多个
// 但最终只有一个会被构造并赋值给configuration对象
Environment.Builder environmentBuilder =
new Environment.Builder(id)
.transactionFactory(txFactory).dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
}
从代码中可以看到首先会从environments节点中取到default属性,确认默认的environment,只有当后续节点有相应的id,才会设置,否则configuration对象中的environment将会为空,后续操作数据库将会报空指针异常,并且default属性必须得设置,否则会抛BuilderException异常。获得environment属性成功后将会解析dataSource节点,获得数据源信息和事务工厂信息。
1.9 databaseIdProvider标签的解析方法databaseIdProviderElement
方法源码如下:
public class XMLConfigBuilder extends BaseBuilder {
private void databaseIdProviderElement(XNode context) throws Exception {
DatabaseIdProvider databaseIdProvider = null;
// 节点不为空
if (context != null) {
String type = context.getStringAttribute("type");
// 兼容以前的老版本
if ("VENDOR".equals(type)) {
type = "DB_VENDOR";
}
Properties properties = context.getChildrenAsProperties();
databaseIdProvider = (DatabaseIdProvider) resolveClass(type)
.newInstance();
databaseIdProvider.setProperties(properties);
}
Environment environment = configuration.getEnvironment();
if (environment != null && databaseIdProvider != null) {
String databaseId = databaseIdProvider
.getDatabaseId(environment.getDataSource());
// 设置数据源环境的databaseId绑定关系
configuration.setDatabaseId(databaseId);
}
}
}
此方法为指定数据源提供id类,其中有个兼容旧版本补丁写在了代码中,即使用VENDOR来兼容以前的DB_VENDOR类型。
1.10 typeHandlers标签的解析方法typeHandlerElement
方法源码如下:
public class XMLConfigBuilder extends BaseBuilder {
private void typeHandlerElement(XNode parent) throws Exception {
// 节点不为空
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
// 如果是package方式,则直接获取name属性,并直接注册,里面会把
// 内部类和接口剔除,剩余的注册进TypeHandlerRegistry
String typeHandlerPackage = child.getStringAttribute("name");
typeHandlerRegistry.register(typeHandlerPackage);
} else {
// 单个的类型设置
String javaTypeName = child.getStringAttribute("javaType");
String jdbcTypeName = child.getStringAttribute("jdbcType");
String handlerTypeName = child.getStringAttribute("handler");
Class<?> javaTypeClass = resolveClass(javaTypeName);
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
Class<?> typeHandlerClass = resolveClass(handlerTypeName);
// 各个属性不同的配置会进入不同的注册方法,但最终都会注册到
// TypeHandlerRegistry的各个对象集合中
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass,
typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType,
typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
}
类似typeAliases标签一样,该标签的子标签也有两种,一个package,一个typeHandler,处理方法差不多一个逻辑。该方法是给用户自定义类型处理器的,注入string、integer都是mybatis为Java官方设置的类型处理器,该标签允许用户自定义类型处理器,确定具体的java类型,对应的数据库类型和具体的处理器类。
至此,除了mapper之外,config.xml文件的其它标签便已经解析完成。
2.mapper.xml文件的解析
2.1 解析mapper.xml构造器入口
方法源码如下:
public class XMLConfigBuilder extends BaseBuilder {
private void mapperElement(XNode parent) throws Exception {
// 节点不为空
if (parent != null) {
// 节点可能有多个,因此循环处理
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
// 如果是package类型的,将会把包下的接口类都拿出来注册进mapperRegistry
// 对象中,里面最终也会调用addMapper方法,这个后面会说
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// resource、url和class三个文件有且只能有一个,如果三个都为空
// 则会抛出BuilderException异常,其中resource和url方式将会
// 调用XMLMapperBuilder对象对引入的mapper.xml文件进行解析
if (resource != null && url == null && mapperClass == null) {
InputStream inputStream = Resources
.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(
inputStream, configuration, resource,
configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null &&
mapperClass == null) {
// url和resource的处理方式差不多,只是调用Resources类的方法不一样
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类型,则直接把class对象添加进mapperRegistry中,
// 当然这种配置方式和package配置方式都可以在接口里面配置mybatis的
// 相关注解,当然如果不配置,在resource文件夹下名字相同且namespace
// 指向同一个xml,mybatis还没有读取这个xml情况下,框架会自动读取
// 对应的xml文件并解析(前提是xml和对应的接口在同一路径下)
// 有兴趣的可以自行去看addMapper方法的逻辑
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException();
}
}
}
}
}
}
类似typeAliases标签一样,该标签的子标签也有两种,一个package,一个typeHandler,处理方法差不多一个逻辑。只是针对mappers下面的mapper标签不同的处理是resource、url和class三个属性只能同时存在一个,否则会抛出BuilderException异常。
使用resource和url两个属性mybatis将会进行一些额外的处理,如果直接使用class指定类,那么调用configuration的addMapper方法即可,但也是需要解析的,具体的解析方法后续分析。
通过上面的这些步骤后,configuration对config.xml文件将解析完成,并把其中的信息放入configuration对象中,后续通过调用build方法实例化DefaultSqlSessionFactory对象并返回,此步骤将完成SqlSessionFactory的构建。
2.2 XMLMapperBuilder构造器调用parse方法
其中mapperParser.parse()方法和configuration.addMapper()这两个方法是将mapper添加进mybatis中的核心方法,我们先看mapperParser.parse()方法源码
public class XMLMapperBuilder extends BaseBuilder {
public void parse() {
// 如果resource没有被加载过则进行加载
if (!configuration.isResourceLoaded(resource)) {
// 解析mapper节点引入的xml文件
configurationElement(parser.evalNode("/mapper"));
// 把解析过的xml添加到loadedResources对象中
configuration.addLoadedResource(resource);
// 将xml文件和接口进行namespace绑定,并把绑定的接口添加进
// mapperRegistry中
bindMapperForNamespace();
}
// 对resultMap解析失败的进行再次解析
parsePendingResultMaps();
// 对cacheRef解析失败的进行再次解析
parsePendingCacheRefs();
// 对statement解析失败的进行再次解析
parsePendingStatements();
}
}
parse方法if语句中bindMapperForNamespace方法之前是为了判断resource字段是否已经被处理过,若处理过则不再处理。bindMapperForNamespace方法是处理resource和url类型mapper的真正方法,下面几个Pending方法则是为了处理在bindMapperForNamespace方法中处理失败的mapper。
2.3 解析mapper.xml文件方法configurationElement
configurationElement方法则是解析mapper.xml文件的主要方法,configurationElement方法源码如下:
public class XMLMapperBuilder extends BaseBuilder {
private XPathParser parser;
private MapperBuilderAssistant builderAssistant;
private Map<String, XNode> sqlFragments;
private void configurationElement(XNode context) {
try {
// 先获取mapper.xml文件的namespace,且这个属性不能为空
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("");
}
// 设置mapper读取助理类的namespace命名空间
builderAssistant.setCurrentNamespace(namespace);
// 接下来解析mapper.xml文件中的积累标签,如果途中解析失败
// 则抛出BuilderException异常
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(
context.evalNodes("select|insert|update|delete")
);
} catch (Exception e) {
throw new BuilderException();
}
}
}
该方法将会对mapper中的parameterMap、resultMap、sql以及增删改查的各种标签进行解析,将其分别解析成ParameterMapping、ResultMapping、sqlFragments以及MappedStatement,其中MappedStatement将包含ParameterMapping和ResultMapping。
2.4 解析cache-ref标签方法cacheRefElement
其方法源码如下:
private void cacheRefElement(XNode context) {
// 开始处理<cacheRef/>标签
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);
}
}
}
可以看到方法十分简单,单纯的将当前的namespace和引入的namespace做个关联,解析器里面做的操作也是类似的差不多。
2.5 解析cache标签方法cacheElement
关键方法源码如下:
private void cacheElement(XNode context) throws Exception {
// 开始处理<cache/>标签
if (context != null) {
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass =
typeAliasRegistry.resolveAlias(type);
// 确定不同的缓存算法
// 缓存收回策略。LRU(最近最少使用的),FIFO(先进先出),SOFT( 软引用)
// WEAK( 弱引用)
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass =
typeAliasRegistry.resolveAlias(eviction);
// 刷新间隔
Long flushInterval = context.getLongAttribute("flushInterval");
// 可被设置为任意正整数,缓存的对象数目等于运行环境的可用内存
// 资源数目,默认是1024
Integer size = context.getIntAttribute("size");
// 只读,true或false。只读的缓存会给所有的调用者返回缓存对象的相同实例
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);
}
}
2.6 解析parameterMap标签方法parameterMapElement
接下来请看parameterMapElement方法解析parameterMap源码:
private void parameterMapElement(List<XNode> list) throws Exception {
// 开始处理<parameterMap/>标签,对接点进行遍历
for (XNode parameterMapNode : list) {
String id = parameterMapNode.getStringAttribute("id");
String type = parameterMapNode.getStringAttribute("type");
Class<?> parameterClass = resolveClass(type);
List<XNode> parameterNodes = parameterMapNode
.evalNodes("parameter");
List<ParameterMapping> parameterMappings =
new ArrayList<ParameterMapping>();
// 对parameter再分别进行遍历
for (XNode parameterNode : parameterNodes) {
String property = parameterNode.getStringAttribute("property");
String javaType = parameterNode.getStringAttribute("javaType");
String jdbcType = parameterNode.getStringAttribute("jdbcType");
String resultMap =
parameterNode.getStringAttribute("resultMap");
String mode = parameterNode.getStringAttribute("mode");
String typeHandler =
parameterNode.getStringAttribute("typeHandler");
Integer numericScale =
parameterNode.getIntAttribute("numericScale");
ParameterMode modeEnum = resolveParameterMode(mode);
Class<?> javaTypeClass = resolveClass(javaType);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
@SuppressWarnings("unchecked")
Class<? extends TypeHandler<?>> typeHandlerClass =
(Class<? extends TypeHandler<?>>)
resolveClass(typeHandler);
// 使用前面获得的各个属性直接调用builderAssistant构造
// ParameterMapping对象,实际上这里面也没做什么,只是把javaType和
// TypeHandler两个类型具体确认了下来,然后再直接赋值
ParameterMapping parameterMapping =
builderAssistant.buildParameterMapping(parameterClass,
property, javaTypeClass, jdbcTypeEnum,
resultMap, modeEnum, typeHandlerClass,
numericScale);
parameterMappings.add(parameterMapping);
}
builderAssistant.addParameterMap(id, parameterClass,
parameterMappings);
}
}
public ParameterMap addParameterMap(String id, Class<?> parameterClass,
List<ParameterMapping> parameterMappings) {
id = applyCurrentNamespace(id, false);
// 调用ParameterMap的内部类构造器,构造ParameterMap对象
// 这里面的构造方法很简单,只是把parameterMappings对象赋值到
// parameterMap对象中,并没有做特殊的操作(只进行了把parameterMappings
// 变成不可变的集合对象)
ParameterMap parameterMap = new ParameterMap.Builder(configuration,
id, parameterClass, parameterMappings).build();
// 添加进configuration的对象中
configuration.addParameterMap(parameterMap);
return parameterMap;
}
该方法对应了xml中的parameterMap标签,parameterMap标签最多只能到parameter那一层,因此只需要将parameterMap标签的id和type拿到,进而对子标签(parameter)各个属性进行相应的解析并构造进ParameterMapping对象即可。最后将一个个的ParameterMapping对象通过builderAssistant.addParameterMap()方法将id、parameter类型和ParameterMapping对象构造成ParameterMap对象,最后添加进configuration对象中。
2.7 解析resultMap标签方法resultMapElements
resultMapElements方法解析resultMap源码:
private void resultMapElements(List<XNode> list) throws Exception {
for (XNode resultMapNode : list) {
try {
// 直接遍历调用
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
...
}
}
}
private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
// 默认additionalResultMappings是空集合
return resultMapElement(resultMapNode, Collections.<ResultMapping>
emptyList());
}
private ResultMap resultMapElement(XNode resultMapNode,
List<ResultMapping> additionalResultMappings) throws Exception {
// 开始处理<resultMap/>标签
// getStringAttribute方法有两个参数,第一个参数key如果有则返回,否则返回
// 第二个默认参数,因此这里的获取顺序是id>getValueBasedIdentifier值
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
// 根据上面的可知,type > ofType > resultType > javaType
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
// 这是新的一层
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
// 先把additionalResultMappings参数值加入进来,但实际上是空的
resultMappings.addAll(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
// 获得resultMap里面的各个子标签
for (XNode resultChild : resultChildren) {
// 这三种条件最终都会把id标签、discriminator等标签组合起来并添加到
// resultMappings集合中,实际上ResultMapping对象也是通过
// builderAssistant对象来构建的,实际进行的操作大致也是获得name-value
// 对,然后进行类型转换,再直接赋值到ResultMapping对象中,基本上没有
// 特殊处理
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<ResultFlag>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
// buildResultMappingFromContext方法就是调用builderAssistant对象
// 构建ResultMapping对象的地方,其它两种条件也都是调用这个方法
resultMappings.add(buildResultMappingFromContext(resultChild,
typeClass, flags));
}
}
ResultMapResolver resultMapResolver = new ResultMapResolver(
builderAssistant, id, typeClass, extend, discriminator,
resultMappings, autoMapping);
try {
// 调用解析方法,这个解析方法中只是将获得的resultMappings、id、type、
// mappedColumns和mappedProperties等在resultMap标签中可以配置的属性
// 依次赋值给ResultMap对象中对应的成员属性中
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
// 如果失败则添加到incompleteResultMaps集合中,等下再次解析
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
private void processConstructorElement(XNode resultChild,
Class<?> resultType, List<ResultMapping> resultMappings)
throws Exception {
// 当标签类型是<constructor/>时将被调用
List<XNode> argChildren = resultChild.getChildren();
for (XNode argChild : argChildren) {
List<ResultFlag> flags = new ArrayList<ResultFlag>();
flags.add(ResultFlag.CONSTRUCTOR);
// id标签类型
if ("idArg".equals(argChild.getName())) {
flags.add(ResultFlag.ID);
}
// 最终还是会调用buildResultMappingFromContext方法
resultMappings.add(buildResultMappingFromContext(argChild,
resultType, flags));
}
}
private ResultMapping buildResultMappingFromContext(XNode context,
Class<?> resultType, List<ResultFlag> flags) throws Exception {
// 最终的constructor、discriminator以及其它的标签名字都会调用到这个方法中
// 获取property对应的对象字段
String property = context.getStringAttribute("property");
// 数据库字段对应名称
String column = context.getStringAttribute("column");
// java类型
String javaType = context.getStringAttribute("javaType");
// jdbcType数据库类型
String jdbcType = context.getStringAttribute("jdbcType");
// 是否要调用其它的sql语句来进行额外的查询操作
String nestedSelect = context.getStringAttribute("select");
// 只有association、collection和case三种类型的标签才会有resultMap属性
// processNestedResultMappings方法后续会仔细分析
String nestedResultMap = context.getStringAttribute("resultMap",
processNestedResultMappings(context,
Collections.<ResultMapping> emptyList()));
String notNullColumn = context.getStringAttribute("notNullColumn");
String columnPrefix = context.getStringAttribute("columnPrefix");
String typeHandler = context.getStringAttribute("typeHandler");
String resultSet = context.getStringAttribute("resultSet");
String foreignColumn = context.getStringAttribute("foreignColumn");
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType",
configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
// 熟悉的javaTypeClass和TypeHandler类型转换
Class<?> javaTypeClass = resolveClass(javaType);
@SuppressWarnings("unchecked")
Class<? extends TypeHandler<?>> typeHandlerClass =
(Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
// 实际上只是对这些参数的简单赋值,没有影响全局的操作
return builderAssistant.buildResultMapping(resultType, property,
column, javaTypeClass, jdbcTypeEnum, nestedSelect,
nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass,
flags, resultSet, foreignColumn, lazy);
}
private String processNestedResultMappings(XNode context,
List<ResultMapping> resultMappings) throws Exception {
// 只有下面的三种类型标签才会有resultMap属性,这个属性会指向一个resultMap
if ("association".equals(context.getName())
|| "collection".equals(context.getName())
|| "case".equals(context.getName())) {
// 如果这里的select属性为空,则代表没有指定的sql查询语句
if (context.getStringAttribute("select") == null) {
// select为空,则调用resultMap属性,再去解析指向的resultMap
// 一直循环,因此这里需要注意是否存在循环依赖调用
ResultMap resultMap = resultMapElement(context,
resultMappings);
return resultMap.getId();
}
}
return null;
}
resultMap标签相对parameterMap标签而言相对复杂,因为collection、collection以及case这三个标签又可以嵌入相同的子标签,并且标签中还可能含有resultMap属性,因此存在递归调用,但总体而言,思路和parameterMap标签的解析差不多。
最开始解析type、ofType、javaType和resultType这四个属性时,因为resultMap、collection、case和association四个标签表达属性名字不一样,因此需要分别解析这四种属性,随后将会解析该标签的子标签,并判断子标签不同的名字进行不同的处理,其中constructor和discriminator是单独处理的,其他的标签将会由buildResultMappingFromContext方法统一处理。
constructor属性解析解析id和idArg标签时,还是会调用buildResultMappingFromContext方法,而discriminator调回调用processDiscriminatorElement方法,解析本标签属性和子标签case后,将会构造成Discriminator对象。
buildResultMappingFromContext方法解析到resultMap属性时,将会调用processNestedResultMappings方法,如果该方法判断标签名字是association、collection和case,则会将该标签当成resultMap标签调用resultMapElement进行处理,这样就完成了resultMap的多层父子关系的解析。调到无子标签时,掉会调用MapperBuilderAssistant类的buildResultMapping方法,在此方法中会调用parseCompositeColumnName方法对特殊的columnName进行解析,当全部解析完成后将会构造成ResultMapping对象,并最后创建ResultMapResolver对象,再调用resolve方法,将所有的标签解析成ResultMap对象。
2.8 解析sql标签方法sqlElement
接下来看sqlElement方法解析sql标签源码:
private void sqlElement(List<XNode> list) throws Exception {
// 两个方法都一样,只是会不会取databaseId而已
if (configuration.getDatabaseId() != null) {
sqlElement(list, configuration.getDatabaseId());
}
sqlElement(list, null);
}
private void sqlElement(List<XNode> list, String requiredDatabaseId)
throws Exception {
for (XNode context : list) {
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
id = builderAssistant.applyCurrentNamespace(id, false);
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
// 如果databaseId一致,则直接添加进sqlFragments集合对象中
sqlFragments.put(id, context);
}
}
}
该方法只是将sql标签的databaseId判断是否是属于当前的数据源,如果是则将标签和id存放进sqlFragments集合中。
2.9 解析select|insert|update|delete标签方法buildStatementFromContext
buildStatementFromContext方法解析select、insert、update和delete标签源码:
private void buildStatementFromContext(List<XNode> list) {
// 和sql标签一样
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list,
String requiredDatabaseId) {
// 循环遍历sql操作语句节点
for (XNode context : list) {
final XMLStatementBuilder statementParser =
new XMLStatementBuilder(configuration, builderAssistant,
context, requiredDatabaseId);
try {
// 交给了XMLStatementBuilder 构造器来解决
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
该类中的方法只是初步的将databaseId和节点分别封装成XMLStatementBuilder对象,进而调用parseStatementNode方法来解析sql语句标签,接下来看下XMLStatementBuilder中具体如何解析sql语句的。
2.10 XMLStatementBuilder构造器解析select|insert|update|delete标签
XMLStatementBuilder类源码如下:
public class XMLStatementBuilder extends BaseBuilder {
// mapper构造器助理类
private MapperBuilderAssistant builderAssistant;
// 当前拥有的select|insert|update|delete四种标签的一个
private XNode context;
private String requiredDatabaseId;
public void parseStatementNode() {
// 当前标签在mapper.xml中唯一的标识符
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
// 判断id、databaseId和requiredDatabaseId是否符合条件
if (!databaseIdMatchesCurrent(id, databaseId,
this.requiredDatabaseId)) {
return;
}
// 常用参数的属性
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
// langDriver的类型是需要注意的,如果没有设置取的实际上是configuration对象
// 中的defaultDriverClass,否则就是从使用lang从languageRegistry中注册获取
// 而languageRegistry没有在setting中设置属性的话,defaultDriverClass默认
// 类型是XMLLanguageDriver,并且里面也会注册一个RawLanguageDriver类
// 因此没有设置lang属性,langDriver类型就是XMLLanguageDriver,后面会用到
LanguageDriver langDriver = getLanguageDriver(lang);
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
// 一共有STATEMENT,PREPARED,CALLABLE三种类型,默认是PREPARED类型
StatementType statementType =
StatementType.valueOf(
context.getStringAttribute("statementType",
StatementType.PREPARED.toString()));
// 默认为null
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String nodeName = context.getNode().getNodeName();
// 共有UNKNOWN,INSERT,UPDATE,DELETE,SELECT,FLUSH六种类型,中间四种
// 分别对应四种不同的标签select|insert|update|delete
SqlCommandType sqlCommandType = SqlCommandType.valueOf(
nodeName.toUpperCase(Locale.ENGLISH));
// 字面意思,如果标签是SELECT,则isSelect为true
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 如果非select标签,则flushCache为false
boolean flushCache = context.getBooleanAttribute("flushCache",
!isSelect);
// 如果是isSelect,useCache则为true开启
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered",
false);
// 对sql操作中的<include/>标签进行转换的类
XMLIncludeTransformer includeParser =
new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// 对<selectKey/>标签进行操作的方法
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// 利用前面获得的langDriver对象创建对应的SqlSource对象,到这里我们已经知道了
// 默认langDriver的类型是XMLLanguageDriver,后续再进行分析
SqlSource sqlSource = langDriver.createSqlSource(configuration,
context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId,
true);
// 根据获得的keyStatementId对象生成对应的keyGenerator对象
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() &&
SqlCommandType.INSERT.equals(sqlCommandType))
? new Jdbc3KeyGenerator() : new NoKeyGenerator();
}
// 这里面实际上也都是将获得的resultMap、parameterMap和SqlSource等对象
// 通过MappedStatement对象中的构造器依次赋值进去,里面没有特殊的处理逻辑
builderAssistant.addMappedStatement(id, sqlSource, statementType,
sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass,
resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver,
resultSets);
}
private void processSelectKeyNodes(String id, Class<?> parameterTypeClass,
LanguageDriver langDriver) {
// 从context节点中获取selectKey节点
List<XNode> selectKeyNodes = context.evalNodes("selectKey");
// 又是判断databaseId是否为空后续都会调用进parseSelectKeyNodes方法
if (configuration.getDatabaseId() != null) {
parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass,
langDriver, configuration.getDatabaseId());
}
parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver,
null);
// 当selectKey节点被处理完之后便可以将此节点从context节点中删除了
// 因为后续已经用不到了
removeSelectKeyNodes(selectKeyNodes);
}
private void parseSelectKeyNodes(String parentId, List<XNode> list,
Class<?> parameterTypeClass, LanguageDriver langDriver,
String skRequiredDatabaseId) {
// 可能有多个
for (XNode nodeToHandle : list) {
String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
String databaseId = nodeToHandle.getStringAttribute("databaseId");
// 如果前一条语句的databaseId不为空,则跳过此语句里面的判断
if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) {
// 执行真正的selectKey标签
parseSelectKeyNode(id, nodeToHandle, parameterTypeClass,
langDriver, databaseId);
}
}
}
private void parseSelectKeyNode(String id, XNode nodeToHandle,
Class<?> parameterTypeClass, LanguageDriver langDriver,
String databaseId) {
// 解析返回类型
String resultType = nodeToHandle.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
// 默认是PREPARED类型的
StatementType statementType = StatementType.valueOf(
nodeToHandle.getStringAttribute("statementType",
StatementType.PREPARED.toString()));
// 对应的property名称,如果需要赋值到字段为id的,这个值设置成id即可
String keyProperty = nodeToHandle.getStringAttribute("keyProperty");
// 对应从数据库查询出来的名称,如果要获取数据库查出来的字段,设置成字段名称
// 即可获取
String keyColumn = nodeToHandle.getStringAttribute("keyColumn");
// 执行顺序,是在主要sql的前面(before)或者后面(after)执行
boolean executeBefore = "BEFORE".equals(
nodeToHandle.getStringAttribute("order", "AFTER"));
// 设置查询参数,不使用缓存(因为是伴生sql)
boolean useCache = false;
boolean resultOrdered = false;
KeyGenerator keyGenerator = new NoKeyGenerator();
Integer fetchSize = null;
Integer timeout = null;
boolean flushCache = false;
String parameterMap = null;
String resultMap = null;
ResultSetType resultSetTypeEnum = null;
// 根据前面的langDriver的对象再获得SqlSource,因此langDriver的分析是必要的
SqlSource sqlSource = langDriver.createSqlSource(configuration,
nodeToHandle, parameterTypeClass);
// 只能是select
SqlCommandType sqlCommandType = SqlCommandType.SELECT;
// 直接根据前面的参数构造一个MappedStatement并添加进configuration中
builderAssistant.addMappedStatement(id, sqlSource, statementType,
sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap,
resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver,
null);
id = builderAssistant.applyCurrentNamespace(id, false);
MappedStatement keyStatement = configuration.getMappedStatement(id,
false);
// 生成一个KeyGenerator对象,并拥有MappedStatement对象和对应的id
configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement,
executeBefore));
}
}
先看入口方法parseStatementNode,此方法最开始会判断被封装的标签databaseId是否一致,如果不一致则返回。否则会开始解析标签的timeout、parameterMap、resultMap、resultType等属性,其中statementType属性默认是PREPARED预处理语句,而SqlCommandType将会根据标签转为大写后的值设置类型,并得到是否是插入的标识,而langDriver会被赋值成默认XMLLanguageDriver类型。
后续调用XMLIncludeTransformer类applyIncludes方法,在此方法中判断有include标签,则根据refid从sqlFragments集合中拿取对应的节点,最后将sql节点替换include标签,完成include标签功能。其关键源码如下:
public class XMLIncludeTransformer {
public void applyIncludes(Node source) {
Properties variablesContext = new Properties();
Properties configurationVariables = configuration.getVariables();
// 将configuration中variables对象的值添加到variablesContext对象中
if (configurationVariables != null) {
variablesContext.putAll(configurationVariables);
}
// 获得前面的变量后执行真正的操作方法
applyIncludes(source, variablesContext, false);
}
private void applyIncludes(Node source,
final Properties variablesContext, boolean included) {
// 这个方法实际上是一个递归方法,因此可以再include里面疯狂套娃都没事
// 确保当前节点是include
if (source.getNodeName().equals("include")) {
// 从方法名字也可以看出来这个方法的大致作用,从sqlFragments集合对象中
// 根据refid指向的<sql/>标签内容获得节点信息
Node toInclude = findSqlFragment(getStringAttribute(source, "refid"),
variablesContext);
// 默认情况下variablesContext是空的,因此暂时不用看,有兴趣可以看下
Properties toIncludeContext = getVariablesContext(source,
variablesContext);
// 再递归toInclude对象标签
applyIncludes(toInclude, toIncludeContext, true);
if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
toInclude = source.getOwnerDocument().importNode(toInclude, true);
}
// 前面递归已完成,现在到了最深层(即可处理的地方)
source.getParentNode().replaceChild(toInclude, source);
// 将toInclude节点插入到第一个节点前面,也就是把toInclude变成第一个节点
while (toInclude.hasChildNodes()) {
toInclude.getParentNode().insertBefore(toInclude.getFirstChild(),
toInclude);
}
// 移除节点
toInclude.getParentNode().removeChild(toInclude);
} else if (source.getNodeType() == Node.ELEMENT_NODE) {
// 如果source节点类型,则继续递归调用
NodeList children = source.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
applyIncludes(children.item(i), variablesContext, included);
}
} else if (included && source.getNodeType() == Node.TEXT_NODE
&& !variablesContext.isEmpty()) {
// 如果节点是纯文本,则替换所有文本节点中的变量,替换标识符为${}
source.setNodeValue(PropertyParser.parse(source.getNodeValue(),
variablesContext));
}
}
}
流程如代码中的注释所述,include标签会一直进行递归调用,并把include这个递归链中${}可替换变量都使用variables对象中的值替换。
2.11 XMLScriptBuilder构造器创建SqlSource对象
在解析完include和selectKey之后,将会通过langDriver驱动器来创建sql语句SqlSource对象,先看一下调用进XMLScriptBuilder构造器的方法源码:
public class XMLLanguageDriver implements LanguageDriver {
@Override
public SqlSource createSqlSource(Configuration configuration,
XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration,
script, parameterType);
// 直接调用构造器解析节点
return builder.parseScriptNode();
}
}
具体的XMLScriptBuilder解析方法关键源码如下:
public class XMLScriptBuilder extends BaseBuilder {
public SqlSource parseScriptNode() {
// 解析动态XML标签节点并返回
List<SqlNode> contents = parseDynamicTags(context);
// 封装成混合SqlNode节点,后续分析SqlNode的时候可以再来分析这些
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
SqlSource sqlSource = null;
// parseDynamicTags方法中会设置isDynamic对象值,根据不同的值再为
// SqlSource创建不同的对象,并封装rootSqlNode
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode,
parameterType);
}
// 当获得具体的SqlSource类型,将会在进行数据库操作时解析,启动时不会
return sqlSource;
}
List<SqlNode> parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<SqlNode>();
// 获取Sql操作标签的所有子节点并遍历
NodeList children = node.getNode().getChildNodes();
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("");
TextSqlNode textSqlNode = new TextSqlNode(data);
// 如果节点是动态的,则直接添加到contents集合中并设置isDynamic
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
// 如果是静态的,则使用StaticTextSqlNode封装并添加
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE){
// 如果是节点类型
String nodeName = child.getNode().getNodeName();
// 获取节点标签名称,并根据标签名称使用不同的handler处理器处理
NodeHandler handler = nodeHandlers(nodeName);
// 如果获取到的为空,则代表无法识别的标签类型
if (handler == null) {
throw new BuilderException("Unknown element <"
+ nodeName + "> in SQL statement.");
}
// 调用处理器的处理节点方法,不同的处理便不做具体分析了
// 需要注意的便是contents会一直被传下去,因此这个对象中
// 将包含了所有的标签对象节点
handler.handleNode(child, contents);
// 这种类型一定是动态的
isDynamic = true;
}
}
return contents;
}
NodeHandler nodeHandlers(String nodeName) {
// 和if/else、switch一样的功能,借助HashMap实现条件判断
Map<String, NodeHandler> map = new HashMap<String, NodeHandler>();
map.put("trim", new TrimHandler());
map.put("where", new WhereHandler());
map.put("set", new SetHandler());
map.put("foreach", new ForEachHandler());
map.put("if", new IfHandler());
map.put("choose", new ChooseHandler());
map.put("when", new IfHandler());
map.put("otherwise", new OtherwiseHandler());
map.put("bind", new BindHandler());
return map.get(nodeName);
}
}
在此方法中,最核心的方法便是parseDynamicTags,此方法是解析mybatis动态sql的核心方法,再判断是否是字节点,如果是则从九种的动态标签根据名称拿取相应的处理器,并调用handleNode解析节点,将节点一次转换为SqlNode不同的子节点,随后返回SqlNode集合,并使用MixedSqlNode封装起来,根据是否是动态sql创建不同的SqlSource对象并返回。之后使用MappedStatement.Builder构造器构造MappedStatement对象。而bindMapperForNamespace方法则是为了将mapper.xml文件和mapper接口对应起来。
至此,mapper.xml文件中所涉及到的标签便已经全部处理完,并转换成了文章二所说的存储解析内容的对象,完成了执行Sql前的必要属性参数解析。