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         //從根節點 <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   }

複製代碼

  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。

  配置舉例:

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>

複製代碼

  接下來我們追溯源碼:

複製代碼

 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 框架最重要的一部分,後面也會詳細講解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章