mybatis 3.x 源代码深度分析和最佳实践(最完整的原文)
mybatis 3.x源码深度解析与最佳实践
html版离线文件可从https://files.cnblogs.com/files/zhjh256/mybatis3.x%E6%BA%90%E7%A0%81%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5.rar下载。
-
-
1 环境准备
- 1.1 mybatis介绍以及框架源码的学习目标
- 1.2 本系列源码解析的方式
- 1.3 环境搭建
- 1.4 从Hello World开始
-
2 容器的加载与初始化
-
2.1 config文件解析XMLConfigBuilder.parseConfiguration
- 2.1.1 属性解析propertiesElement
- 2.1.2 加载settings节点settingsAsProperties
- 2.1.3 加载自定义VFS loadCustomVfs
- 2.1.4 解析类型别名typeAliasesElement
- 2.1.5 加载插件pluginElement
- 2.1.6 加载对象工厂objectFactoryElement
-
2.1.7 创建对象包装器工厂objectWrapperFactoryElement
- 2.1.8 加载反射工厂reflectorFactoryElement
- 2.1.9 加载环境配置environmentsElement
- 2.1.10 数据库厂商标识加载databaseIdProviderElement
-
2.1.11 加载类型处理器typeHandlerElement
- 处理枚举类型映射
- 2.1.12 加载mapper文件mapperElement
- 2.2 mapper加载与初始化
-
2.3 解析mapper文件XMLMapperBuilder
- 2.3.1 鉴别器discriminator的解析
- 解析SQL主体
-
sql语句解析的核心:mybatis语言驱动器XMLLanguageDriver
- IfHandler
- OtherwiseHandler
- BindHandler
- ChooseHandler
- ForEachHandler
- SetHandler
- TrimHandler
- WhereHandler
- 静态SQL创建RawSqlSource
- 动态SQL创建 DynamicSqlSource
- 4、二次解析未完成的结果映射、缓存参照、CRUD语句;
-
2.1 config文件解析XMLConfigBuilder.parseConfiguration
-
3 关键对象总结与回顾
- 3.1 SqlSource
-
3.2 SqlNode
- ChooseSqlNode
- ForEachSqlNode
- IfSqlNode
- StaticTextSqlNode
- TextSqlNode
- VarDeclSqlNode
- TrimSqlNode
- SetSqlNode
- WhereSqlNode
- 3.3 BaseBuilder
- 3.4 AdditionalParameter
- 3.5 TypeHandler
- 3.6 对象包装器工厂ObjectWrapperFactory
- 3.7 MetaObject
- 3.8 对象工厂ObjectFactory
- 3.13 LanguageDriver
- 3.14 ResultMap
- 3.15 ResultMapping
- 3.16 Discriminator
-
4 SQL语句的执行流程
- 4.1 传统JDBC用法
-
4.2 mybatis执行SQL语句
- 4.2.1 获取openSession
-
4.2.2 sql语句执行方式一
- mybatis结果集处理
- selectMap实现
- update/insert/delete实现
- 4.2.3 SQL语句执行方式二 SqlSession.getMapper实现
- 4.3 动态sql
- 4.4 存储过程与函数调用实现
- 4.5 mybatis事务实现
- 4.6 缓存
-
5 执行期主要类总结
-
5.1 执行器Executor
- 5.4.1 SIMPLE执行器
- 5.4.2 REUSE执行器
- 5.4.3 BATCH执行器
- 5.4.4 缓存执行器CachingExecutor的实现
- 5.2 参数处理器ParameterHandler
- 5.3 语句处理器StatementHandler
- 5.4 结果集处理器ResultSetHandler
-
5.1 执行器Executor
-
6 插件
- 6.1 分页插件PageHelper详解
- 6.2 自定义监控插件StatHelper实现
- 7 与spring集成
-
1 环境准备
1 环境准备
1.1 mybatis介绍以及框架源码的学习目标
MyBatis是当前最流行的java持久层框架之一,其通过XML配置的方式消除了绝大部分JDBC重复代码以及参数的设置,结果集的映射。虽然mybatis是最为流行的持久层框架之一,但是相比其他开源框架比如spring/netty的源码来说,其注释相对而言显得比较少。为了更好地学习和理解mybatis背后的设计思路,作为高级开发人员,有必要深入研究了解优秀框架的源码,以便更好的借鉴其思想。同时,框架作为设计模式的主要应用场景,通过研究优秀框架的源码,可以更好的领会设计模式的精髓。学习框架源码和学习框架本身不同,我们不仅要首先比较细致完整的熟悉框架提供的每个特性,还要理解框架本身的初始化过程等,除此之外,更重要的是,我们不能泛泛的快速浏览哪个功能是通过哪个类或者接口实现和封装的,对于核心特性和初始化过程的实现,我们应该达到下列目标:
- 对于核心特性和内部功能,具体是如何实现的,采用什么数据结构,边研究边思考这么做是否合理,或者不合理,尤其是很多的特性和功能的调用频率是很低的,或者很多集合中包含的元素数量在99%以上的情况下都是1,这种情况下很多设计决定并不需要特别去选择或者适合于大数据量或者高并发的复杂实现。对于很多内部的数据结构和辅助方法,不仅需要知道其功能本身,还需要知道他们在上下文中发挥的作用。
- 对于核心特性和内部功能,具体实现采用了哪些设计模式,使用这个设计模式的合理性;
- 绝大部分框架都被设计为可扩展的,mybatis也不例外,它提供了很多的扩展点,比如最常用的插件,语言驱动器,执行器,对象工厂,对象包装器工厂等等都可以扩展,所以,我们应该知道如有必要的话,如何按照要求进行扩展以满足自己的需求;
1.2 本系列源码解析的方式
首先,和从使用层面相同,我们在理解实现细节的时候,也会涉及到学习A的时候,引用到B中的部分概念,B又引用了C的部分概念,C反过来又依赖于D接口和A的相关接口操作,D又有很多不同的具体实现。在使用层面,我们通常不需要深入去理解B的具体实现,只要知道B的概念和可以提供什么特性就可以了。在实现层面,我们必须去全部掌握这所有依赖的实现,直到最底层JDK原生操作或者某些我们熟悉的类库为止。这实际上涉及到横向流程和纵向切面的交叉引用,以及两个概念的接口和多种实现之间的交叉调用。所以,我们在整个系列中会先按照业务流程横向的方式进行整体分析,并附上必要的流程图,在整个大流程完成之后,在一个专门的章节安排对关键的类进行详细分析,它们的适用场景,这样就可以交叉引用,又不会在主流程中夹杂太多的干扰内容。
其次,因为我们免不了会对源码的主要部分做必要的注释,所以,对源码的讲解和注释会穿插进行,对于某些部分通过注释后已经完全能够知道其含义的实现,就不会在多此一举的重述。
1.3 环境搭建
从https://github.com/mybatis/mybatis-3下载mybatis 3源代码,导入eclipse工程,执行maven clean install,本书所使用的mybatis版本是3.4.6-SNAPSHOT,编写时的最新版本,读者如果使用其他版本,可能代码上会有细微差别,但其基本不影响我们对主要核心代码的分析。为了更好地理解mybatis的核心实现,我们需要创建一个演示工程mybatis-internal-example,读者可从github上下载,并将依赖的mybatis坐标改成mybatis-3.4.6-SNAPSHOT。
1.4 从Hello World开始
要理解mybatis的内部原理,最好的方式是直接从头开始,而不是从和spring的集成开始。每一个MyBatis的应用程序都以一个SqlSessionFactory对象的实例为核心。SqlSessionFactory对象的实例可以又通过SqlSessionFactoryBuilder对象来获得。SqlSessionFactoryBuilder对象可以从XML配置文件加载配置信息,然后创建SqlSessionFactory。先看下面的例子:
主程序:
package org.mybatis.internal.example;
import java.io.IOException;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.mybatis.internal.example.pojo.User;
public class MybatisHelloWorld {
public static void main(String[] args) {
String resource = "org/mybatis/internal/example/Configuration.xml";
Reader reader;
try {
reader = Resources.getResourceAsReader(resource);
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = sqlMapper.openSession();
try {
User user = (User) session.selectOne("org.mybatis.internal.example.mapper.UserMapper.getUser", 1);
System.out.println(user.getLfPartyId() + "," + user.getPartyName());
} finally {
session.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
配置文件:
<?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="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://10.7.12.4:3306/lfBase?useUnicode=true"/>
<property name="username" value="lfBase"/>
<property name="password" value="eKffQV6wbh3sfQuFIG6M"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/internal/example/UserMapper.xml"/>
</mappers>
</configuration>
mapper文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.internal.example.mapper.UserMapper">
<select id="getUser" parameterType="int" resultType="org.mybatis.internal.example.pojo.User">
select lfPartyId,partyName from LfParty where lfPartyId = #{id}
</select>
</mapper>
mapper接口
package org.mybatis.internal.example.mapper;
import org.mybatis.internal.example.pojo.User;
public interface UserMapper {
public User getUser(int lfPartyId);
}
运行上述程序,不出意外的话,将输出:
1,汇金超级管理员
从上述代码可以看出,SqlSessionFactoryBuilder是一个关键的入口类,其中承担了mybatis配置文件的加载,解析,内部构建等职责。下一章,我们将重点分析mybatis配置文件的加载,配置类的构建等一系列细节。
2 容器的加载与初始化
SqlSessionFactory是通过SqlSessionFactoryBuilder工厂类创建的,而不是直接使用构造器。容器的配置文件加载和初始化流程如下:
SqlSessionFactoryBuilder的主要代码如下:
public class SqlSessionFactoryBuilder {
// 使用java.io.Reader构建
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
public SqlSessionFactory build(Reader reader, String environment) {
return build(reader, environment, null);
}
public SqlSessionFactory build(Reader reader, Properties properties) {
return build(reader, null, properties);
}
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) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment) {
return build(inputStream, environment, null);
}
public SqlSessionFactory build(InputStream inputStream, Properties properties) {
return build(inputStream, null, properties);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
根据上述接口可知,SqlSessionFactory提供了根据字节流、字符流以及直接使用org.apache.ibatis.session.Configuration配置类(后续我们会详细讲到)三种途径的读取配置信息方式,无论是字符流还是字节流方式,首先都是将XML配置文件构建为Configuration配置类,然后将Configuration设置到SqlSessionFactory默认实现DefaultSqlSessionFactory的configurationz字段并返回。所以,它本身很简单,解析配置文件的关键逻辑都委托给XMLConfigBuilder了,我们以字符流也就是java.io.Reader为例进行分析,SqlSessionFactoryBuilder使用了XMLConfigBuilder作为解析器。
public class XMLConfigBuilder extends BaseBuilder {
private boolean parsed;
private XPathParser parser;
private String environment;
private ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
....
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
....
XMLConfigBuilder以及解析Mapper文件的XMLMapperBuilder都继承于BaseBuilder。他们对于XML文件本身技术上的加载和解析都委托给了XPathParser,最终用的是jdk自带的xml解析器而非第三方比如dom4j,底层使用了xpath方式进行节点解析。new XPathParser(reader, true, props, new XMLMapperEntityResolver())的参数含义分别是Reader,是否进行DTD 校验,属性配置,XML实体节点解析器。
entityResolver比较好理解,跟Spring的XML标签解析器一样,有默认的解析器,也有自定义的比如tx,dubbo等,主要使用了策略模式,在这里mybatis硬编码为了XMLMapperEntityResolver。
XMLMapperEntityResolver的定义如下:
public class XMLMapperEntityResolver implements EntityResolver {
private static final String IBATIS_CONFIG_SYSTEM = "ibatis-3-config.dtd";
private static final String IBATIS_MAPPER_SYSTEM = "ibatis-3-mapper.dtd";
private static final String MYBATIS_CONFIG_SYSTEM = "mybatis-3-config.dtd";
private static final String MYBATIS_MAPPER_SYSTEM = "mybatis-3-mapper.dtd";
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";
/*
* Converts a public DTD into a local one
* 将公共的DTD转换为本地模式
*
* @param publicId The public id that is what comes after "PUBLIC"
* @param systemId The system id that is what comes after the public id.
* @return The InputSource for the DTD
*
* @throws org.xml.sax.SAXException If anything goes wrong
*/
@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());
}
}
private InputSource getInputSource(String path, String publicId, String systemId) {
InputSource source = null;
if (path != null) {
try {
InputStream in = Resources.getResourceAsStream(path);
source = new InputSource(in);
source.setPublicId(publicId);
source.setSystemId(systemId);
} catch (IOException e) {
// ignore, null is ok
}
}
return source;
}
}
从上述代码可以看出,mybatis解析的时候,引用了本地的DTD文件,和本类在同一个package下,其中的ibatis-3-config.dtd应该主要是用于兼容用途。在其中getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId)的调用里面有两个参数publicId(公共标识符)和systemId(系统标示符),他们是XML 1.0规范的一部分。他们的使用如下:
<?xml version="1.0"?>
<!DOCTYPE greeting SYSTEM "hello.dtd">
<greeting>Hello, world!</greeting>
系统标识符 “hello.dtd” 给出了此文件的 DTD 的地址(一个 URI 引用)。在mybatis中,这里指定的是mybatis-3-config.dtd和ibatis-3-config.dtd。publicId则一般从XML文档的DOCTYPE中获取,如下:
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
继续往下看,
public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(reader));
}
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
可知,commonConstructor并没有做什么。回过头到createDocument上,其使用了org.xml.sax.InputSource作为参数,createDocument的关键代码如下:
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(validation);
//设置由本工厂创建的解析器是否支持XML命名空间 TODO 什么是XML命名空间
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
//设置是否将CDATA节点转换为Text节点
factory.setCoalescing(false);
//设置是否展开实体引用节点,这里应该是sql片段引用的关键
factory.setExpandEntityReferences(true);
DocumentBuilder builder = factory.newDocumentBuilder();
//设置解析mybatis xml文档节点的解析器,也就是上面的XMLMapperEntityResolver
builder.setEntityResolver(entityResolver);
builder.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void warning(SAXParseException exception) throws SAXException {
}
});
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
主要是根据mybatis自身需要创建一个文档解析器,然后调用parse将输入input source解析为DOM XML文档并返回。
得到XPathParser实例之后,就调用另一个使用XPathParser作为配置来源的重载构造函数了,如下:
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
其中调用了父类BaseBuilder的构造器(主要是设置类型别名注册器,以及类型处理器注册器):
public abstract class BaseBuilder {
protected final Configuration configuration;
protected final TypeAliasRegistry typeAliasRegistry;
protected final TypeHandlerRegistry typeHandlerRegistry;
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
我们等下再来看Configuration这个核心类的关键信息,主要是xml配置文件里面的所有配置的实现表示(几乎所有的情况下都要依赖与Configuration这个类)。
设置关键配置environment以及properties文件(mybatis在这里的实现和spring的机制有些不同),最后将上面构建的XPathParser设置为XMLConfigBuilder的parser属性值。
XMLConfigBuilder创建完成之后,SqlSessionFactoryBuild调用parser.parse()创建Configuration。所有,真正Configuration构建逻辑就在XMLConfigBuilder.parse()里面,如下所示:
public class XMLConfigBuilder extends BaseBuilder {
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//mybatis配置文件解析的主流程
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
}
2.1 config文件解析XMLConfigBuilder.parseConfiguration
首先判断有没有解析过配置文件,只有没有解析过才允许解析。其中调用了parser.evalNode(“/configuration”)返回根节点的org.apache.ibatis.parsing.XNode表示,XNode里面主要把关键的节点属性和占位符变量结构化出来,后面我们再看。然后调用parseConfiguration根据mybatis的主要配置进行解析,如下所示:
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
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);
// read it after objectFactory and objectWrapperFactory issue #631
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);
}
}
所有的root.evalNode底层都是调用XML DOM的evaluate()方法,根据给定的节点表达式来计算指定的 XPath 表达式,并且返回一个XPathResult对象,返回类型在Node.evalNode()方法中均被指定为NODE。
在详细解释每个节点的解析前,我们先来粗略看下mybatis-3-config.dtd的声明:
<?xml version="1.0" encoding="UTF-8" ?>
<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>
<!ELEMENT databaseIdProvider (property*)>
<!ATTLIST databaseIdProvider
type CDATA #REQUIRED
>
<!ELEMENT properties (property*)>
<!ATTLIST properties
resource CDATA #IMPLIED
url CDATA #IMPLIED
>
<!ELEMENT property EMPTY>
<!ATTLIST property
name CDATA #REQUIRED
value CDATA #REQUIRED
>
<!ELEMENT settings (setting+)>
<!ELEMENT setting EMPTY>
<!ATTLIST setting
name CDATA #REQUIRED
value CDATA #REQUIRED
>
<!ELEMENT typeAliases (typeAlias*,package*)>
<!ELEMENT typeAlias EMPTY>
<!ATTLIST typeAlias
type CDATA #REQUIRED
alias CDATA #IMPLIED
>
<!ELEMENT typeHandlers (typeHandler*,package*)>
<!ELEMENT typeHandler EMPTY>
<!ATTLIST typeHandler
javaType CDATA #IMPLIED
jdbcType CDATA #IMPLIED
handler CDATA #REQUIRED
>
<!ELEMENT objectFactory (property*)>
<!ATTLIST objectFactory
type CDATA #REQUIRED
>
<!ELEMENT objectWrapperFactory EMPTY>
<!ATTLIST objectWrapperFactory
type CDATA #REQUIRED
>
<!ELEMENT reflectorFactory EMPTY>
<!ATTLIST reflectorFactory
type CDATA #REQUIRED
>
<!ELEMENT plugins (plugin+)>
<!ELEMENT plugin (property*)>
<!ATTLIST plugin
interceptor CDATA #REQUIRED
>
<!ELEMENT environments (environment+)>
<!ATTLIST environments
default CDATA #REQUIRED
>
<!ELEMENT environment (transactionManager,dataSource)>
<!ATTLIST environment
id CDATA #REQUIRED
>
<!ELEMENT transactionManager (property*)>
<!ATTLIST transactionManager
type CDATA #REQUIRED
>
<!ELEMENT dataSource (property*)>
<!ATTLIST dataSource
type CDATA #REQUIRED
>
<!ELEMENT mappers (mapper*,package*)>
<!ELEMENT mapper EMPTY>
<!ATTLIST mapper
resource CDATA #IMPLIED
url CDATA #IMPLIED
class CDATA #IMPLIED
>
<!ELEMENT package EMPTY>
<!ATTLIST package
name CDATA #REQUIRED
>
从上述DTD可知,mybatis-config文件最多有11个配置项,分别是properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?,但是所有的配置都是可选的,这意味着mybatis-config配置文件本身可以什么都不包含。因为所有的配置最后保存到org.apache.ibatis.session.Configuration中,所以在详细查看每块配置的解析前,我们来看下Configuration的内部完整结构: