本文将会介绍MyBatis
配置文件解析部分的代码解读,从创建一个SqlSessionFactory
作为入口,引入MyBatis配置文件的说明。简要说明配置文件中常用标签的用法和说明,根据每个标签,详细介绍MyBatis
是如何解析这些标签的。
创建一个SqlSessionFactory的几种方式
每个基于 MyBatis
的应用都是以一个 SqlSessionFactory
的实例为核心的。SqlSessionFactory
的实例可以通过 SqlSessionFactoryBuilder
获得。而 SqlSessionFactoryBuilder
则可以从 XML
配置文件或一个预先定制的 Configuration
的实例构建出 SqlSessionFactory
的实例。
1 2 3 String resource = "org/mybatis/example/mybatis-config.xml" ;InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder ().build(inputStream);
从上文的实例代码可以看出,sqlSessionFactory
实例是通过SqlSessionFactoryBuilder
的build
方法构建出来的。我们进入SqlSessionFactoryBuilder
中可以看到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class SqlSessionFactoryBuilder { public SqlSessionFactory build (Reader reader) {...}; public SqlSessionFactory build (Reader reader, String environment) {...}; public SqlSessionFactory build (Reader reader, Properties properties) {...}; public SqlSessionFactory build (Reader reader, String environment, Properties properties) {...}; public SqlSessionFactory build (InputStream inputStream) {...}; public SqlSessionFactory build (InputStream inputStream, String environment) {...}; public SqlSessionFactory build (InputStream inputStream, Properties properties) {...}; public SqlSessionFactory build (InputStream inputStream, String environment, Properties properties) {...}; public SqlSessionFactory build (Configuration config) { return new DefaultSqlSessionFactory (config); }; }
SqlSessionFactoryBuilder
拥有9
个build
重载方法,大概可以分为两类:
以XML配置文件输入流的方式,如Read
字符流或 InputStream
字节流
预先实例化一个Configuration
实例
SqlSessionFactoryBuilder
从命名可以看出就是SqlSessionFactory
构建器,功能是去构建出SqlSessionFactory
实例,而SqlSessionFactory
再去构建出SqlSession
, 这个SqlSession
可以理解为数据库客户端连接服务端的会话。在现实生活中,我们是知道数据库服务器的主机地址,端口,用户名,密码等信息的,对应用而言,也应该有个「地方」去记录这些配置信息,在MyBatis
中,这个「地方」就是「配置文件」,一般命名为mybatis-config.xml
。XML文件是文件层面的「配置」,而Configuration
类是Java Class
层面上的「配置」。
MyBatis 配置文件的使用 一般MyBatis
的配置文件是一个XML文件,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd" > <configuration > <environments default ="development" > <environment id ="development" > <transactionManager type ="JDBC" /> <dataSource type ="POOLED" > <property name ="driver" value ="${driver}" /> <property name ="url" value ="${url}" /> <property name ="username" value ="${username}" /> <property name ="password" value ="${password}" /> </dataSource > </environment > </environments > <mappers > <mapper resource ="org/mybatis/example/BlogMapper.xml" /> </mappers > </configuration >
大概可以分为两部分:
XML头部声明 DTD文件(Document Type Definition
):用于验证格式的正确性,关于DTD
的介绍可以查看这里 。
以configuration
为头节点的配置节点树:用于设置MyBatis的行为和属性
这里稍微多说一句,配置文件的头部声明是HTTP协议的,那是不是意味着校验XML合法性时必须请求网络一次? 在初始化XMLConfigBuilder
时,发现也同时传入了一个XMLMapperEntityResolver
实例。
1 2 3 public XMLConfigBuilder (InputStream inputStream, String environment, Properties props) { this (new XPathParser (inputStream, true , props, new XMLMapperEntityResolver ()), environment, props); }
可以看到XMLMapperEntityResolver
#resolveEntity
方法中,会直接读取存在本地的dtd文件,这样保证了就算处于离线环境依旧可以成功校验配置文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd" ; private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd" ; @Override public InputSource resolveEntity (String publicId, String systemId) throws SAXException { try { if (systemId != null ) { String lowerCaseSystemId = systemId.toLowerCase(Locale.ENGLISH); if (lowerCaseSystemId.contains(MYBATIS_CONFIG_SYSTEM) || lowerCaseSystemId.contains(IBATIS_CONFIG_SYSTEM)) { return getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId); } else if (lowerCaseSystemId.contains(MYBATIS_MAPPER_SYSTEM) || lowerCaseSystemId.contains(IBATIS_MAPPER_SYSTEM)) { return getInputSource(MYBATIS_MAPPER_DTD, publicId, systemId); } } return null ; } catch (Exception e) { throw new SAXException (e.toString()); } }
从文档 得知全部配置如下,关于配置的解释和使用这里不作具体说明,文档的解释比较详细。
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
MyBatis 配置文件的使用的parse 过程 MyBatis
配置文件的解析就是将XML配置转换为Configuration
实例的过程。可以分为两步:
第一步:通过XPathParser
将XML转换为org.w3c.dom.Document
对象
第二步:通过eval
节点属性,设置Configuration
属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public SqlSessionFactory build (Reader reader, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder (reader, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession." , e); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException e) { } } }
回过头再看SqlSessionFactoryBuilder
#build
方法,创建 XMLConfigBuilder
对象时就完成转换为Document
对象的过程,parser.parse()
做的就是构造Configuration
实例。
创建 org.w3c.dom.Document 对象 XMLConfigBuilder
持有XPathParser
,XPathParser
持有Document
,在构造XMLConfigBuilder
时,同时也会构造XPathParser
,在构造XPathParser
时通过调用createDocument
方法设置了该属性。
1 2 3 4 public XPathParser (InputStream inputStream, boolean validation, Properties variables) { commonConstructor(validation, variables, null ); this .document = createDocument(new InputSource (inputStream)); }
构造Configuration
XMLConfigBuilder
的parse
方法会返回一个Configuration
实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public Configuration parse () { if (parsed) { throw new BuilderException ("Each XMLConfigBuilder can only be used once." ); } parsed = true ; parseConfiguration(parser.evalNode("/configuration" )); return configuration; }
对于配置文件的解析全部体现在parseConfiguration(parser.evalNode("/configuration"));
,进入到该方法中可以看到,这个方法做的事情就是去一一解析Document对象中的标签,然后将解析后的标签值设置到configuration
实例中。其中的每一个方法都代表了对一种配置解析。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 private void parseConfiguration (XNode root) { try { propertiesElement(root.evalNode("properties" )); Properties settings = settingsAsProperties(root.evalNode("settings" )); loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases" )); pluginElement(root.evalNode("plugins" )); objectFactoryElement(root.evalNode("objectFactory" )); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory" )); reflectorFactoryElement(root.evalNode("reflectorFactory" )); settingsElement(settings); environmentsElement(root.evalNode("environments" )); databaseIdProviderElement(root.evalNode("databaseIdProvider" )); typeHandlerElement(root.evalNode("typeHandlers" )); mapperElement(root.evalNode("mappers" )); } catch (Exception e) { throw new BuilderException ("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
由于配置项过多,我们这对其中几个比较常用的配置解析进行说明。
解析properties标签 1 2 3 4 5 6 7 8 9 10 11 <properties resource ="org/mybatis/example/config.properties" > <property name ="username" value ="dev_user" /> <property name ="password" value ="F2Fa3!33TYyg" /> </properties > <dataSource type ="POOLED" > <property name ="driver" value ="${driver}" /> <property name ="url" value ="${url}" /> <property name ="username" value ="${username}" /> <property name ="password" value ="${password}" /> </dataSource >
properties
的作用简单说就是去动态替换属性的值。properties
基本和Java
语言中的Properties
是一个意思,其本质就体现Configuration
中的variables字段。
1 2 3 4 5 6 protected Properties variables = new Properties ();
细心一点就会发现,其实Properties
不仅可以在XML配置中定义,而且还可以通过读取Properties
方法,以及通过XMLConfigBuilder(reader, environment, properties)
的方式传入到variables
中。
XML配置中的Properties
应用中的Properties
文件
作为XMLConfigBuilder
构造方法参数传递的Properties
当以上3个地方都有相同名字的Properties
时,那么MyBatis
会用哪一个呢?对于这一点,文档中有特别解释。
如果属性在不只一个地方进行了配置,那么 MyBatis
将按照下面的顺序来加载: 在 properties
元素体内指定的属性首先被读取。 然后根据 properties
元素中的 resource
属性读取类路径下属性文件或根据 url 属性指定的路径读取属性文件,并覆盖已读取的同名属性。 最后读取作为方法参数传递的属性,并覆盖已读取的同名属性。 因此,通过方法参数传递的属性具有最高优先级,resource/url
属性中指定的配置文件次之,最低优先级的是 properties
属性中指定的属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 private void propertiesElement (XNode context) throws Exception { if (context != null ) { Properties defaults = context.getChildrenAsProperties(); String resource = context.getStringAttribute("resource" ); String url = context.getStringAttribute("url" ); if (resource != null && url != null ) { throw new BuilderException ("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other." ); } if (resource != null ) { defaults.putAll(Resources.getResourceAsProperties(resource)); } else if (url != null ) { defaults.putAll(Resources.getUrlAsProperties(url)); } Properties vars = configuration.getVariables(); if (vars != null ) { defaults.putAll(vars); } parser.setVariables(defaults); configuration.setVariables(defaults); } }
以上做了两件事:
依次分别读取XML配置的Properties
标签、读取本地文件系统或远程网络的Properties
配置文件(取决于<properties>
节点的 resource
和 url
是否为空)、作为XMLConfigBuilder
构造方法的Properties
参数,将其全部putAll
到defaults
中。
设置 defaults
到 parser
和 configuration
中 所以,优先级最高的是作为XMLConfigBuilder
构造方法的Properties
参数、其次是本地文件系统或远程网络的Properties
配置文件、优先级最低的是XML配置的Properties
标签。
解析settings标签 settings
是对MyBatis
行为和属性的定义,比如useGeneratedKeys
这个setting
代表的含义就是允许 JDBC
支持自动生成主键。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private Properties settingsAsProperties (XNode context) { if (context == null ) { return new Properties (); } Properties props = context.getChildrenAsProperties(); MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory); for (Object key : props.keySet()) { if (!metaConfig.hasSetter(String.valueOf(key))) { throw new BuilderException ("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive)." ); } } return props; }
以上做了两件事:
解析setting
标签到props
反射Configuration
,循环props
,调用metaConfig.hasSetter
方法检测Configuration
中是否持有对应的setter
方法,否则抛出异常。
为什么需要这样做,我的理解是在对settings
设置到Configuration
前端,需要先进行安全检查,从而保证用于填写的settings
标签都是正确的。
解析typeAliases标签 typeAliases
中文直译就是「类型别名」,解决的问题在于简化类的全限名的冗余。
1 2 3 4 5 6 7 8 9 <typeAliases > <typeAlias alias ="Author" type ="domain.blog.Author" /> <typeAlias alias ="Blog" type ="domain.blog.Blog" /> <typeAlias alias ="Comment" type ="domain.blog.Comment" /> <typeAlias alias ="Post" type ="domain.blog.Post" /> <typeAlias alias ="Section" type ="domain.blog.Section" /> <typeAlias alias ="Tag" type ="domain.blog.Tag" /> <package name ="domain.blog" /> </typeAliases >
现在当前的XML文件中,如果之后在resultType
中要用到domain.blog.Author
类型引用,就只要用Author
就可以代替。在typeAliases
标签中支持两种注册别名的方式:
准确注册:typeAlias
标签,定义alias
为别名,type
为别名引用,用于为准确的一个类注册别名;
包注册:package
标签,name
属性,将整个包中的类都注册为别名,除非类中已经声明@Alias
注解,否则别名默认为类的小写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 private void typeAliasesElement (XNode parent) { if (parent != null ) { for (XNode child : parent.getChildren()) { if ("package" .equals(child.getName())) { String typeAliasPackage = child.getStringAttribute("name" ); configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage); } else { String alias = child.getStringAttribute("alias" ); String type = child.getStringAttribute("type" ); try { Class<?> clazz = Resources.classForName(type); if (alias == null ) { typeAliasRegistry.registerAlias(clazz); } else { typeAliasRegistry.registerAlias(alias, clazz); } } catch (ClassNotFoundException e) { throw new BuilderException ("Error registering typeAlias for '" + alias + "'. Cause: " + e, e); } } } } }
以上做了3件事:
遍历typeAlias
标签
匹配到package
时,将整个包的类都注册为别名
否则直接注册类和别名,注册之前需要先检查一下类是否存在,不存在就跑出异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class TypeAliasRegistry { private final Map<String, Class<?>> TYPE_ALIASES = new HashMap <>(); public TypeAliasRegistry () { registerAlias("string" , String.class); registerAlias("byte" , Byte.class); registerAlias("long" , Long.class); registerAlias("short" , Short.class); registerAlias("int" , Integer.class); registerAlias("integer" , Integer.class); registerAlias("double" , Double.class); registerAlias("float" , Float.class); registerAlias("boolean" , Boolean.class); ... ... } }
可以看到,注册到MyBatis
中的别名和类都存在一个叫typeAliasRegistry
字段中,打开TypeAliasRegistry
这个类,发现其持有一个以String
为key
,Class<?>
为value
的hashmap
,而在构造方法中也初始化了一系列的JDK
原生的对象类型,这也解释了我们不用自己动手再去注册这个基础对象类型。在注释中,说到在Configuration
的构造方法中也有类似的注册,其中注册大多都是业务对象的别名。
1 2 3 4 5 6 7 public Configuration () { typeAliasRegistry.registerAlias("JDBC" , JdbcTransactionFactory.class); typeAliasRegistry.registerAlias("MANAGED" , ManagedTransactionFactory.class); ... ... }
这里还有个问题没解决,MyBatis
时怎么处理没有声明alias
这种情况的呢?比如下面这种场景
1 2 3 <typeAliases > <typeAlias type ="domain.blog.Author" /> </typeAliases >
点进TypeAliasRegistry#registerAlias
方法则解释了对该情况的处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public void registerAlias (Class<?> type) { String alias = type.getSimpleName(); Alias aliasAnnotation = type.getAnnotation(Alias.class); if (aliasAnnotation != null ) { alias = aliasAnnotation.value(); } registerAlias(alias, type); } public void registerAlias (String alias, Class<?> value) { if (alias == null ) { throw new TypeException ("The parameter alias cannot be null" ); } String key = alias.toLowerCase(Locale.ENGLISH); if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) { throw new TypeException ("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'." ); } TYPE_ALIASES.put(key, value); }
MyBatis
对于alias
为null
的情况的处理方式是直接获取注册的这个类的SimpleName
,之后再检查这个类中是否存在alias
注解,有的话直接用声明的注解中的值,注册到typeAliasRegistry
之前需要将alias
全部小写。
解析environments标签 一般而言,我们通常在配置文件中定义environment
1 2 3 4 5 6 7 8 9 10 11 12 13 <environments default ="development" > <environment id ="development" > <transactionManager type ="JDBC" > <property name ="..." value ="..." /> </transactionManager > <dataSource type ="POOLED" > <property name ="driver" value ="${driver}" /> <property name ="url" value ="${url}" /> <property name ="username" value ="${username}" /> <property name ="password" value ="${password}" /> </dataSource > </environment > </environments >
environments
的中文解释是「环境配置」,在MyBatis
的文档中特别强调了,SqlSessionFactory
和environment
是一对一关系,也就是说,对于一个数据库环境比如MySQL
环境,需要一个SqlSessionFactory
实例,但如果这时需要增加一个数据库环境(比如Oracle
环境或者另外一台MySQL
主机环境),则需要再添加一个environment
,再添加一个SqlSessionFactory
实例。所以不会出现说,一个SqlSessionFactory
实例同时选择多个environment
的情况。
尽管可以配置多个环境,但每个 SqlSessionFactory
实例只能选择一种环境。 每个数据库对应一个 SqlSessionFactory
实例
这样就解释了,在创建SqlSessionFactory
实例时,可以通过直接将environment
作为入参的方式。
1 2 SqlSessionFactory factory = new SqlSessionFactoryBuilder ().build(reader, environment);SqlSessionFactory factory = new SqlSessionFactoryBuilder ().build(reader, environment, properties);
而environment
在MyBatis
中便是org.apache.ibatis.mapping.Environment
这个类,environmentsElement
方法做的事情就是,先找出default
环境,遍历environments
节点找出对应的环境配置,然后初始化TransactionFactory
、DataSourceFactory
、DataSource
这些实例,最后再构造出该环境并设置到configuration
中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 private void environmentsElement (XNode context) throws Exception { if (context != null ) { if (environment == null ) { environment = context.getStringAttribute("default" ); } for (XNode child : context.getChildren()) { String id = child.getStringAttribute("id" ); if (isSpecifiedEnvironment(id)) { TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager" )); DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource" )); DataSource dataSource = dsFactory.getDataSource(); Environment.Builder environmentBuilder = new Environment .Builder(id) .transactionFactory(txFactory) .dataSource(dataSource); configuration.setEnvironment(environmentBuilder.build()); } } } }
解析mappers标签 在MyBatis
中有两种配置文件:
全局配置的Configuration
文件
用于记录SQL语句的Mapper
文件
在Configuration
文件中,mappers
标签的用处就是去声明具体执行的SQL
语句记录在哪里。mappers
标签支持4种声明方式:
使用相对于类路径的资源引用:<mapper resource="abc.xml"/>
使用完全限定资源定位符(URL):<mapper url="file:///abc.xml"/>
使用映射器接口实现类的完全限定类名:<mapperclass="abcMapper"/>
将包内的映射器接口实现全部注册为映射器:<package name="org.mybatis.builder"/>
我的理解以上的声明方式可以分为两类:一类是引用XML文件,一类是引用Mapper Interface
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 private void mapperElement (XNode parent) throws Exception { if (parent != null ) { for (XNode child : parent.getChildren()) { if ("package" .equals(child.getName())) { String mapperPackage = child.getStringAttribute("name" ); configuration.addMappers(mapperPackage); } else { String resource = child.getStringAttribute("resource" ); String url = child.getStringAttribute("url" ); String mapperClass = child.getStringAttribute("class" ); if (resource != null && url == null && mapperClass == null ) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder (inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null ) { ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder (inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null ) { Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException ("A mapper element may only specify a url, resource or class, but not more than one." ); } } } } }
MyBatis
解析mappers
标签时,会便利每一个mapper
的节点,首先区分是否为package
,若属性不为package
则检测其他3种情况,根据每种情况再具体处理。 可以跟进package
的代码往下看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public <T> void addMapper (Class<T> type) { if (type.isInterface()) { if (hasMapper(type)) { throw new BindingException ("Type " + type + " is already known to the MapperRegistry." ); } boolean loadCompleted = false ; try { knownMappers.put(type, new MapperProxyFactory <>(type)); MapperAnnotationBuilder parser = new MapperAnnotationBuilder (config, type); parser.parse(); loadCompleted = true ; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
以上做的事情时将package
下的interface
全部注册到MapperRegistry
中,在MapperRegistry
中持有knownMappers
去存储这些interface
的信息。 到现在为止,只是处理了interface
,那么真实映射的那些XML配置文件时在哪里处理的呢。可以跟进到parser.parse();
中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public void parse () { String resource = type.toString(); if (!configuration.isResourceLoaded(resource)) { loadXmlResource(); configuration.addLoadedResource(resource); assistant.setCurrentNamespace(type.getName()); parseCache(); parseCacheRef(); Method[] methods = type.getMethods(); for (Method method : methods) { try { if (!method.isBridge()) { parseStatement(method); } } catch (IncompleteElementException e) { configuration.addIncompleteMethod(new MethodResolver (this , method)); } } } parsePendingMethods(); }
loadXmlResource()
做的事情主要是对于Mapper XML
文件的处理,具体怎么解析在下一篇文章中介绍。loadedResources
用来存储已加载资源的信息。 最后解析后的语句信息保存在mappedStatements
中,mappedStatements
是一个HashMap
,key
是namespace.id
的字符,具体执行的SQL
语句信息就是value
,在实际debug
中,发现同时也存储了相同一份为id
的Entry
。不知道MyBatis
为什么要这样做。
总结 回过头来,我们再看如何去生成一个SqlSessionFactory
,从SqlSessionFactoryBuilder
的build
方法中我们可以找到答案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public SqlSessionFactory build (Reader reader, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder (reader, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession." , e); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException e) { } } }
build
方法做的事情就是读取配置文件XML转换为Configuration
实例。其中最关键的两句代码代表来这个过程的两个阶段。
创建 XMLConfigBuilder
对象,就是读取全局配置XML
文件到DOM
对象的过程,这个过程中包含对XML
配置的校验。
从DOM
对象到Configuration
对象的过程,解析全局配置XML
各个标签,以及解析mapper
标签中声明的mapper.xml
文件。
参考引用 MyBatis 文档