Mybatis源码分析(1)------配置文件的解析

注:文章大部分内容转载自 https://www.tianxiaobo.com/ 以及 《Mybatis技术内幕》

1.1 解析配置文件的入口

在只使用Mybatis访问数据库时我们可以这样构建SqlSessionFactory对象:

  使用MyBatis 提供的工具类 Resources 加载配置文件,得到一个输入流。然后再通过 SqlSessionFactoryBuilder 对象的build()方法构建 SqlSessionFactory 对象。

1 String resource = "mybatis-config.xml";
2 InputStream inputStream = Resources.getResourceAsStream(resource);
3 //将mybatis-config.xml作为一个输入流传入给build()方法
4 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

而在日常结合Spring使用Mybatis时配置文件中(applicationContext.xml)一般有如下配置信息:

1 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
2   <property name="dataSource" ref="dataSource"></property>
3   <property name="mapperLocations" value="classpath:com/emiyaa/mappers/*.xml"></property>
4 </bean>

我们要分析的build()方法的进入路径是:SqlSessionFactoryBean → SqlSessionBuilder → build()

 1 public SqlSessionFactory build(InputStream inputStream) {
 2     return this.build((InputStream) inputStream, (String) null, (Properties) null);
 3 }
 4 
 5 public SqlSessionFactory build(InputStream inputStream, String environment) {
 6     return this.build((InputStream) inputStream, environment, (Properties) null);
 7 }
 8 
 9 public SqlSessionFactory build(InputStream inputStream, Properties properties) {
10     return this.build((InputStream) inputStream, (String) null, properties);
11 }
12 
13 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
14     SqlSessionFactory var5;
15     try {
16         //创建配置文件解析器,将配置文件加载为XMLConfig
17         XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
18         //构建SqlSessionFactory,调用 parse 方法解析配置文件,生成 Configuration 对象
19         var5 = this.build(parser.parse());
20         throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
21     } finally {
22         ErrorContext.instance().reset();
23 
24         try {
25             inputStream.close();
26         } catch (IOException var13) {
27         }
28 
29     }
30 
31     return var5;
32 }
33 
34 public SqlSessionFactory build(Configuration config) {
35     //创建DefaultSqlSessionFactory
36     return new DefaultSqlSessionFactory(config);
37 }

 可大致了解到XMLConfigBuilder有解析配置文件的功能,下面分析parse()方法:SqlSessionFactoryBean → SqlSessionBuilder → XMLConfigBuilder → parse()

 1 public Configuration parse() {
 2     if (this.parsed) {
 3         throw new BuilderException("Each XMLConfigBuilder can only be used once.");
 4     } else {
 5         this.parsed = true;
 6         // 解析配置,xpath表达式 /configuration 代表Mybatis的<configuration/>标签
 7         this.parseConfiguration(this.parser.evalNode("/configuration"));
 8         return this.configuration;
 9     }
10 }

进入parseConfiguration()方法:SqlSessionFactoryBean → SqlSessionBuilder → XMLConfigBuilder → parse() → parseConfiguration() 

 1 private void parseConfiguration(XNode root) {
 2     try {
 3         Properties settings = this.settingsAsPropertiess(root.evalNode("settings"));
 4         this.propertiesElement(root.evalNode("properties"));
 5         this.loadCustomVfs(settings);
 6         this.typeAliasesElement(root.evalNode("typeAliases"));
 7         this.pluginElement(root.evalNode("plugins"));
 8         this.objectFactoryElement(root.evalNode("objectFactory"));
 9         this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
10         this.reflectionFactoryElement(root.evalNode("reflectionFactory"));
11         this.settingsElement(settings);
12         this.environmentsElement(root.evalNode("environments"));
13         this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
14         this.typeHandlerElement(root.evalNode("typeHandlers"));
15         this.mapperElement(root.evalNode("mappers"));
16     } catch (Exception var3) {
17         throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
18     }
19 }

发现方法中有很多配置的解析逻辑,下面开始分析其中重要的解析。

