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     }
View Code

  这段代码我们不用深究,只需要知道这是将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         //从根节点 <configuration></configuration>处开始解析
 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 <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
 3 <configuration>
 4 
 5     <!-- 加载数据库属性文件 -->
 6     <properties resource="jdbc.properties">
 7     </properties>
 8     <!-- 可以配置多个运行环境,但是每个 SqlSessionFactory 实例只能选择一个运行环境 一、development:开发模式 二、work:工作模式 -->
 9     <environments default="development">
10         <!--id属性必须和上面的default一样 -->
11         <environment id="development">
12             <transactionManager type="JDBC" />
13             <!--dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象源 -->
14             <dataSource type="POOLED">
15                 <property name="driver" value="${jdbc.driver}" />
16                 <property name="url" value="${jdbc.url}" />
17                 <property name="username" value="${jdbc.username}" />
18                 <property name="password" value="${jdbc.password}" />
19             </dataSource>
20         </environment>
21     </environments>
22 
23     <mappers>
24         <mapper resource="com/ys/mapper/userMapper.xml"/>
25     </mappers>
26 </configuration>

2、初始化基础配置

  上面一步我们已经读取了xml文件的所有配置,接下来初始化配置文件中的信息,也就是读取xml文件每个节点的配置信息:

①、properties 全局参数

  配置举例:

1     <!-- 加载数据库属性文件 -->
2     <properties resource="jdbc.properties">
3         <property name="username" value="root"/>
4         <property name="password" value="root"/>
5     </properties>

  具体读取详情:

 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     <!-- 加载数据库属性文件 -->
2     <properties resource="jdbc.properties">
3         <property name="username" value="rootfasfsdf"/>
4         <property name="password" value="root"/>
5     </properties>

  只要jdbc.properties 文件中的username是正确的,<property name="username" value="rootfasfsdf"/>标签中value无论是什么,都不影响。

②、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 }

  总结:在<settings>标签中配置的节点信息必须在 Configuration 类中存在相应的属性,否则会抛出异常。然后根据标签中配置的值初始化 Configuration 类中的属性值。

③、typeAliases 别名

  配置举例:

1     <typeAliases>
2         <typeAlias type="com.ys.po.User" alias="user"/>
3     </typeAliases>

  类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。它还有一个 <package name="com.ys.po" /> 标签,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。

  具体用法可以参考官网

  接下来我们查看源码:

 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... 语句可以看到,<typeAliases> 标签可以有 一个标签<package>或者含有 type属性和 alias 属性的标签(其实就是<typeAlias type="" alias=""/>,在解析文档时做了约束)。

  注意:这两个标签可以共存。但是<typeAliases />标签一定要在 <package />标签的前面。因为一个类可以有多个别名,所以这时候两个标签设置的名称都有效。

  首先看第 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<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
 7         //根据包名 packageName 获取包下所有的 .class 文件(反射)
 8         resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
 9         Set<Class<? 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<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();
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 中。

  接下来看 <typeAlias type="" alias=""/> 标签,它有两个属性,type和alias。其中如果 alias 为空,那么和解析 package 标签一样,缺省采用类名的小写。

  总结:

  ①、不管是通过 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   }
View Code

  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   }
View Code

  对于这些别名,我们可以在配置文件中直接使用,而不用额外配置了。

④、typeHandlers 类型处理器

  我们知道想Java数据类型和数据库数据类型是有区别的,而我们想通过Java代码来操作数据库或从数据库中取值的时候,必须要进行类型的转换。而  typeHandlers 便是来完成这一工作的。

  想要自定义一个类型处理器,必须要实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个类 org.apache.ibatis.type.BaseTypeHandler。

  配置举例:

1 <typeHandlers>
2   <package name="org.mybatis.example"/>
3 </typeHandlers>

  或者:

1 <typeHandlers>
2   <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
3 </typeHandlers>

  mybatis也为我们提供了许多内置的类型处理器,具体可以参考官网

  <environment>标签中的还有诸如 ObjectFactory 对象、plugin 插件、environment 环境、DatabaserIdProvider 数据库标识等配置,其中 plugin 插件特别重要,这属于 mybatis进阶内容,后面我们会详细讲解。

⑤、Mapper 映射器

  在 mybatis-configuration.xml 配置文件中有两个标签,一个是 <environments/> 用来配置数据源等信息。另一个就是 <mappers />标签了,用来进行 sql 文件映射。也就是说我们需要告诉 MyBatis 到哪里去找到这些语句。 Java 在自动查找这方面没有提供一个很好的方法,所以最佳的方式是告诉 MyBatis 到哪里去找映射文件。可以使用相对于类路径的资源引用, 或完全限定资源定位符(包括 file:/// 的 URL),或类名和包名等。例如:

 1 <!-- 使用相对于类路径的资源引用 -->
 2 <mappers>
 3   <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
 4   <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
 5   <mapper resource="org/mybatis/builder/PostMapper.xml"/>
 6 </mappers>
 7 <!-- 使用完全限定资源定位符(URL) -->
 8 <mappers>
 9   <mapper url="file:///var/mappers/AuthorMapper.xml"/>
10   <mapper url="file:///var/mappers/BlogMapper.xml"/>
11   <mapper url="file:///var/mappers/PostMapper.xml"/>
12 </mappers>
13 <!-- 使用映射器接口实现类的完全限定类名 -->
14 <mappers>
15   <mapper class="org.mybatis.builder.AuthorMapper"/>
16   <mapper class="org.mybatis.builder.BlogMapper"/>
17   <mapper class="org.mybatis.builder.PostMapper"/>
18 </mappers>
19 <!-- 将包内的映射器接口实现全部注册为映射器 -->
20 <mappers>
21   <package name="org.mybatis.builder"/>
22 </mappers>
View Code

  接下来我们追溯源码:

 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{} 可以看到在<mappers>标签中是可以同时存在子标签<package>和子标签<mapper>的,但是根据 dtd 约束:

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.");
}

  下面我们来重点分析<mapper /> 子标签:也就是上面代码的第 8 行到第 26 行。

  首先第 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,<cache-ref>的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 标签的解析是最重要也是最复杂的,后面会详细讲解。

  还有比较重要的对于如下标签的解析:

    <!-- 可以配置多个运行环境,但是每个 SqlSessionFactory 实例只能选择一个运行环境 一、development:开发模式 二、work:工作模式 -->
    <environments default="development">
        <!--id属性必须和上面的default一样 -->
        <environment id="development">
            <transactionManager type="JDBC" />
            <!--dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象源 -->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}" />
                <property name="url" value="${jdbc.url}" />
                <property name="username" value="${jdbc.username}" />
                <property name="password" value="${jdbc.password}" />
            </dataSource>
        </environment>
    </environments>

  这是对于数据源以及事务的配置,这也是一个 ORM 框架最重要的一部分,后面也会详细讲解。

posted @ 2018-05-07 20:31  YSOcean  阅读(6770)  评论(2编辑  收藏  举报