mybatis源码解读(二)——构建Configuration对象
Configuration 对象保存了所有mybatis的配置信息,主要包括:
①、 mybatis-configuration.xml 基础配置文件
②、 mapper.xml 映射器配置文件
1、读取配置文件
前面例子有这么一段代码:
1 private static SqlSessionFactory sqlSessionFactory; 2 3 static{ 4 InputStream inputStream = MybatisTest.class.getClassLoader().getResourceAsStream("mybatis-configuration.xml"); 5 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 6 }
第 4 行代码是获取基础配置文件mybatis-configuration.xml 的字节流。接着我们将该字节流对象作为 bulid() 方法的参数传入进去。bulid 方法源码如下:这是一个多态方法
1 public SqlSessionFactory build(InputStream inputStream) { 2 return build(inputStream, null, null); 3 } 4 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { 5 try { 6 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); 7 return build(parser.parse()); 8 } catch (Exception e) { 9 throw ExceptionFactory.wrapException("Error building SqlSession.", e); 10 } finally { 11 ErrorContext.instance().reset(); 12 try { 13 inputStream.close(); 14 } catch (IOException e) { 15 // Intentionally ignore. Prefer previous error. 16 } 17 } 18 } 19 public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) { 20 this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props); 21 } 22 public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) { 23 commonConstructor(validation, variables, entityResolver); 24 this.document = createDocument(new InputSource(inputStream)); 25 } 26 27 private Document createDocument(InputSource inputSource) { 28 // important: this must only be called AFTER common constructor 29 try { 30 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 31 factory.setValidating(validation); 32 33 factory.setNamespaceAware(false); 34 factory.setIgnoringComments(true); 35 factory.setIgnoringElementContentWhitespace(false); 36 factory.setCoalescing(false); 37 factory.setExpandEntityReferences(true); 38 39 DocumentBuilder builder = factory.newDocumentBuilder(); 40 builder.setEntityResolver(entityResolver); 41 builder.setErrorHandler(new ErrorHandler() { 42 @Override 43 public void error(SAXParseException exception) throws SAXException { 44 throw exception; 45 } 46 47 @Override 48 public void fatalError(SAXParseException exception) throws SAXException { 49 throw exception; 50 } 51 52 @Override 53 public void warning(SAXParseException exception) throws SAXException { 54 } 55 }); 56 return builder.parse(inputSource); 57 } catch (Exception e) { 58 throw new BuilderException("Error creating document instance. Cause: " + e, e); 59 } 60 }
这段代码我们不用深究,只需要知道这是将mybatis-configuration.xml文件解析成org.w3c.dom.Document对象,并将 Document 对象存储在 XPathParser 对象中便于后面解析。(XPath 语法解析xml具有很大的优势)
下一步就是将 Document 对象转换成 Configuration 对象:
首先回到 SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) 方法:
1 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { 2 try { 3 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); 4 return build(parser.parse()); 5 } catch (Exception e) { 6 throw ExceptionFactory.wrapException("Error building SqlSession.", e); 7 } finally { 8 ErrorContext.instance().reset(); 9 try { 10 inputStream.close(); 11 } catch (IOException e) { 12 // Intentionally ignore. Prefer previous error. 13 } 14 } 15 }
第3行代码完成了xml文件到 Document 对象的转换,接下来我们看 build(parser.parse()) 方法:
1 public Configuration parse() { 2 if (parsed) { 3 throw new BuilderException("Each XMLConfigBuilder can only be used once."); 4 } 5 parsed = true; 6 //从根节点处开始解析 7 parseConfiguration(parser.evalNode("/configuration")); 8 return configuration; 9 } 10 11 private void parseConfiguration(XNode root) { 12 try { 13 //分别解析相应的节点标签 14 propertiesElement(root.evalNode("properties")); 15 Properties settings = settingsAsProperties(root.evalNode("settings")); 16 loadCustomVfs(settings); 17 typeAliasesElement(root.evalNode("typeAliases")); 18 pluginElement(root.evalNode("plugins")); 19 objectFactoryElement(root.evalNode("objectFactory")); 20 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); 21 reflectorFactoryElement(root.evalNode("reflectorFactory")); 22 settingsElement(settings); 23 environmentsElement(root.evalNode("environments")); 24 databaseIdProviderElement(root.evalNode("databaseIdProvider")); 25 typeHandlerElement(root.evalNode("typeHandlers")); 26 //解析引入的mapper.xml文件 27 mapperElement(root.evalNode("mappers")); 28 } catch (Exception e) { 29 throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); 30 } 31 }
我们可以看看前一篇中对于 mybatis-configuration.xml 文件的配置信息:
1 <?xml version="1.0" encoding="UTF-8"?> 2 34 5 6 7 8 9default="development"> 10 11 22 2312 2113 14 15 2016 17 18 19 24 2625
2、初始化基础配置
上面一步我们已经读取了xml文件的所有配置,接下来初始化配置文件中的信息,也就是读取xml文件每个节点的配置信息:
①、properties 全局参数
配置举例:
1 23 4 5
具体读取详情:
1 private void propertiesElement(XNode context) throws Exception { 2 if (context != null) { 3 //先加载property子节点下的属性 4 Properties defaults = context.getChildrenAsProperties(); 5 //读取properties 节点中的属性resource和url 6 String resource = context.getStringAttribute("resource"); 7 String url = context.getStringAttribute("url"); 8 //url和resource不能同时存在,否则抛出异常 9 if (resource != null && url != null) { 10 throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other."); 11 } 12 if (resource != null) { 13 //读取引入文件的信息,resource引入的文件属性会覆盖子节点的配置 14 defaults.putAll(Resources.getResourceAsProperties(resource)); 15 } else if (url != null) { 16 //url引入的文件信息也会覆盖子节点的信息 17 defaults.putAll(Resources.getUrlAsProperties(url)); 18 } 19 //读取Configuration对象中variables属性信息,如果有,则将其添加到properties对象中 20 Properties vars = configuration.getVariables(); 21 if (vars != null) { 22 defaults.putAll(vars); 23 } 24 //将Properties类设置到XPathParser和Configuration的variables属性中 25 parser.setVariables(defaults); 26 configuration.setVariables(defaults); 27 } 28 } 29 public synchronized void putAll(Map<? extends K, ? extends V> t) { 30 for (Map.Entry<? extends K, ? extends V> e : t.entrySet()) 31 put(e.getKey(), e.getValue()); 32 }
具体解释在代码中都已经给出注释了。需要注意如下三点:
一、可以设置url或resource属性从外部文件中加载一个properties文件
二、可以通过property子节点进行配置,如果子节点属性的key与外部文件的key重复的话,子节点的将被覆
三、通过编程方式定义的属性最后加载,优先级最高(上面代码第20行到23行):
比如:
1 23 4 5
只要jdbc.properties 文件中的username是正确的,
②、setting 设置
配置举例:
1 <settings> 2 3 <setting name="cacheEnabled" value="true" /> 4 5 <setting name="lazyLoadingEnabled" value="true" /> 6 settings>
详细的配置项信息可以参考官网。
接着我们追溯源码:
1 private void settingsElement(XNode context) throws Exception { 2 if (context != null) { 3 //读取所有子节点信息 4 Properties props = context.getChildrenAsProperties(); 5 //检查所有setting配置文件的属性是否在 Configuration.class中存在set方法 6 //如果不存在,则抛出异常 7 MetaClass metaConfig = MetaClass.forClass(Configuration.class); 8 for (Object key : props.keySet()) { 9 if (!metaConfig.hasSetter(String.valueOf(key))) { 10 throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive)."); 11 } 12 } 13 //给configuration类中的属性初始化 14 configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL"))); 15 configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true)); 16 configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory"))); 17 configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false)); 18 configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true)); 19 configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true)); 20 configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true)); 21 configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false)); 22 configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE"))); 23 configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null)); 24 configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false)); 25 configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false)); 26 configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION"))); 27 configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER"))); 28 configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString")); 29 configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true)); 30 configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage"))); 31 configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false)); 32 configuration.setLogPrefix(props.getProperty("logPrefix")); 33 configuration.setLogImpl(resolveClass(props.getProperty("logImpl"))); 34 configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory"))); 35 } 36 }
总结:在
③、typeAliases 别名
配置举例:
12 3
类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。它还有一个
具体用法可以参考官网。
接下来我们查看源码:
1 private void typeAliasesElement(XNode parent) { 2 if (parent != null) { 3 for (XNode child : parent.getChildren()) { 4 if ("package".equals(child.getName())) { 5 String typeAliasPackage = child.getStringAttribute("name"); 6 configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage); 7 } else { 8 String alias = child.getStringAttribute("alias"); 9 String type = child.getStringAttribute("type"); 10 try { 11 Class<?> clazz = Resources.classForName(type); 12 if (alias == null) { 13 typeAliasRegistry.registerAlias(clazz); 14 } else { 15 typeAliasRegistry.registerAlias(alias, clazz); 16 } 17 } catch (ClassNotFoundException e) { 18 throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e); 19 } 20 } 21 } 22 } 23 }
从第 4 行和第 7 行的 if...else... 语句可以看到,
注意:这两个标签可以共存。但是
首先看第 6 行代码,解析 package 标签:
1 public void registerAliases(String packageName) { 2 registerAliases(packageName, Object.class); 3 } 4 5 public void registerAliases(String packageName, Class<?> superType) { 6 ResolverUtil> resolverUtil = new ResolverUtil >(); 7 //根据包名 packageName 获取包下所有的 .class 文件(反射) 8 resolverUtil.find(new ResolverUtil.IsA(superType), packageName); 9 Set extends Class<?>>> typeSet = resolverUtil.getClasses(); 10 //遍历所有的class,不能是匿名类、接口以及成员类 11 for (Class<?> type : typeSet) { 12 if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) { 13 registerAlias(type); 14 } 15 } 16 } 17 18 public void registerAlias(Class<?> type) { 19 //去掉包名,得到类名 20 String alias = type.getSimpleName(); 21 //如果配置了注解,以注解上面的名称为准 22 Alias aliasAnnotation = type.getAnnotation(Alias.class); 23 if (aliasAnnotation != null) { 24 alias = aliasAnnotation.value(); 25 } 26 registerAlias(alias, type); 27 } 28 29 private final Map > TYPE_ALIASES = new HashMap >(); 30 //将别名作为key,别名代表的类作为value存入HashMap 中 31 public void registerAlias(String alias, Class<?> value) { 32 if (alias == null) 33 throw new TypeException("The parameter alias cannot be null"); 34 //别名转成小写 35 String key = alias.toLowerCase(Locale.ENGLISH); 36 if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) { 37 throw new TypeException("The alias '" + alias + "' is already mapped to the value '" 38 + TYPE_ALIASES.get(key).getName() + "'."); 39 } 40 TYPE_ALIASES.put(key, value); 41 }
这段代码其实作用就是将配置的别名作为key(全部转成小写,如果有配置注解,以注解为准),别名代表的类作为 value 存入 HashMap 中。
接下来看
总结:
①、不管是通过 package 标签配置,还是通过 typeAlias 标签配置的别名,在mapper.xml文件中使用的时候,转换成小写是相等的,那么就可以使用。
②、如果不手动设置别名,默认是类名的小写。
③、如果配置了注解别名,注解别名会覆盖上面的所有配置。
默认别名
除了上面手动配置的别名以外,mybatis 还为我们默认配置了一系列的别名。
1、在 TypeAliasRegistry.class 类中
1 public TypeAliasRegistry() { 2 registerAlias("string", String.class); 3 4 registerAlias("byte", Byte.class); 5 registerAlias("long", Long.class); 6 registerAlias("short", Short.class); 7 registerAlias("int", Integer.class); 8 registerAlias("integer", Integer.class); 9 registerAlias("double", Double.class); 10 registerAlias("float", Float.class); 11 registerAlias("boolean", Boolean.class); 12 13 registerAlias("byte[]", Byte[].class); 14 registerAlias("long[]", Long[].class); 15 registerAlias("short[]", Short[].class); 16 registerAlias("int[]", Integer[].class); 17 registerAlias("integer[]", Integer[].class); 18 registerAlias("double[]", Double[].class); 19 registerAlias("float[]", Float[].class); 20 registerAlias("boolean[]", Boolean[].class); 21 22 registerAlias("_byte", byte.class); 23 registerAlias("_long", long.class); 24 registerAlias("_short", short.class); 25 registerAlias("_int", int.class); 26 registerAlias("_integer", int.class); 27 registerAlias("_double", double.class); 28 registerAlias("_float", float.class); 29 registerAlias("_boolean", boolean.class); 30 31 registerAlias("_byte[]", byte[].class); 32 registerAlias("_long[]", long[].class); 33 registerAlias("_short[]", short[].class); 34 registerAlias("_int[]", int[].class); 35 registerAlias("_integer[]", int[].class); 36 registerAlias("_double[]", double[].class); 37 registerAlias("_float[]", float[].class); 38 registerAlias("_boolean[]", boolean[].class); 39 40 registerAlias("date", Date.class); 41 registerAlias("decimal", BigDecimal.class); 42 registerAlias("bigdecimal", BigDecimal.class); 43 registerAlias("biginteger", BigInteger.class); 44 registerAlias("object", Object.class); 45 46 registerAlias("date[]", Date[].class); 47 registerAlias("decimal[]", BigDecimal[].class); 48 registerAlias("bigdecimal[]", BigDecimal[].class); 49 registerAlias("biginteger[]", BigInteger[].class); 50 registerAlias("object[]", Object[].class); 51 52 registerAlias("map", Map.class); 53 registerAlias("hashmap", HashMap.class); 54 registerAlias("list", List.class); 55 registerAlias("arraylist", ArrayList.class); 56 registerAlias("collection", Collection.class); 57 registerAlias("iterator", Iterator.class); 58 59 registerAlias("ResultSet", ResultSet.class); 60 }
2、在 Configuration.class 类中
1 public Configuration() { 2 typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class); 3 typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class); 4 5 typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class); 6 typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class); 7 typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class); 8 9 typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class); 10 typeAliasRegistry.registerAlias("FIFO", FifoCache.class); 11 typeAliasRegistry.registerAlias("LRU", LruCache.class); 12 typeAliasRegistry.registerAlias("SOFT", SoftCache.class); 13 typeAliasRegistry.registerAlias("WEAK", WeakCache.class); 14 15 typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class); 16 17 typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class); 18 typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class); 19 20 typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class); 21 typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class); 22 typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class); 23 typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class); 24 typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class); 25 typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class); 26 typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class); 27 28 typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class); 29 typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class); 30 31 languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class); 32 languageRegistry.register(RawLanguageDriver.class); 33 }
对于这些别名,我们可以在配置文件中直接使用,而不用额外配置了。
④、typeHandlers 类型处理器
我们知道想Java数据类型和数据库数据类型是有区别的,而我们想通过Java代码来操作数据库或从数据库中取值的时候,必须要进行类型的转换。而 typeHandlers 便是来完成这一工作的。
想要自定义一个类型处理器,必须要实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个类 org.apache.ibatis.type.BaseTypeHandler。
配置举例:
12 <package name="org.mybatis.example"/> 3
或者:
12 3
mybatis也为我们提供了许多内置的类型处理器,具体可以参考官网。
⑤、Mapper 映射器
在 mybatis-configuration.xml 配置文件中有两个标签,一个是
1 23 7 84 5 6 9 13 1410 11 12 15 19 20class="org.mybatis.builder.AuthorMapper"/> 16 class="org.mybatis.builder.BlogMapper"/> 17 class="org.mybatis.builder.PostMapper"/> 18 21 <package name="org.mybatis.builder"/> 22
接下来我们追溯源码:
1 private void mapperElement(XNode parent) throws Exception { 2 if (parent != null) { 3 for (XNode child : parent.getChildren()) { 4 if ("package".equals(child.getName())) { 5 String mapperPackage = child.getStringAttribute("name"); 6 configuration.addMappers(mapperPackage); 7 } else { 8 String resource = child.getStringAttribute("resource"); 9 String url = child.getStringAttribute("url"); 10 String mapperClass = child.getStringAttribute("class"); 11 if (resource != null && url == null && mapperClass == null) { 12 ErrorContext.instance().resource(resource); 13 InputStream inputStream = Resources.getResourceAsStream(resource); 14 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); 15 mapperParser.parse(); 16 } else if (resource == null && url != null && mapperClass == null) { 17 ErrorContext.instance().resource(url); 18 InputStream inputStream = Resources.getUrlAsStream(url); 19 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); 20 mapperParser.parse(); 21 } else if (resource == null && url == null && mapperClass != null) { 22 Class<?> mapperInterface = Resources.classForName(mapperClass); 23 configuration.addMapper(mapperInterface); 24 } else { 25 throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); 26 } 27 } 28 } 29 } 30 }
从上面的 if ("package".equals(child.getName())) {} else{} 可以看到在
Element : mappers Content Model : (mapper*, package*)
mapper子标签必须在package标签前面。实际应用中,package标签使用的比较少,这里就不贴源码对package进行分析了(需要注意的是,如果两个子标签同时存在,前面解析完mapper标签后,存在相同的接口名,会抛出异常)
if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); }
下面我们来重点分析
首先第 8 行到第 10 行,读取子标签属性分别为 resource、url、class的值。然后看下面的if-else语句:
1 if(resource !=null&&url ==null&&mapperClass ==null){ 2 3 }else if(resource ==null&&url !=null&&mapperClass ==null){ 4 5 }else if(resource==null&&url==null&&mapperClass!=null){ 6 7 }else{ 8 throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); 9 }
第一个 if 表示 resource 值不为 null,且url 值和 mapperClass 值都为null。
第二个else if 表示 url 值不为 null,且 resource 值和 mapperClass 值都为null。
第三个 else if 表示 mapperClass 值不为 null,且 resource 值和 url 值都为null。
第四个 else 表示如果三个都为null或者都不为null,或者有两个不为null,都会抛出异常。
也就是说这三个标签有且仅有一个有值,其余两个都为null,才能正常执行。
1、首先看第一个 if 语句:
1 if (resource != null && url == null && mapperClass == null) { 2 ErrorContext.instance().resource(resource); 3 InputStream inputStream = Resources.getResourceAsStream(resource); 4 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); 5 mapperParser.parse(); 6 }
第3行是获取 resource只能目录的字节流。
第4行和前面讲解将 xml 文档解析为 Document 对象。
第5行追溯源码:
1 public void parse() { 2 if (!configuration.isResourceLoaded(resource)) { 3 configurationElement(parser.evalNode("/mapper")); 4 configuration.addLoadedResource(resource); 5 bindMapperForNamespace(); 6 } 7 8 parsePendingResultMaps(); 9 parsePendingCacheRefs(); 10 parsePendingStatements(); 11 }
第2行的代码判断了当前资源是否被加载过,如果没有被加载过则会执行第3行到第5行的代码。
第3行代码从节点 mapper 出开始解析:
1 private void configurationElement(XNode context) { 2 try { 3 //读取namespace属性值,如果为null或者为空,则抛出异常 4 String namespace = context.getStringAttribute("namespace"); 5 if (namespace == null || namespace.equals("")) { 6 throw new BuilderException("Mapper's namespace cannot be empty"); 7 } 8 builderAssistant.setCurrentNamespace(namespace); 9 //解析cache-ref标签 10 cacheRefElement(context.evalNode("cache-ref")); 11 //解析cache标签 12 cacheElement(context.evalNode("cache")); 13 //解析/mapper/parameterMap标签 14 parameterMapElement(context.evalNodes("/mapper/parameterMap")); 15 //解析/mapper/resultMap标签 16 resultMapElements(context.evalNodes("/mapper/resultMap")); 17 //解析/mapper/sql标签 18 sqlElement(context.evalNodes("/mapper/sql")); 19 //解析select|insert|update|delete标签 20 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); 21 } catch (Exception e) { 22 throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); 23 } 24 }
配置的属性作用如下:
1 cache – 给定命名空间的缓存配置。 2 cache-ref – 其他命名空间缓存配置的引用。 3 resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。 4 parameterMap – 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。 5 sql – 可被其他语句引用的可重用语句块。 6 insert – 映射插入语句 7 update – 映射更新语句 8 delete – 映射删除语句 9 select – 映射查询语句
对于第一个解析 cache-ref 标签:
1 private void cacheRefElement(XNode context) { 2 if (context != null) { 3 //将mapper标签的的namespace作为key,的namespace作为value存放Configuration对象的cacheRefMap中 4 configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace")); 5 CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace")); 6 try { 7 cacheRefResolver.resolveCacheRef(); 8 } catch (IncompleteElementException e) { 9 configuration.addIncompleteCacheRef(cacheRefResolver); 10 } 11 } 12 }
其余的几个标签,其中对于 resultMap 标签的解析,以及对于 select|insert|update|delete 标签的解析是最重要也是最复杂的,后面会详细讲解。
还有比较重要的对于如下标签的解析:
default="development">
这是对于数据源以及事务的配置,这也是一个 ORM 框架最重要的一部分,后面也会详细讲解。