1.2 解析properties配置

1 <properties resource="prop.properties"><!-- 也可以配置url,但url和resource只能存在一个 -->
2     <property name="username" value="Emiyaa"/>
3     <property name="password" value="Emiyaa"/>
4     <property name="test" value="test"/>
5 </properties>

解析properties节点是由propertiesElement这个方法完成的,参照上面的配置,进入propertiesElement()方法:SqlSessionFactoryBean → SqlSessionBuilder → XMLConfigBuilder → propertiesElement()

 1 private void propertiesElement(XNode context) throws Exception {
 2     if (context != null) {
 3         // 解析 propertis 的子节点,并将这些节点内容转换为属性对象 Properties
 4         Properties defaults = context.getChildrenAsProperties();
 5         // 获取 propertis 节点中的 resource 和 url 属性值
 6         String resource = context.getStringAttribute("resource");
 7         String url = context.getStringAttribute("url");
 8 
 9         // 两者都不用空,则抛出异常
10         if (resource != null && url != null) {
11             throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
12         }
13         if (resource != null) {
14             // 从文件系统中加载并解析属性文件
15             defaults.putAll(Resources.getResourceAsProperties(resource));
16         } else if (url != null) {
17             // 通过 url 加载并解析属性文件
18             defaults.putAll(Resources.getUrlAsProperties(url));
19         }
20         Properties vars = configuration.getVariables();
21         if (vars != null) {
22             defaults.putAll(vars);
23         }
24         parser.setVariables(defaults);
25         // 将属性值设置到 configuration 中
26         configuration.setVariables(defaults);
27     }
28 }
29 
30 public Properties getChildrenAsProperties() {
31     Properties properties = new Properties();
32     // 获取并遍历子节点
33     for (XNode child : getChildren()) {
34         // 获取 property 节点的 name 和 value 属性
35         String name = child.getStringAttribute("name");
36         String value = child.getStringAttribute("value");
37         if (name != null && value != null) {
38             // 设置属性到属性对象中
39             properties.setProperty(name, value);
40         }
41     }
42     return properties;
43 }
44 
45 public List<XNode> getChildren() {
46     List<XNode> children = new ArrayList<XNode>();
47     // 获取子节点列表
48     NodeList nodeList = node.getChildNodes();
49     if (nodeList != null) {
50         for (int i = 0, n = nodeList.getLength(); i < n; i++) {
51             Node node = nodeList.item(i);
52             if (node.getNodeType() == Node.ELEMENT_NODE) {
53                 // 将节点对象封装到 XNode 中,并将 XNode 对象放入 children 列表中
54                 children.add(new XNode(xpathParser, node, variables));
55             }
56         }
57     }
58     return children;
59 }

节点解析过程可大致分为以下三步:

一:解析 properties 节点的子节点,并将解析结果设置到 Properties 对象中。

二:从文件系统或通过网络读取属性配置,这取决于 properties 节点的 resource 和 url 是否为空。

三:将解析出的属性对象设置到 XPathParser 和 Configuration 对象中。

※需要注意的是,propertiesElement 方法是先解析 properties 节点的子节点内容,后再从文件系统或者网络读取属性配置,并将所有的属性及属性值都放入到 defaults 属性对象中。这就会存在同名属性覆盖的问题,也就是从文件系统,或者网络上读取到的属性及属性值会覆盖掉 properties 子节点中同名的属性和及值。

1.3 解析settings配置

1.3.1 setting节点的解析过程

