天天看點

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 方法源碼如下:這是一個多态方法

mybatis源碼解讀(二)——建構Configuration對象
mybatis源碼解讀(二)——建構Configuration對象
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 類中

mybatis源碼解讀(二)——建構Configuration對象
mybatis源碼解讀(二)——建構Configuration對象
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 類中

mybatis源碼解讀(二)——建構Configuration對象
mybatis源碼解讀(二)——建構Configuration對象
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),或類名和包名等。例如:

mybatis源碼解讀(二)——建構Configuration對象
mybatis源碼解讀(二)——建構Configuration對象
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 架構最重要的一部分,後面也會詳細講解。

作者:

YSOcean

出處:

http://www.cnblogs.com/ysocean/

本文版權歸作者所有,歡迎轉載,但未經作者同意不能轉載,否則保留追究法律責任的權利。