MyBatis初探:揭示初始化阶段的核心流程与内部机制
前言
初始化阶段主要是完成 XML 配置文件和注解配置信息的读取,创建全局单例的 Configuration 配置对象,完成各部分的初始化工作,最底层封装的 XPath 进行解析,具体的创建过程需要三个核心类来完成解析。
核心类
创建 Configuration 的核心类和作用如下:
类名 | 作用 |
---|---|
XmlConfigBuilder | 加载解析主配置文件 |
XmlMapperBuilder | 加载解析 Mapper 映射文件中非 SQL 节点部分 |
XmlStatementBuilder | 加载解析 Mapper 映射文件中 SQL 节点部分 |
三个类共同完成全局配置文件的加载解析,来构建 Configuration 配置对象。这三个类看起来并没有使用到建造者模式的流式风格,但是借鉴了建造者模式的思想,在 CacheBuilder 里面的 build 方法是典型的建造者模式加载核心配置文件来创建全局的配置对象。
其他的核心类如下:
类名 | 作用 |
---|---|
Configuration | 单例对象,存在于程序的整个生命周期,包含所有的配置信息(初始化核心就是创建该对象) |
MapperRegistry | Mapper 接口动态代理的注册中心,注册的就是 Java 接口 |
MapperProxy | 动态代理类,实现了 InvocationHandler 接口,用于为接口生成动态代理实例,实例就是 MapperProxy 类型的。 |
MapperProxyFactory | 用于生成 MapperProxy 实例 |
ResultMap | 解析映射配置文件中的 resultMap 节点,内部包含一个 ResultMapping 类型的 List,里面就封装了 id、result 等子元素 |
MappedStatement | 存储映射文件中的 insert、select 等 SQL 语句 |
SqlSource | 映射文件中的 SQL 语句会解析成 SqlSource 对象,解析 SqlSource 后得到的 SQL 语句只包含占位符,可以直接交给 DB 执行 |
Configuration
包含 MyBatis 全部的配置信息、全局单例、生命周期贯穿整个 MyBatis 的生命周期,应用级生命周期。
public class Configuration {
// 环境信息
protected Environment environment;
// 是否启用行内嵌套语句
protected boolean safeRowBoundsEnabled = false;
protected boolean safeResultHandlerEnabled = true;
// 是否启用下划线转驼峰命名的属性
protected boolean mapUnderscoreToCamelCase = false;
// 延迟加载
protected boolean aggressiveLazyLoading = true;
// 是否允许单条 SQL 返回多个数据集(取决于驱动的兼容性)default:true
protected boolean multipleResultSetsEnabled = true;
// 允许 JDBC 生成主键。需要驱动器支持。如果设为了 true,这个设置将强制使用被生成的主键,有一些驱动器不兼容不过仍然可以执行。default:false
protected boolean useGeneratedKeys = false;
protected boolean useColumnLabel = true;
// 是否开启二级缓存
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls = false;
protected boolean useActualParamName = true;
// 日志打印所有的前缀
protected String logPrefix;
protected Class<? extends Log> logImpl;
protected Class<? extends VFS> vfsImpl;
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
// 设置触发延迟加载的方法
protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[]{"equals", "clone", "hashCode", "toString"}));
protected Integer defaultStatementTimeout;
protected Integer defaultFetchSize;
// 执行器类型,有 simple、resue 及 batch
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
// 指定 MyBatis 应如何自动映射列到字段或属性
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
protected Properties variables = new Properties();
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
// 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 String databaseId;
/**
* Configuration factory class.
* Used to create Configuration for loading deserialized unread properties.
*
* @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300 (google code)</a>
*/
// 插件集合
protected Class<?> configurationFactory;
// Mapper 接口的动态代理注册中心
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
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 Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
// Mapper 文件中配置 cache 节点的二级缓存
protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
// Mapper 文件中配置的所有 resultMap 对象,key 为命名空间 + ID
protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
// Mapper 文件中配置 KeyGenerator 的 insert 和 update 节点,key 为命名空间 + ID
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");
// 加载到的所有 *Mapper.xml 文件
protected final Set<String> loadedResources = new HashSet<String>();
// Mapper 文件中配置的 SQL 元素,key 为命名空间 + ID
protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();
public Configuration(Environment environment) {
this();
this.environment = environment;
}
// 注册默认的别名
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
// 相关属性的 get/set 方法,和其他方法,省略
}
初始化过程
入口程序
public class DaoTest {
private SqlSession sqlSession;
@Before
public void before() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSession = sessionFactory.openSession();
inputStream.close();
}
}
SqlSessionFactoryBuilder#build
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
// XMLConfigBuilder#parse 方法是配置解析的主要方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 构建 XMLConfigBuilder 对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 建造者模式,XMLConfigBuilder 对象的 parse 方法就可以构造 Configuration 对象,屏蔽了所有实现细节
// 并且将返回的 Configuration 对象作为参数构造 SqlSessionFactory 对象
// SqlSessionFactory 的默认实现类 DefaultSqlSessionFactory 拿到了配置对象之后,就具备生产 SqlSession 的能力了
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.
}
}
}
// DefaultSqlSessionFactory 是 SqlSessionFactory 的默认实现
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
XMLConfigBuilder#parse
XMLConfigBuilder#parse 方法是配置解析的核心方法。
public Configuration parse() {
// 1.判断是否已经解析过,不重复解析
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 2.读取主配置文件的 Configuration 节点下面的配置信息,parseConfiguration 方法完成解析的流程
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
XMLConfigBuilder#parseConfiguration
XMLConfigBuilder#parseConfiguration 方法完成全部的配置解析主流程。解析核心配置文件的关键方法,读取节点的信息,并通过对应的方法去解析配置,解析到的配置全部会放在 Configuration 里面。
private void parseConfiguration(XNode root) {
try {
Properties settings = settingsAsPropertiess(root.evalNode("settings"));
// 解析<properties>节点
propertiesElement(root.evalNode("properties"));
// 解析<settings>节点
loadCustomVfs(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"));
// 将settings填充到configuration
settingsElement(settings);
// 解析<environments>节点
environmentsElement(root.evalNode("environments"));
// 解析<databaseIdProvider>节点
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 解析<typeHandlers>节点
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析<mappers>节点,里面会使用XMLMapperBuilder
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
我们看到这个方法的解析流程都很类似,最后面引入了 XMLMapperBuilder 解析节点,我们在前面挑一个解析 typeAliases 的看看细节,其他的就不逐个分析了,然后看 XMLMapperBuilder 解析阶段的分析。
XMLConfigBuilder#typeAliasesElement
typeAliasesElement 解析别名节点,typeAliases 有两种配置方式,如下所示:
<typeAliases>
<package name="world.xuewei.entity"/>
<typeAlias alias="Product" type="world.xuewei.entity.Product"/>
</typeAliases>
private void typeAliasesElement(XNode parent) {
if (parent != null) {
// 1. 非空才会处理,依次遍历所有节点
for (XNode child : parent.getChildren()) {
// 2. 处理 package 类型配置
if ("package".equals(child.getName())) {
// 2.1 获取包名
String typeAliasPackage = child.getStringAttribute("name");
// 2.2 注册包名,将包名放到 typeAliasRegistry 里面,里面拿到包名之后还会进一步处理
// 最后会放到 TypeAliasRegistry.TYPE_ALIASES 这个 Map 里面去
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
// 3. 处理 typeAlias 类型配置
// 3.1 获取别名
String alias = child.getStringAttribute("alias");
// 3.2 获取类名
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
// 3.3 下面的注册逻辑其实比前面注册包名要简单,注册包名要依次处理包下的类,也会调用 registerAlias 方法,
// 这里直接处理类,别名没有配置也没关系,里面会生成一个 getSimpleName 或者根据 Alias 注解去取别名
if (alias == null) {
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
// 4.其他类型直接报错
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
XMLConfigBuilder#mapperElement
XMLConfigBuilder#mapperElement 方法解析节点,这个方法包含解析 mapper 节点的主流程,但是解析的细节还看不到,解析的细节在后面的 XMLMapperBuilder#parse 里面,我们先通过这个方法看一下解析的主流程,mappers 有多种配置方式,如下所示:
<mappers>
<!-- 直接映射到相应的 mapper 文件 -->
<mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
<mapper url="xx"/>
<mapper class="yy"/>
<package name="world.xuewei.mapper"/>
</mappers>
解析配置文件的 mappers 子节点,方法主要是实现一个大体框架,按照 resource->url->class 的优先级读取配置,具体的解析细节是依赖于 XMLMapperBuilder 来实现的, XMLMapperBuilder 通过 parse 方法屏蔽了细节,内部完成解析过程。
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// 1. 节点非空,遍历子节点逐个处理,因为 mapperElement(root.evalNode("mappers")) 解析的 mappers 里面可能有多个标签
for (XNode child : parent.getChildren()) {
// 1.1 处理 package 类型的配置
if ("package".equals(child.getName())) {
// 1.2 按照包来添加,扫包之后默认会在包下找与 Java 接口名称相同的 mapper 映射文件,name 就是包名
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
// 1.3 一个一个 Mapper.xml 文件的添加,resource、url 和 class 三者是互斥的,resource 优先级最高
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
// 1.4 按照 resource 属性实例化 XMLMapperBuilder 来解析 xml 配置文件
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 1.5 解析配置,因为 XMLMapperBuilder 继承了 BaseBuilder,BaseBuilder 内部持有 Configuration 对象
// 因此 XMLMapperBuilder 解析之后直接把配置设置到 Configuration 对象
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
// 1.6 按照 url 属性实例化 XMLMapperBuilder 来解析 xml 配置文件
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) {
// 1.7 按照 class 属性实例化 XMLMapperBuilder 来解析 xml 配置文件
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
// resource、url 和 class 三者是互斥的,配置了多个或者不配置都抛出异常
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
结合代码注释和配置文件的结构,流程还是比较清晰的,这里面会将 mappers 里面的节点信息全部注册到 Configuration 所持有的 MapperRegistry 对象里面去, MapperRegistry 是 Mapper 接口动态代理的注册中心,总而言之 Configuration 里包含所有的配置信息,最后一种情况使用的是 configuration.addMapper 方法,其实前面几种情况最后也是走的这个方法,这里我们看到了主体流程,下面我们开始看解析的具体过程分析。
XMLMapperBuilder#parse
XMLMapperBuilder#parse 进入了 XMLMapperBuilder 解析 mapper 节点的详细流程,最后把 mapper 节点信息保存到 Configuration 的 MapperRegistry 里面去,并且会对 Mapper.xml 映射文件的内存进行解析,映射文件的内容是非常复杂的,我们慢慢跟进看。
public void parse() {
// 1. 首先判断是否已经加载过了,没有加载才继续加载
// loadedResources 是一个 set 集合,保存了已经加载的映射文件,如果一个配置在 mappers 里面写了两次,那么第二次就不加载了
if (!configuration.isResourceLoaded(resource)) {
// 2. 处理 mapper 子节点
// 这里使用 XPathParser 来解析 xml 文件,XPathParser 在 XMLMapperBuilder 构造方法执行的时候就已经初始化好了
configurationElement(parser.evalNode("/mapper"));
// 3. 将解析过的文件添加到已经解析过的 set 集合里面
configuration.addLoadedResource(resource);
// 4. 注册 mapper 接口
bindMapperForNamespace();
}
// 5. 处理解析失败的节点
// 把加载失败的节点重新加载一遍,因为这些节点可能在之前解析失败了,比如他们继承的节点还未加载导致
// 因此这里把失败的部分再加载一次,之前加载失败的节点会放在一个 map 里面
// 6. 处理解析失败的 ResultMap 节点
parsePendingResultMaps();
// 7. 处理解析失败的 CacheRef 节点
parsePendingChacheRefs();
// 8. 处理解析失败的 Sql 语句节点
parsePendingStatements();
}
XMLMapperBuilder#configurationElement
configurationElement 方法是解析 mapper 映射文件的主流程,我们可以看到每种类型节点的解析过程,注意第八步是解析 SQL 信息,我们前面说过,XMLMapperBuilder 解析 mapper 文件但是不解析 SQL 节点,SQL 节点是由 XmlStatementBuilder 来负责的,后面我们会看到 XmlStatementBuilder 的作用。
private void configurationElement(XNode context) {
try {
// 1. 获取 namespace 属性(对应 Java 接口的全路径名称)不能为空
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 2. 把 namespace 属性交给建造助手 builderAssistant
builderAssistant.setCurrentNamespace(namespace);
// 3. 解析 cache-ref 节点
cacheRefElement(context.evalNode("cache-ref"));
// 4. 重点:解析 cache 节点,和缓存相关
cacheElement(context.evalNode("cache"));
// 5. 解析 parameterMap 节点(已废弃)
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 6. 重点:解析 resultMap 节点
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 7. 重点:解析 SQL 节点
sqlElement(context.evalNodes("/mapper/sql"));
// 8. 重点:解析 SQL 语句,解析 select、insert、update、delete 节点
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
// 异常处理
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
XMLMapperBuilder#bindMapperForNamespace
bindMapperForNamespace 负责将 mapper 映射文件对应的 Java 接口类(这个接口类的名称就是 mapper 文件里面的 namespace)注册到 Configuration 的 MapperRegistry 里面,这代表该接口已经注册。
private void bindMapperForNamespace() {
// 1. 获取命名空间,在 bindMapperForNamespace 前面的 configurationElement 方法将 namespace 已经 set 到 builderAssistant 了,这里直接取出
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
// 1.1 通过命名空间获取 Mapper 接口的 Class 对象
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
// ignore, bound type is not required
}
if (boundType != null) {
// 1.2 是否已经注册过该 mapper 接口?
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
// 1.3 如果没有注册,将命名空间添加至 configuration.loadedResource 集合中
configuration.addLoadedResource("namespace:" + namespace);
// 1.4 将 mapper 接口添加到 mapper 注册中心
configuration.addMapper(boundType);
}
}
}
}
XMLMapperBuilder#parsePendingResultMaps
处理之前加载失败的 resultMap。
private void parsePendingResultMaps() {
// 1. 之前加载失败的 resultMap 会保存在这个集合中,这里需要做的就是遍历这个集合并处理
Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps();
synchronized (incompleteResultMaps) {
Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
while (iter.hasNext()) {
try {
// 2. 遍历并且调用 resolve 处理即可,如果异常直接忽略,因为最后还会重试的
iter.next().resolve();
// 3. 处理完就从集合移除
iter.remove();
} catch (IncompleteElementException e) {
// ResultMap is still missing a resource...
}
}
}
}
parsePendingChacheRefs(),parsePendingStatements() 和 parsePendingResultMaps 方法是类似的,就不多解析了,这一步我们主要是梳理 XMLMapperBuilder#parse 方法,并对里面几个关键的方法做了大概的跟踪,但是底层的实现逻辑我们暂时不跟进,否则内容太多。
XMLMapperBuilder#buildStatementFromContext
前面的 XMLMapperBuilder 我们跟了一部分源码,看到了 XMLMapperBuilder 解析 mapper 映射文件的主体流程,现在我们看 XmlStatementBuilder 是如何解析 mapper 中的 SQL 语句的,解析 SQL 语句属于解析 mapper 文件的一部分,我们 XMLMapperBuilder#configurationElement 的最后一个流程方法 buildStatementFromContext 里面跟进去,就可以看到他的身影,我们跟进去看看主流程。
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
// 解析 SQL 语句节点并注册到 Configuration 对象
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// XMLStatementBuilder 和 XMLMapperBuilder 一样都是继承自 BaseBuilder,BaseBuilder 持有 Configuration 对象,因此
//直接解析,然后将配置信息 set 进去即可
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
// 如果解析异常,就把对象添加到未完成的 Map 集合里面
configuration.addIncompleteStatement(statementParser);
}
}
}
XMLStatementBuilder#parseStatementNode
XMLStatementBuilder#parseStatementNode 是解析 SQL 节点的核心方法,该方法是解析 SQL 节点的主流程,包括四种 SQL 节点,解析完毕之后会将 SQL 语句信息封装之后,注册到 Configuration 对象里面的 mappedStatements 集合里面去。
public void parseStatementNode() {
// 1. 获取 sql 节点的 id
String id = context.getStringAttribute("id");
// 2. 获取 databaseId
String databaseId = context.getStringAttribute("databaseId");
// 不符合就返回
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
// 3. 获取 sql 节点的各种属性
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
// 4. 根据 sql 节点的名称获取操作的类型 SqlCommandType(INSERT, UPDATE, DELETE, SELECT)
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
// 5. 获取操作的各种配置,比如缓存,resultOrdered 之类的
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 6. flushCache 默认值是 !isSelect,如果是查询语句,没有配置 flushCache 就是 false,不是查询语句,没有配置 flushCache 就是 true
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
// 7. 在解析 sql 语句之前先解析 <include> 节点
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
// 8. 在解析 sql 语句之前,处理 <selectKey> 子节点,并在 xml 节点中删除
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
// 9. 解析 sql 语句是解析 mapper.xml 的核心,实例化 sqlSource,使用 sqlSource 封装 sql 语句
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
// 10. 获取 resultSets 属性
String resultSets = context.getStringAttribute("resultSets");
// 11. 获取主键信息 keyProperty
String keyProperty = context.getStringAttribute("keyProperty");
// 12. 获取主键信息 keyColumn
String keyColumn = context.getStringAttribute("keyColumn");
// 13. 根据 <selectKey> 获取对应的 SelectKeyGenerator 的 id
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
// 14. 获取 keyGenerator 对象,如果是 insert 类型的 sql 语句,会使用 KeyGenerator 接口获取数据库生产的 id;
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? new Jdbc3KeyGenerator() : new NoKeyGenerator();
}
// 15. 通过 MapperBuilderAssistant 类型的 builderAssistant 实例化 MappedStatement,并注册至 configuration 对象
// 相当于自身主要复杂属性的解析和读取,构造对象的过程交给助手来做
// 注意前面解析了 mapper 节点之后是注册到 mapperRegistry,那是注册 mapper 节点信息,注册的实际上是对应的 java 接口
// 这里是注册到 MappedStatement,注册的是 sql 语句信息,但是二者都是保存在 Configuration 对象里面的Map集合
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
MapperBuilderAssistant#addMappedStatement
在 XMLStatementBuilder#parseStatementNode 的最后一步,通过调用 builderAssistant.addMappedStatement 方法来完成 MappedStatement 对象实例化和注册到 Configuration 对象的 mappedStatements 集合,sql 语句解析之后,对应到的 java 的数据结构类是 MappedStatement,它是保存 sql 语句的数据结构。sql 语句的节点的全部配置,都能够在 MappedStatement 中找到对应的属性。
到此 XmlConfigBuilder、XmlMapperBuilder 和 XmlStatementBuilder 三者分工协作,依次完成了主配置文件、mapper 映射文件(不含 sql 信息)和 mapper 文件 sql 节点信息配置的加载,最终都把这些信息放到了 Configuration 这个大配置对象里面去了,到此处虽然还有很多细节没有分析,但是基本上初始化的流程已经看得出个大概了。 下面是代码细节:
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {
// 1. 确保 Cache-ref 节点已经被解析过了
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 2. 典型的建造者模式,创建 MappedStatement 对象
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
// 3. build 方法创建 MappedStatement 对象
MappedStatement statement = statementBuilder.build();
// 4. 添加 MappedStatement 对象到全局 Configuration 配置对象的对应 Map 中
configuration.addMappedStatement(statement);
return statement;
}
- 点赞
- 收藏
- 关注作者
评论(0)