cacheEnabled
Globally enables or disables any caches configured in any mapper under this configuration.
描述:全局开启或关闭配置文件中所有 mapper 已经配置的缓存。
可选值:true|false
默认值:true
lazyLoadingEnabled
Globally enables or disables lazy loading. When enabled, all relations will be lazily loaded. This value can be superseded for an specific relation by using the fetchType attribute on it.
描述:全局开启或关闭延迟加载。开启时,所有关联对象都会延迟加载。特定关联关系中可以通过设置 fetchType 属性来取覆盖该项的开关状态。
可选值:true|false
默认值:false
aggressiveLazyLoading
When enabled, any method call will load all the lazy properties of the object. Otherwise, each property is loaded on demand (see also lazyLoadTriggerMethods).
描述:开启或关闭属性的延迟加载。开启时,任何方法调用都将加载该对象的所有延迟属性。关闭时,每个属性将按需加载(参考 lazyLoadTriggerMethods)。
可选值:true|false
默认值:false (true in ≤3.4.1)
multipleResultSetsEnabled
Allows or disallows multiple ResultSets to be returned from a single statement (compatible driver required).
描述:是否允许单个语句中返回多个结果集(需要兼容驱动)。
可选值:true|false
默认值:true
useColumnLabe
Uses the column label instead of the column name. Different drivers behave differently in this respect. Refer to the driver documentation, or test out both modes to determine how your driver behaves.
描述:使用列别名替代列名。不同驱动在这方面表现不同。具体可参考驱动文档,或者通过测试判断这两种模式下不同驱动的表现。
可选值:true|false
默认值:true
useGeneratedKeys
Allows JDBC support for generated keys. A compatible driver is required. This setting forces generated keys to be used if set to true, as some drivers deny compatibility but still work (e.g. Derby).
描述:是否允许 JDBC 支持主键自动生成。需要兼容驱动。若设置为 true,则强制使用主键自动生成,尽管一些驱动不兼容也可以正常工作(如 Derby)。
可选项:true|false
默认值:false
autoMappingBehavior
Specifies if and how MyBatis should automatically map columns to fields/properties.
NONE disables auto-mapping.
PARTIAL will only auto-map results with no nested result mappings defined inside.
FULL will auto-map result mappings of any complexity (containing nested or otherwise).
描述:指定 Mybatis 是否自动映射列到字段或属性。
NONE:不允许自动映射。
PARTIAL:只自动映射没有定义嵌套结果集映射的结果集。
FULL:自动映射任意复杂的结果集(无论是否嵌套)。
可选值:NONE, PARTIAL, FULL
默认值:PARTIAL
autoMappingUnknownColumnBehavior
Specify the behavior when detects an unknown column (or unknown property type) of automatic mapping target.
NONE: Do nothing.
WARNING: Output warning log (The log level of 'org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' must be set to WARN).
FAILING: Fail mapping (Throw SqlSessionException)。
描述:指定发现自动映射目标未知列(或未知属性类型)的行为。
NONE:什么也不做。
WARNING:输出警告日志( 'org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志级别要设置为 WARN)。
FAILING:映射失败(抛出 SqlSessionException 异常)。
可选值:NONE, WARNING, FAILING
默认值:NONE
defaultExecutorType
Configures the default executor.
SIMPLE executor does nothing special.
REUSE executor reuses prepared statements.
BATCH executor reuses statements and batches updates.
描述:配置默认执行器。
SIMPLE:普通的执行器。
REUSE:重复执行预处理语句(prepared statements)。
BATCH:重复执行语句并执行批量更新。
可选值:SIMPLE REUSE BATCH
默认值:SIMPLE
defaultStatementTimeout
Sets the number of seconds the driver will wait for a response from the database.
描述:设置驱动等待数据库响应的超时秒数。
可选值:Any positive integer //任意正整数
默认值:Not Set (null)
defaultFetchSize
Sets the driver a hint as to control fetching size for return results. This parameter value can be override by a query setting.
描述:为驱动的结果集获取数量设置一个提示值。这个参数值可以被查询设置覆盖。
可选值:Any positive integer //任意正整数
默认值:Not Set (null)
safeRowBoundsEnabled
Allows using RowBounds on nested statements. If allow, set the false.
描述:允许在嵌套语句中使用 RowBound。如果允许,设置为 false。
可选值:true|false
默认值:false
safeResultHandlerEnabled
Allows using ResultHandler on nested statements. If allow, set the false.
描述:允许在嵌套语句中使用 ResultHandler。如果允许,设置为 false。
可选值:true|false
默认值:true
mapUnderscoreToCamelCase
Enables automatic mapping from classic database column names A_COLUMN to camel case classic Java property names aColumn.
描述:开启驼峰规则命名的自动映射,即从经典数据库列名命名 A_COLUMN 到经典 Java 属性命名 aColumn 的自动映射。
可选值:true|false
默认值:False
localCacheScope
MyBatis uses local cache to prevent circular references and speed up repeated nested queries. By default (SESSION) all queries executed during a session are cached. If localCacheScope=STATEMENT local session will be used just for statement execution, no data will be shared between two different calls to the same SqlSession.
描述:Mybatis 使用本地缓存机制(local cache)来防止循环引用和加速重复嵌套查询。
SESSION:默认设置,一个 session 期间的所有查询执行都将被缓存。
STATEMENT:本地 session 仅在语句执行时使用,且对同一个 session 的不同调用不会共享数据。
可选项:SESSION|STATEMENT
默认值:SESSION
jdbcTypeForNull
Specifies the JDBC type for null values when no specific JDBC type was provided for the parameter. Some drivers require specifying the column JDBC type but others work with generic values like NULL, VARCHAR or OTHER.
描述:未对参数指定 JDBC 类型时,当参数为空值时指定 JDBC 类型。某些驱动要求指定列的 JDBC 类型,某些驱动用一般类型,比如 NULL、VARCHAR 或 OTHER 即可。
可选值:JdbcType enumeration. Most common are: NULL, VARCHAR and OTHER
默认值:OTHER
lazyLoadTriggerMethods
Specifies which Object's methods trigger a lazy load
描述:指定对象的哪些方法触发一次延迟加载。
可选值:A method name list separated by commas //逗号分隔的方法名称列表
默认值:equals,clone,hashCode,toString
defaultScriptingLanguage
Specifies the language used by default for dynamic SQL generation.
描述:指定动态 SQL 生成的默认语言。
可选值:A type alias or fully qualified class name.
类型别名或完全限定类名
默认值:org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
defaultEnumTypeHandler
Specifies the TypeHandler used by default for Enum. (Since: 3.4.5)
描述:指定 Enum 默认使用的 TypeHandler(从3.4.5开始)。
可选值:A type alias or fully qualified class name. //类型别名或完全限定类名
默认值:org.apache.ibatis.type.EnumTypeHandler
callSettersOnNulls
Specifies if setters or map's put method will be called when a retrieved value is null. It is useful when you rely on Map.keySet() or null value initialization. Note primitives such as (int,boolean,etc.) will not be set to null.
描述:指定当结果集中值为空时是否调用映射对象的 setter 方法或 map 的 put 方法。如果你依赖于 Map.keySet() 或者在 null 值初始化时,该设置有用。注意,基本类型比如 int 、boolean 等不能被设置为 null。
可选值:true | false
默认值:false
returnInstanceForEmptyRow
MyBatis, by default, returns null when all the columns of a returned row are NULL. When this setting is enabled, MyBatis returns an empty instance instead. Note that it is also applied to nested results (i.e. collectioin and association). Since: 3.4.2
描述:当返回行的所有列都是 null 时,Mybatis 默认返回 null 。当该设置被开启时,Mybatis 会返回一个空实例。注意这将同样适用于嵌套结果集(如 collection 和 association)。从 3.4.2 开始。
可选值:true | false
默认值:false
logPrefix
Specifies the prefix string that MyBatis will add to the logger names.
描述:指定 Mybatis 添加到日志名称的前缀。
可选值:Any String
默认值:Not set
logImpl
Specifies which logging implementation MyBatis should use. If this setting is not present logging implementation will be autodiscovered.
描述:指定 Mybatis 使用的日志工具。如果此项未设置,将会自动查找日志工具。
可选值:SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
默认值:Not set
proxyFactory
Specifies the proxy tool that MyBatis will use for creating lazy loading capable objects.
描述:指定 Mybatis 创建具有懒加载能力的对象的代理工具。
可选值:CGLIB | JAVASSIST
默认值:JAVASSIST (MyBatis 3.3 or above)
vfsImpl
Specifies VFS implementations
描述:指定 VFS 的具体实现。
可选值:Fully qualified class names of custom VFS implementation separated by commas. //逗号分隔的自定义 VFS 具体实现的完全限定类名。
默认值:Not set
useActualParamName
Allow referencing statement parameters by their actual names declared in the method signature. To use this feature, your project must be compiled in Java 8 with -parameters option. (Since: 3.4.1)
描述:允许将方法签名中定义的真实名称作为语句参数名称。为了使用这一特性,你的项目必须采用 java 8 编译,且必须加上 -parameters 选项。(从 3.4.1 开始)
可选值:true | false
默认值:true
configurationFactory
Specifies the class that provides an instance of Configuration. The returned Configuration instance is used to load lazy properties of deserialized objects. This class must have a method with a signature static Configuration getConfiguration(). (Since: 3.2.3)
描述:指定提供 Configuration 实例的类。返回的 Configuration 实例用来加载被反序列化对象的懒加载属性。这个类必须有一个 static Configuration getConfiguration() 签名方法。(从 3.2.3 开始)
可选值:A type alias or fully qualified class name. //类型别名或完全限定类名
默认值:Not set
settings
 1 <settings>
 2   <setting name="cacheEnabled" value="true"/>
 3   <setting name="lazyLoadingEnabled" value="true"/>
 4   <setting name="multipleResultSetsEnabled" value="true"/>
 5   <setting name="useColumnLabel" value="true"/>
 6   <setting name="useGeneratedKeys" value="false"/>
 7   <setting name="autoMappingBehavior" value="PARTIAL"/>
 8   <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
 9   <setting name="defaultExecutorType" value="SIMPLE"/>
10   <setting name="defaultStatementTimeout" value="25"/>
11   <setting name="defaultFetchSize" value="100"/>
12   <setting name="safeRowBoundsEnabled" value="false"/>
13   <setting name="mapUnderscoreToCamelCase" value="false"/>
14   <setting name="localCacheScope" value="SESSION"/>
15   <setting name="jdbcTypeForNull" value="OTHER"/>
16   <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
17 </settings>

关于Mybatis的settings配置我找了一个较全版本的配置(https://www.jianshu.com/p/3883ea8ad3a6),下面开始分析settingsAsProperties()方法:SqlSessionFactoryBean → SqlSessionBuilder → XMLConfigBuilder →settingsAsProperties()

 1 private Properties settingsAsProperties(XNode context) {
 2     if (context == null) {
 3         return new Properties();
 4     }
 5     // 获取 settings 子节点中的内容,getChildrenAsProperties 方法前面已分析过,这里不再赘述
 6     Properties props = context.getChildrenAsProperties();
 7 
 8     // 创建 Configuration 类的“元信息”对象
 9     MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
10     for (Object key : props.keySet()) {
11         // 检测 Configuration 中是否存在相关属性,不存在则抛出异常
12         if (!metaConfig.hasSetter(String.valueOf(key))) {
13             throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
14         }
15     }
16     return props;
17 }

以上代码的逻辑大致为:

一:解析 settings 子节点的内容,并将解析结果转成 Properties 对象

二:为 Configuration 创建元信息对象

三:通过 MetaClass 检测 Configuration 中是否存在某个属性的 setter 方法,不存在则抛异常

四:若通过 MetaClass 的检测,则返回 Properties 对象,方法逻辑结束

之后进行第二步与第三步的分析,以及MetaClass类的分析:

1.3.2 元信息对象创建过程

1.3.2.1 defaultReflectorFactory分析

待更新。。。。。。

猜你喜欢

转载自www.cnblogs.com/Emiyaa/p/11313258.html
今日推荐