一、 mybatis初始化
类似于 Spring、 MyBatis 等灵活性和可扩展性都很高的开源框架都提供了很多配置项,开发人员需要在使用时提供相应的配置信息,实现相应的需求。
MyBatis 中的配置文件主要有两 个,分别是 mybatis-config.xml 配置文件和映射配置文件。 现在主流的配置方式除了使用 XML 配置文件,还会配合注解进行配置。在 MyBatis 初始 化的过程中,除了会读取 mybatis-config.xml 配置文件以及映射配置文件,还会加载配直文件指 定的类,处理类中的注解,创建一些配置对象,最终完成框架中各个模块的初始化。另外,也 可以使用 Java API 的方式对 MyBatis 进行配置,这种硬编码的配置方式主要用在配置量比较少 且配置信息不常变化的场景下。
1.1mybatis初始化
过程主要使用建造者模型
- XMLConfigBuilder:主要负责解释mybatis-config.xml
- XMLMapperBuilder:负责解析映射配置文件
- XMLStatementBuilder:负责解析映射配置文件中的SQL结点
1.2 映射器的关键类
- Configuration:Mybatis启动初始化的核心就是将所有xml配置文件信息加载到Configuration对象中,Configuration是单例的,生命周期是应用级的
- MapperRegistry:mapper接口动态代理工厂类的注册中心。在Mybatis中,通过mapperProxy实现InvocationHandler接口,MapperProxyFactory用于生成动态代理的实例对象
- ResultMap:用于解析mapper.xml文件中的resultMap节点,使用ResultMapping来封装id、result等子元素
- MappedStatement:用于存储mapper.xml文件中的select、insert、update和delete节点,同时还包含了这些节点很多重要属性
- SqlSource:mapper.xml文件中sql语句会被解析成SqlSource对象,经过解析SqlSource包含的语句最终仅仅包含?占位符,可以直接交给数据库执行。
1.3 configuration类图解
1.4 ResultMap图解
1.5 mappedStatment图解
二 、源码解析
2.1 Configuration的域对应mybatis-config.xml中相应的配置项
public class Configuration {
protected Environment environment;
/* 是否启用数据组A_column自动映射到Java类中的驼峰命名的属性**/
protected boolean mapUnderscoreToCamelCase;
/*当对象使用延迟加载时 属性的加载取决于能被引用到的那些延迟属性,否则,按需加载(需要的是时候才去加载)**/
protected boolean aggressiveLazyLoading;
/*是否允许单条sql 返回多个数据集 (取决于驱动的兼容性) default:true **/
protected boolean multipleResultSetsEnabled = true;
/*-允许JDBC 生成主键。需要驱动器支持。如果设为了true,这个设置将强制使用被生成的主键,有一些驱动器不兼容不过仍然可以执行。 default:false**/
protected boolean useGeneratedKeys;
/*配置全局性的cache开关,默认为true**/
protected boolean cacheEnabled = true;
/*指定 MyBatis 应如何自动映射列到字段或属性*/
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
/*MyBatis每次创建结果对象的新实例时,它都会使用对象工厂(ObjectFactory)去构建POJO*/
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
/*延迟加载的全局开关*/
protected boolean lazyLoadingEnabled = false;
/*指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具*/
protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
/*插件集合*/
protected final InterceptorChain interceptorChain = new InterceptorChain();
/*TypeHandler注册中心*/
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
/*TypeAlias注册中心*/
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
//-------------------------------------------------------------
/*mapper接口的动态代理注册中心*/
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
/*mapper文件中增删改查操作的注册中心*/
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");
/*mapper文件中配置的所有resultMap对象 key为命名空间+ID*/
protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
/*加载到的所有*mapper.xml文件*/
protected final Set<String> loadedResources = new HashSet<>();
2.2 BaseBuilder
MyBatis 初始化的主 要工作是加载井解析 mybatis-config.xml 配置文件、映射配置文件以及相关的注解信息。 MyBatis 的初始化入口是 SqlSessionFactoryBuilder. build()方法,其具体实现如下:
// 读取mybatis配置文件
inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//1. SqlSessionFactoryBuilder创建 SqlSessionFactory
/**
* 读取配置信息创建SqlSessionFactory,建造者模式,方法级别生命周期;
* SqlSessionFactory:创建Sqlsession,工厂单例模式,存在于程序的整个生命周期;
*/
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
////读取配置文件
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//解析配置文件得到 Configuration 对象,创建 DefaultSqlSessionFactory 对象
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);
}
SqlSessionFactoryBuilder.build()方法会创建 XMLConfigBuilder 对象来解析 mybatis-config 配置文件,而 XMLConfigBuilder 继承自 BaseBuilder 抽象类,
正如前面所说, MyBatis 的初始化过程使用了建造者模式,这里的 BaseBuilder 抽象类就扮 演着建造者接口 的角色。 BaseBuilder 中核心宇段的含义如下:
// Configuration 是 MyBatis 初始化过程的核心对象,
// MyBatis 中几乎全部的配置信息会保存到 Configuration对象中。
// Configuration 对象是在 MyBatis 初始化过程中创建且是全局唯一的,
// 也有人称它是一个“All-In-One”配置对象
protected final Configuration configuration;
//在 mybatis-config.xml配置文件中可以使用<typeAliases>标签定义别名,这些定义的别名都会记录在该
protected final TypeAliasRegistry typeAliasRegistry;
//在 mybatis-config.xml 配置文件中可以使用<typeHandlers>标签添加自定义 TypeHandler器,完
// 成指定数据库类型与 Java 类型的转换,这些 TypeHandler 都会记录在 TypeHandlerRegistry 中。
protected final TypeHandlerRegistry typeHandlerRegistry;
2.3 XMLConfigBuilder解析mybatis-config.xml,将解析出的相关的值加入到Configuration对象中
XMLConfigBuilder 是 BaseBuilder 的众多子类之一,它扮演的是具体建造者的角色。 XMLConfigBuilder 主要负责解析 mybatis-config.xml 配置文件,其核心字段如下:
//标识是否已经解析过 mybatis-config.xml 配置丈件
private boolean parsed;
//用于解析 mybatis-config.xml 配置文件的 XPathParser 对象
private final XPathParser parser;
//标识<environment>配置的名称,默认读取<env工ronment>标签的 default 属性
private String environment;
/// ReflectorFactory 负责创建和缓存 Reflector 对象
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
XMLConfigBuilder.parse()方法是解析 mybatis-config且nl 配置文件的入口,它通过调用 XMLConfigBuilder.parseConfiguration()方法实现整个解析过程, 具体实现如下:
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//在 mybatis-config.xml配置文件中查找<configuration>节点,并开始解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
//解析<properties>节点
propertiesElement(root.evalNode("properties"));
//解析<settings>节点
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
//解析<typeAliases>节点
typeAliasesElement(root.evalNode("typeAliases"));
//解析<plugins>节点
pluginElement(root.evalNode("plugins"));
//解析<objectFactory>节点
objectFactoryElement(root.evalNode("objectFactory"));
//解析<objectWrapperFactory>节点
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//解析<reflectorFactory>节点
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);//将settings填充到configuration
// read it after objectFactory and objectWrapperFactory issue #631
//解析<environments>节点
environmentsElement(root.evalNode("environments"));
//解析<databaseIdProvider>节点
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//解析<typeHandlers>节点
typeHandlerElement(root.evalNode("typeHandlers"));
//解析<mappers>节点
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
2.4 XMLMapperBuilder解析mapper.xml映射文件
config中配置的xml文件示例如下:
<!-- 映射文件,mapper的配置文件 -->
<mappers>
<!--直接映射到相应的mapper文件 -->
<mapper resource="sqlmapper/TUserMapper.xml" />
<mapper resource="sqlmapper/TJobHistoryMapper.xml" />
<mapper resource="sqlmapper/TPositionMapper.xml" />
<mapper resource="sqlmapper/THealthReportFemaleMapper.xml" />
<mapper resource="sqlmapper/THealthReportMaleMapper.xml" />
<mapper class="com.enjoylearning.mybatis.mapper.TJobHistoryAnnoMapper"/>
</mappers>
通过对 XMLConfigBuilder.mapperElement()方法用来解析上面文件:
//解析<mappers>节点
mapperElement(root.evalNode("mappers"));
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {//处理mapper子节点
if ("package".equals(child.getName())) {//package子节点
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {//获取<mapper>节点的resource、url或mClass属性这三个属性互斥
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {//如果resource不为空
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);//加载mapper文件
//实例化XMLMapperBuilder解析mapper映射文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {//如果url不为空
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);//加载mapper文件
//实例化XMLMapperBuilder解析mapper映射文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {//如果class不为空
Class<?> mapperInterface = Resources.classForName(mapperClass);//加载class对象
configuration.addMapper(mapperInterface);//向代理中心注册mapper
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
XMLMapperBuilder 负责 解析映射配置文件,它继承了 BaseBuilder 抽象类,也是具体建造者的角色。 XMLMapperBuilder.parse()方法是解析映射文件的入口 , 具体代码如下:
public void parse() {
//判断是否已经加载该配置文件
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));//处理mapper节点
configuration.addLoadedResource(resource);//将mapper文件添加到configuration.loadedResources中
bindMapperForNamespace();//注册mapper接口
}
//处理解析失败的ResultMap节点
parsePendingResultMaps();
//处理解析失败的CacheRef节点
parsePendingCacheRefs();
//处理解析失败的Sql语句节点
parsePendingStatements();
}
<mapper namespace="com.enjoylearning.mybatis.mapper.TUserMapper">
<cache></cache>
<resultMap id="BaseResultMap" type="TUser">
<!-- <constructor> <idArg column="id" javaType="int"/> <arg column="user_name"
javaType="String"/> </constructor> -->
<id column="id" property="id" />
<result column="user_name" property="userName" />
<result column="real_name" property="realName" />
<result column="sex" property="sex" />
<result column="mobile" property="mobile" />
<result column="email" property="email" />
<result column="note" property="note" />
</resultMap>
<resultMap id="userAndPosition1" extends="BaseResultMap" type="TUser">
<association property="position" javaType="TPosition" columnPrefix="post_">
<id column="id" property="id"/>
<result column="name" property="postName"/>
<result column="note" property="note"/>
</association>
</resultMap>
<resultMap id="userAndPosition2" extends="BaseResultMap" type="TUser">
<!--<association property="position" column="position_id" select="com.enjoylearning.mybatis.mapper.TPositionMapper.selectByPrimaryKey" />-->
<association property="position" fetchType="lazy" column="position_id" select="com.enjoylearning.mybatis.mapper.TPositionMapper.selectByPrimaryKey" />
</resultMap>
<select id="selectUserPosition1" resultMap="userAndPosition1">
select a.id,user_name,real_name, sex, mobile,email, a.note, b.id post_id,
b.post_name, b.note post_note from t_user a,t_position b where a.position_id = b.id
</select>
XMLMapperBuilder 也是将每个节点 的解析过程封装成了一个方法,而这些方法由XMLMapperBuilder.configurationElement()方法调用, 本小节将逐一分析这些节点的解析过程, configurationElement()方法的具体实现如下:
private void configurationElement(XNode context) {
try {
//获取mapper的xml文件中的namespace节点
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//设置builderAssistant的namespace属性
builderAssistant.setCurrentNamespace(namespace);
//解析<cache-ref> 节点
cacheRefElement(context.evalNode("cache-ref"));
//解析<cache>节点
cacheElement(context.evalNode("cache"));
//解析<parameterMap>节点(该节点 已废弃,不再推荐使用,不做详细介绍)
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析<resultMap>节点
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析<sql>节点
sqlElement(context.evalNodes("/mapper/sql"));
//解析<select>、<insert>、<update>、<delete>等 SQL 节点
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
2.4.1 解析<cache>节点
MyBatis 拥有非常强大的二级缓存功能, 该功能可以非常方便地进行配置, MyBatis 默认情 况下没有开启二级缓存,如果要为某命名空间开启二级缓存功能,则需要在相应映射配置文件 中添加<cache>节点,还可以通过配置<cache>节点的相关属性,为二级缓存配置相应的特性 (本 质上就是添加相应的装饰器〉。 XMLMapperBuilder.cacheElement()方法主要负责解析<cache>节点, 其具体实现如下:
private void cacheElement(XNode context) {
if (context != null) {
//获取<cache>节点的 type的属性,默认位是 PERPETUAL
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
//获取<cache>节点的 eviction属性,也就是缓存的淘汰策略,默认LRU(最近最少使用淘汰策略)
String eviction = context.getStringAttribute("eviction", "LRU");
//根据eviction属性,找到装饰器
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
//读取flushInterval属性,既缓存的刷新周期,,默认值是 null
Long flushInterval = context.getLongAttribute("flushInterval");
//读取size属性,既缓存的容量大小
Integer size = context.getIntAttribute("size");
//读取readOnly属性,既缓存的是否只读,默认位是 false
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
//读取blocking属性,既缓存的是否阻塞 默认位是 false
boolean blocking = context.getBooleanAttribute("blocking", false);
// 获取<cache>节点下的子节点,将用于初始化二级缓存
Properties props = context.getChildrenAsProperties();
//通过builderAssistant创建缓存对象,并添加至configuration
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
经典的建造起模式,创建一个cache对象 MapperBuilderAssistant.useNewCache()方法的实现:
//创建相关的cache 对象
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
//创建 Cache 对象,这里使用了建造者模式, CacheBuilder 是建造者的角色,而 Cache 是生成的产品
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
//将缓存添加至configuration,注意二级缓存以命名空间为单位进行划分
configuration.addCache(cache);
currentCache = cache;
return cache;
}
CacheBuilder 是 Cache 的建造者, 这里重点分析 CacheBuilder.build()方法, 该方法根据 CacheBuilder 中上述字 段的值创建 Cache 对象并添加合适的装饰器, 具体实现如下:
public Cache build() {
//implementation 字段和 decorators 集合为空,则为其设立默认佳, implementation 默认
// 位是 PerpetualCache.class, decorators 集合,默认只包含 LruCache.class
setDefaultImplementations();
//根据 implementation 指定的类型 , 通过反射获取参数为 String 类型的构造方法,并通过该构造方
// 法创建 Cache 对象
Cache cache = newBaseCacheInstance(implementation, id);
//根据<cache>节点下自己置的<property>信息,初始化 Cache 对象
setCacheProperties(cache);
// issue #352, do not apply decorators to custom caches
//检测 cache 对象的类型,如果是 PerpetualCache 类型,为其添加 decorators 集合中
// 的装饰器; 如采是自定义类型的 Cache 接口 实现,则不添加 decorators 集合中的装饰器
if (PerpetualCache.class.equals(cache.getClass())) {
for (Class<? extends Cache> decorator : decorators) {
//通过反射获取参数为 Cache 类型的构造方法,并通过该构造方法创建装饰器
cache = newCacheDecoratorInstance(decorator, cache);
setCacheProperties(cache);
}
//添加 MyBatis 中提供的标准装饰器
cache = setStandardDecorators(cache);
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
cache = new LoggingCache(cache);
}
return cache;
}
CacheBuilder.setStandardDecorators()方法会根据 CacheBuilder 中各个字段的值,为 cache 对 象添加对应的装饰器,具体实现如下:
//缓存模块装饰器的使用
private Cache setStandardDecorators(Cache cache) {
try {
//获取基本的cache对象
MetaObject metaCache = SystemMetaObject.forObject(cache);
//缓存的容量大小
if (size != null && metaCache.hasSetter("size")) {
metaCache.setValue("size", size);
}
//检测是否指定了 clearinterval 字段 (定期清除)
if (clearInterval != null) {
cache = new ScheduledCache(cache);//添加定期清除的装饰器
((ScheduledCache) cache).setClearInterval(clearInterval);
}
//如果是只读缓存
if (readWrite) {
//是否只读,对应添加 SerializedCache 装饰器
cache = new SerializedCache(cache);
}
//默认添加 LoggingCache 和 SynchronizedCache 两个装饰器
cache = new LoggingCache(cache);
cache = new SynchronizedCache(cache);
if (blocking) {//阻塞装饰器
cache = new BlockingCache(cache);
}
return cache;
} catch (Exception e) {
throw new CacheException("Error building standard cache decorators. Cause: " + e, e);
}
}
2.4.2 解析resultMap
select 语句查询得到的结果集是一张二维表,水平方向上看是一个个字段,垂直方向上看是 一条条记录。 而 Java 是面向对象的程序设计语言,对象是根据类定义创建的,类之间的引用关 系可以认为是嵌套的结构。在 JDBC 编程中,为了将结果集中的数据映射成对象,我们需要自 己写代码从结果集中获取数据,然后封装成对应的对象并设置对象之间的关系,而这些都是大 量的重复性代码。为了减少这些重复的代码, MyBatis 使用<resultMap>节点定义了结果集与结 果对象 OavaBean 对象〉之间的映射规则,<resultMap>节点可以满足绝大部分的映射需求,从 而减少开发人员的重复性劳动,提高开发效率。
public class Configuration {
/*mapper文件中配置的所有resultMap对象 key为命名空间+ID*/
protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
ResultMapping
每个 ResultMapping 对象记录了结果集中的一列与 JavaBean 中一个属性之间的映射关系。在后面的 分析过程中我们可以看到,<resultMap>节点下除了<discriminator>子节点的其他子节点,都会 被解析成对应的 ResultMapping 对象。 ResultMapping 中的核心字段含义如下:
//configuration对象
private Configuration configuration;
private String property;//对应节点的 column 属性,表示的是从数据库中得到的列名或是列名的别名
private String column;//对应节点的 property 属性,表示的是与该列进行映射的属性
private Class<?> javaType;//对应节点的 javaType 属性,表示的是一个 JavaBea口的完全限定名,或一个类型别名
private JdbcType jdbcType;//对应节点的jdbcType 属性,表示的是进行映射的列的 JDBC 类型
//对应节点的 typeHandler 属性,表示的是类型处理器,它会覆盖默认的类型处理器,后面会介绍该字段的作用
private TypeHandler<?> typeHandler;
//对应节点的 resultMap 属性,该属性通过id引用了另一个<resultMap>节点定义,它负 责将结采集中的一部
//分列映射成其他关联的结果对象。 这样我们就可以通过 join方式进行关联查询,然后直接映射成多个对象,
//并同时设置这些对象之间的组合关系
private String nestedResultMapId;
private String nestedQueryId;
private Set<String> notNullColumns;
private String columnPrefix;//对应节点的 columnPrefix 属性
private List<ResultFlag> flags;///处理后的标志,标志共两个: id 和 constructor
private List<ResultMapping> composites;
private String resultSet;//对应节点的 resultSet 属性
private String foreignColumn;
private boolean lazy;///是否延迟加载,对应节点的 fetchType 属性
ResultMap
ResultMap, 每个<resultMap>节点都会被解析成一个 ResultMap 对象,其中每个节点所定义的映射关系,则使用 ResultMapping 对象表示,如下图。
ResultMap 中各个字段的含义如下:
public class ResultMap {
private Configuration configuration;//configuration对象
private String id;//resultMap的id属性
private Class<?> type;//resultMap的type属性
private List<ResultMapping> resultMappings;//除discriminator节点之外的映射关系
private List<ResultMapping> idResultMappings;//记录ID或者<constructor>中idArg的映射关系
private List<ResultMapping> constructorResultMappings;记录<constructor>标志的映射关系
private List<ResultMapping> propertyResultMappings;//记录非<constructor>标志的映射关系
private Set<String> mappedColumns;//记录所有有映射关系的columns字段
private Set<String> mappedProperties;//记录所有有映射关系的property字段
private Discriminator discriminator;//鉴别器,对应discriminator节点
private boolean hasNestedResultMaps;//是否有嵌套结果映射
private boolean hasNestedQueries;是否有嵌套查询
private Boolean autoMapping;//是否开启了自动映射
2.5 XMLStatementBuilder解析SQL语句
映射配置文件中还有一类比较重要的节点需要解析,也就是本节 将要介绍的 SQL 节点。这些 SQL 节点主要用于定义 SQL 语句,它们不再由 XMLMapperBuilder 进行解析,而是由 XMLStatementBuilder 负责进行解析。
//重点分析 :解析select、insert、update、delete节点 ----------------3-------------------
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
//解析select、insert、update、delete节点
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
//处理所有的sql语句节点并注册至configuration对象
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
//创建XMLStatementBuilder 专门用于解析sql语句节点
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//解析sql语句节点
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
增删改查sql语句对应的对象MappedStatement:
三 绑定 Mapper 接口
每个映射配置文件的命名空间可以绑定一个 Mapper 接口,井注册到 MapperRegis盯f 中。 MapperRegist:ry 以及其他相关类的实现在分析 binding 模块时介绍,这里不再重复。在 XMLMapperBuilder. bindMapperForNamespace()方法中,完成了映射配置文件与对应 Mapper 接 口的绑定,具体实现如下:
private void bindMapperForNamespace() {
//获取映射配置文件的命名空间
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
//解析命名空间对应的类型
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
//追加 namespace 前缀,并添加到 Configuration.loadedResources 集合中保存
configuration.addLoadedResource("namespace:" + namespace);
//调用 MapperRegistry .addMapper ()方法,注册 boundType 接口
configuration.addMapper(boundType);
}
}
}
}
四 总结Configuration建造过程的总结
XMLConfigBuilder,XMLMapperBuilder和XMLStatementBuilder整体层次上并没有采用建造者的流式编程风格,但是采用了建造者模式的灵魂,因为这三个建造者分别构建了Configuration对象的不同部分。
MapperBuilderAssistant builderAssistant调用经典的建造者模式
//通过builderAssistant创建缓存对象,并添加至configuration
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
//经典的建造起模式,创建一个cache对象
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
//将缓存添加至configuration,注意二级缓存以命名空间为单位进行划分
configuration.addCache(cache);
currentCache = cache;
return cache;
}