MyBatis 源码分析--SqlSessionFactory

前言:

前文我们简单的回顾了 MyBatis 的基本概念,有聊到核心组件,工作流程等,本篇我们开始深入剖析 MyBatis 的核心源码,欢迎大家持续关注。

Mybatis 知识传送门

初识 MyBatis 【MyBatis 核心概念】

MyBatis 源码解析

为了方便我们分析 MyBatis 源码,我们先写一段简单的小程序,来辅助我们分析 MyBatis 源码,如下:

public class MyBatisTest {
    @Test
    public void test() throws IOException {
        //读取配置文件
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        //创建 SqlSessionFactoryBuilder 对象
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //通过 SqlSessionBuilder 对象 解析 mybatis-config.xml 文件 构建一个SqlSessionFactory 
        SqlSessionFactory sqlSessionFactory = builder.build(is);
        //通过SqlSessionFactory构建一个SqlSession
        SqlSession session = sqlSessionFactory.openSession();
        //通过SqlSession 获取 Mapper 实例
        UserMapper userMapper = session.getMapper(UserMapper.class);
        //获取数据
		List<User> users = userMapper.findAll();
        //打印输出
        for (User user : users) {
            System.out.println(user);
        }
        //关闭资源
        session.close();
        is.close();
    }
}

在分析创建 SqlSessionFactory 源码之前,先分享一个代码流程简图,帮助梳理源码脉络。

在这里插入图片描述

获取 SqlSessionFactory 源码分析

SqlSessionFactory 工厂是通过 SqlSessionFactoryBuilder#build 方法获取的,build 方法会先创建一个 XMLConfigBuilder ,XMLConfigBuilder 会去解析 XML 文件,生成一个 SqlSessionFactory 方返回,我们重点关注 var5 = this.build(parser.parse()) 这行代码。

//org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.Reader, java.lang.String, java.util.Properties)
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
	//SqlSessionFactory
	SqlSessionFactory var5;
	try {
		//根据入参创建 xml 配置生成器   
		XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
		//parser.parse() :解析 xml 文件  重点关注
		//创建 SqlSessionFactory 并返回
		var5 = this.build(parser.parse());
	} catch (Exception var14) {
		throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
	} finally {
		ErrorContext.instance().reset();

		try {
			reader.close();
		} catch (IOException var13) {
		}

	}

	return var5;
}

XMLConfigBuilder#parse 方法源码分析

XMLConfigBuilder#parse 方法会先判断当前 XML 文件是否解析过,如果解析过就抛出异常,没有解析过就正常开始解析文件 。

//org.apache.ibatis.builder.xml.XMLConfigBuilder#parse
public Configuration parse() {
	//判断是否被解析过
	if (this.parsed) {
		//解析过再次被解析就要抛出异常了
		throw new BuilderException("Each XMLConfigBuilder can only be used once.");
	} else {
		//标记为已经解析
		this.parsed = true;
		//解析配置 xml 文件  重点关注
		this.parseConfiguration(this.parser.evalNode("/configuration"));
		//返回 Configuration
		return this.configuration;
	}
}

XMLConfigBuilder#parseConfiguration 方法源码分析

XMLConfigBuilder#parseConfiguration 方法会解析 XML 文件的各种属性,最后会去加载 Mapper 文件,也就是我们写的 SQL 文件。

//org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
private void parseConfiguration(XNode root) {
	try {
		//解析 properties 节点
		this.propertiesElement(root.evalNode("properties"));
		//解析 Setting 节点
		Properties settings = this.settingsAsProperties(root.evalNode("settings"));
		//加载自定义的 VFS  VFS 主要用来加载容器内的各种资源 例如 jar 或者 class文件
		this.loadCustomVfs(settings);
		//加载自定义的日志实现
		this.loadCustomLogImpl(settings);
		//解析 typeAliases 
		this.typeAliasesElement(root.evalNode("typeAliases"));
		//加载 插件 
		this.pluginElement(root.evalNode("plugins"));
		//加载对象工厂
		this.objectFactoryElement(root.evalNode("objectFactory"));
		//加载对象包装工厂
		this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
		//加载反射工厂
		this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
		//把 settings 赋值给 confiuration
		this.settingsElement(settings);
		//加载环境配置
		this.environmentsElement(root.evalNode("environments"));
		//加载数据库标识
		this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
		//加载 typeHandler
		this.typeHandlerElement(root.evalNode("typeHandlers"));
		//加载 mapper 文件 也就是我们写的 sql 文件  重点关注
		this.mapperElement(root.evalNode("mappers"));
	} catch (Exception var3) {
		throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
	}
}

XMLConfigBuilder#mapperElement 方法源码分析

XMLConfigBuilder#mapperElement 方法会解析 SQL 文件的配置方式,在 Mappers 下 Mapper 文件有四种配置方式,分别是 package、resource、url、class,mapperElement 方法对其进行了区分处理,我们重点关注 mapperParser.parse() 方法。

//org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement
private void mapperElement(XNode parent) throws Exception {
	if (parent != null) {
		//获取 mappers 的子节点 也就是 mapper 节点 迭代遍历
		Iterator var2 = parent.getChildren().iterator();

		while(true) {
			while(var2.hasNext()) {
				//Mappers 的子节点  只能是 package 节点 和 mapper 节点
				XNode child = (XNode)var2.next();
				String resource;
				//是否是 packge 节点
				if ("package".equals(child.getName())) {
					//获取需要扫描的包路径 
					resource = child.getStringAttribute("name");
					//加入到 configuration 中
					this.configuration.addMappers(resource);
				} else {
					//获取 resource、url、class这三个属性  只能有一个生效
					//获取 resource 
					resource = child.getStringAttribute("resource");
					//获取 url  
					String url = child.getStringAttribute("url");
					//获取 mapperClass
					String mapperClass = child.getStringAttribute("class");
					//xml 解析器
					XMLMapperBuilder mapperParser;
					InputStream inputStream;
					if (resource != null && url == null && mapperClass == null) {
						//解析 resource 类路径
						ErrorContext.instance().resource(resource);
						inputStream = Resources.getResourceAsStream(resource);
						mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
						mapperParser.parse();
					} else if (resource == null && url != null && mapperClass == null) {
						//解析 url 绝对url路径
						ErrorContext.instance().resource(url);
						inputStream = Resources.getUrlAsStream(url);
						mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
						mapperParser.parse();
					} else {
						if (resource != null || url != null || mapperClass == null) {
							throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
						}
						//解析 mapperclass  类名
						Class<?> mapperInterface = Resources.classForName(mapperClass);
						//加入到 configuration 中
						this.configuration.addMapper(mapperInterface);
					}
				}
			}

			return;
		}
	}
}

Configuration#addMappers 方法源码分析

addMappers 方法会把包下面的所有 Mapper 解析出来,加入到一个 knownMappers 的 HashMap中。

//org.apache.ibatis.session.Configuration#addMappers
public void addMappers(String packageName) {
	this.mapperRegistry.addMappers(packageName);
}


//org.apache.ibatis.binding.MapperRegistry#addMappers(java.lang.String)
public void addMappers(String packageName) {
	this.addMappers(packageName, Object.class);
}


//org.apache.ibatis.binding.MapperRegistry#addMappers
public void addMappers(String packageName, Class<?> superType) {
	//处理 package 下的 所有 mapper 
	ResolverUtil<Class<?>> resolverUtil = new ResolverUtil();
	resolverUtil.find(new IsA(superType), packageName);
	Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
	Iterator var5 = mapperSet.iterator();
	//循环遍历每个 Mapper
	while(var5.hasNext()) {
		Class<?> mapperClass = (Class)var5.next();
		//加入到 knownMappers Map 中
		this.addMapper(mapperClass);
	}

}


//org.apache.ibatis.binding.MapperRegistry#addMapper
public <T> void addMapper(Class<T> type) {
	if (type.isInterface()) {
		//判断 Mapper 是否存在
		if (this.hasMapper(type)) {
			throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
		}

		boolean loadCompleted = false;

		try {
			//将 Mapper 的 Class 做为 key value 为 代理对象工厂
			this.knownMappers.put(type, new MapperProxyFactory(type));
			//解析注解方式的 Mapper
			MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
			parser.parse();
			loadCompleted = true;
		} finally {
			if (!loadCompleted) {
				//失败 从 Mapper 中移出
				this.knownMappers.remove(type);
			}

		}
	}

}

XMLMapperBuilder#parse 方法源码分析

XMLMapperBuilder#parse 方法主要作用就是解析 Mapper 文件,加载 Resouce,创建 MapperProxyFactory,我们重点分析解析 Mapper 标签部分。

//org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
public void parse() {
	//是否加载过
	if (!this.configuration.isResourceLoaded(this.resource)) {
		//解析 mapper 标签
		this.configurationElement(this.parser.evalNode("/mapper"));
		//加载 resource
		this.configuration.addLoadedResource(this.resource);
		//为名称空间绑定映射器 其实就是创建 MapperProxyFactory
		this.bindMapperForNamespace();
	}
	//结果集
	this.parsePendingResultMaps();
	//缓存
	this.parsePendingCacheRefs();
	//待处理的sql
	this.parsePendingStatements();
}

XMLMapperBuilder#configurationElement 方法源码分析

XMLMapperBuilder#configurationElement 方法已经开始解析 Mapper 文件了,会对 Mapper 文件中的一些标签进行解析,并去构建 SQL 语句,我们重点关注 this.buildStatementFromContext(context.evalNodes(“select|insert|update|delete”))。

//org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
private void configurationElement(XNode context) {
	try {
		//获取 namespace
		String namespace = context.getStringAttribute("namespace");
		if (namespace != null && !namespace.isEmpty()) {
			//设置 namespace
			this.builderAssistant.setCurrentNamespace(namespace);
			//解析 cache-ref 标签
			this.cacheRefElement(context.evalNode("cache-ref"));
			//解析 cache 标签
			this.cacheElement(context.evalNode("cache"));
			//解析映射参数 parameterMap 
			this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
			//解析结果集 resultMap
			this.resultMapElements(context.evalNodes("/mapper/resultMap"));
			//解析 sql
			this.sqlElement(context.evalNodes("/mapper/sql"));
			//构建 crud 语句 重点关注
			this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
		} else {
			throw new BuilderException("Mapper's namespace cannot be empty");
		}
	} catch (Exception var3) {
		throw new BuilderException("Error parsing Mapper XML. The XML location is '" + this.resource + "'. Cause: " + var3, var3);
	}
}

XMLMapperBuilder#buildStatementFromContext 方法源码分析

XMLMapperBuilder#buildStatementFromContext 方法包装调用了 XMLMapperBuilder#buildStatementFromContext 方法,主要做的事情就是迭代解析构建 SQL 语句。

//org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>)
private void buildStatementFromContext(List<XNode> list) {
	if (this.configuration.getDatabaseId() != null) {
		//database 不为空 构建 sql 语句
		this.buildStatementFromContext(list, this.configuration.getDatabaseId());
	}
	//构建 sql 语句
	this.buildStatementFromContext(list, (String)null);
}


//org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>, java.lang.String)
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
	//迭代遍历 sql 语句
	Iterator var3 = list.iterator();

	while(var3.hasNext()) {
		XNode context = (XNode)var3.next();
		//创建 XMLStatementBuilder 解析器
		XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);

		try {
			//解析 sql  重点关注
			statementParser.parseStatementNode();
		} catch (IncompleteElementException var7) {
			this.configuration.addIncompleteStatement(statementParser);
		}
	}

}

XMLStatementBuilder#parseStatementNode 方法源码分析

XMLStatementBuilder#parseStatementNode 方法创建了 SqlSource ,并获取一系列的属性,最后封装成了 MappedStatement 对象。

//org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
public void parseStatementNode() {
	//获取 id 属性
	String id = this.context.getStringAttribute("id");
	//获取 database id
	String databaseId = this.context.getStringAttribute("databaseId");
	//启用的数据库和sql节点配置的是否匹配
	if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
		//匹配
		//获取 nodeName
		String nodeName = this.context.getNode().getNodeName();
		//获取 sql 命令类型
		SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
		//是否是 select 类型
		boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
		//是否需要刷新本地缓存 和 二级缓存
		boolean flushCache = this.context.getBooleanAttribute("flushCache", !isSelect);
		//是否需要使用二级缓存
		boolean useCache = this.context.getBooleanAttribute("useCache", isSelect);
		//这个设置仅针对嵌套结果 select 语句 如果为 true 将会假设包含了嵌套结果集或是分组 当返回一个主结果行时 就不会产生对前面结果集的引用 这就使得在获取嵌套结果集的时候不至于内存不够用 默认值:false。
		boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", false);
		//xml 转换其
		XMLIncludeTransformer includeParser = new XMLIncludeTransformer(this.configuration, this.builderAssistant);
		//适用到 node
		includeParser.applyIncludes(this.context.getNode());
		//获取参数类型
		String parameterType = this.context.getStringAttribute("parameterType");
		//解析参数类型的 class
		Class<?> parameterTypeClass = this.resolveClass(parameterType);
		//获取 long 属性
		String lang = this.context.getStringAttribute("lang");
		//动态sql 语言驱动器
		LanguageDriver langDriver = this.getLanguageDriver(lang);
		//处理  selectKey节点  当数据表中主键设计为自增 可能会存在业务需要在插入后获取到主键 这时候就需要使用 selectKey 节点 解析完毕后删除 selectKey 节点
		this.processSelectKeyNodes(id, parameterTypeClass, langDriver);
		//拼接id
		String keyStatementId = id + "!selectKey";
		//名称空间+.+keyStatementId
		keyStatementId = this.builderAssistant.applyCurrentNamespace(keyStatementId, true);
		//获取 keyGenerator
		Object keyGenerator;
		if (this.configuration.hasKeyGenerator(keyStatementId)) {
			keyGenerator = this.configuration.getKeyGenerator(keyStatementId);
		} else {
			keyGenerator = this.context.getBooleanAttribute("useGeneratedKeys", this.configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
		}
		//创建  SqlSource 这里面有是否动态 sql 的判断
		SqlSource sqlSource = langDriver.createSqlSource(this.configuration, this.context, parameterTypeClass);
		//获取 StatementType  STATEMENT:普通语句    PREPARED:预处理     CALLABLE:存储过程
		StatementType statementType = StatementType.valueOf(this.context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
		//获取 fetchSize
		Integer fetchSize = this.context.getIntAttribute("fetchSize");
		//获取 timeout
		Integer timeout = this.context.getIntAttribute("timeout");
		//获取入参 parameterMap
		String parameterMap = this.context.getStringAttribute("parameterMap");
		//获取 resultType
		String resultType = this.context.getStringAttribute("resultType");
		//获取 resultType 的类型
		Class<?> resultTypeClass = this.resolveClass(resultType);
		//获取 resultMap
		String resultMap = this.context.getStringAttribute("resultMap");
		//获取 resultSetType
		String resultSetType = this.context.getStringAttribute("resultSetType");
		//获取 ResultSetType
		ResultSetType resultSetTypeEnum = this.resolveResultSetType(resultSetType);
		if (resultSetTypeEnum == null) {
			//获取默认的 ResultSetType
			resultSetTypeEnum = this.configuration.getDefaultResultSetType();
		}
		//获取 keyProperty
		String keyProperty = this.context.getStringAttribute("keyProperty");
		//获取 keyColumn
		String keyColumn = this.context.getStringAttribute("keyColumn");
		//获取 resultSets
		String resultSets = this.context.getStringAttribute("resultSets");
		//创建 SQL 对应的 MappedStatement 对象
		this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
	}
}

XMLLanguageDriver#createSqlSource 方法源码分析

XMLLanguageDriver#createSqlSource 方法中有对动态 SQL 的处理 ,对于动态 SQL 和普通 SQL 会有不同的处理。

  • RawSqlSource : 存储的是只有 #{} 或者没有标签的纯文本SQL信息。
  • DynamicSqlSource : 存储的是写有 ${} 或者具有动态SQL标签的SQL信息。
  • StaticSqlSource : 是DynamicSqlSource和RawSqlSource解析为BoundSql的一个中间态对象类型。
  • BoundSql:用于生成我们最终执行的SQL语句,属性包括参数值、映射关系、以及SQL(带问号的)。
//org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource(org.apache.ibatis.session.Configuration, org.apache.ibatis.parsing.XNode, java.lang.Class<?>)
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
	//创建 XMLScriptBuilder 解析 SQL
	XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
	return builder.parseScriptNode();
}


//org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode
public SqlSource parseScriptNode() {
	//解析动态标签 
	MixedSqlNode rootSqlNode = this.parseDynamicTags(this.context);
	Object sqlSource;
	//是否是动态 sql
	if (this.isDynamic) {
		//创建动态 SQL DynamicSqlSource 
		sqlSource = new DynamicSqlSource(this.configuration, rootSqlNode);
	} else {
		//普通 SQL RawSqlSource
		sqlSource = new RawSqlSource(this.configuration, rootSqlNode, this.parameterType);
	}

	return (SqlSource)sqlSource;
}


//org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseDynamicTags
protected MixedSqlNode parseDynamicTags(XNode node) {
	List<SqlNode> contents = new ArrayList();
	NodeList children = node.getNode().getChildNodes();

	for(int i = 0; i < children.getLength(); ++i) {
		XNode child = node.newXNode(children.item(i));
		String nodeName;
		if (child.getNode().getNodeType() != 4 && child.getNode().getNodeType() != 3) {
			if (child.getNode().getNodeType() == 1) {
				nodeName = child.getNode().getNodeName();
				XMLScriptBuilder.NodeHandler handler = (XMLScriptBuilder.NodeHandler)this.nodeHandlerMap.get(nodeName);
				if (handler == null) {
					throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
				}

				handler.handleNode(child, contents);
				this.isDynamic = true;
			}
		} else {
			nodeName = child.getStringBody("");
			//重点关注这里 将节点封装成 TextSqlNode
			TextSqlNode textSqlNode = new TextSqlNode(nodeName);
			// textSqlNode.isDynamic() 判断是否是动态的
			if (textSqlNode.isDynamic()) {
				contents.add(textSqlNode);
				//设置为 是
				this.isDynamic = true;
			} else {
				contents.add(new StaticTextSqlNode(nodeName));
			}
		}
	}

	return new MixedSqlNode(contents);
}


//org.apache.ibatis.scripting.xmltags.TextSqlNode#isDynamic
public boolean isDynamic() {
	//创建一个 DynamicCheckerTokenParser
	TextSqlNode.DynamicCheckerTokenParser checker = new TextSqlNode.DynamicCheckerTokenParser();
	//创建一个以 ${ 为开始和以 } 为结尾的解析器 DynamicCheckerTokenParser 
	GenericTokenParser parser = this.createParser(checker);
	//解析
	parser.parse(this.text);
	//解析成功 返回 
	return checker.isDynamic();
}

//org.apache.ibatis.scripting.xmltags.TextSqlNode#createParser
private GenericTokenParser createParser(TokenHandler handler) {
	return new GenericTokenParser("${", "}", handler);
}

createSqlSource 源码分析完毕,我们继续分析创建 SQL 对应的 MappedStatement 对象的源码。

MapperBuilderAssistant#addMappedStatement 方法源码分析

MapperBuilderAssistant#addMappedStatement 方法的主要作用就是将 MappedStatement 加入 mappedStatements 集合中。

//org.apache.ibatis.builder.MapperBuilderAssistant#addMappedStatement
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) {
	if (this.unresolvedCacheRef) {
		throw new IncompleteElementException("Cache-ref not yet resolved");
	} else {
		//获取 名称空间
		id = this.applyCurrentNamespace(id, false);
		//是否是 select 类型
		boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
		//创建 statementBuilder
		org.apache.ibatis.mapping.MappedStatement.Builder statementBuilder = (new org.apache.ibatis.mapping.MappedStatement.Builder(this.configuration, id, sqlSource, sqlCommandType)).resource(this.resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(this.getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType).flushCacheRequired((Boolean)this.valueOrDefault(flushCache, !isSelect)).useCache((Boolean)this.valueOrDefault(useCache, isSelect)).cache(this.currentCache);
		ParameterMap statementParameterMap = this.getStatementParameterMap(parameterMap, parameterType, id);
		if (statementParameterMap != null) {
			//给 mappedStatement 赋值 parameterMap
			statementBuilder.parameterMap(statementParameterMap);
		}

		MappedStatement statement = statementBuilder.build();
		//将 MappedStatement 加入 mappedStatements 集合中
		this.configuration.addMappedStatement(statement);
		return statement;
	}
}


//org.apache.ibatis.session.Configuration#addMappedStatement
public void addMappedStatement(MappedStatement ms) {
	//将 MappedStatement 加入 mappedStatements 集合中
	this.mappedStatements.put(ms.getId(), ms);
}

this.build(parser.parse()) 源码分析

经过以上步骤,Configuration 已经加载完毕,调用SqlSessionFactoryBuilder#build 方法创建 SqlSessionFactory 。

//org.apache.ibatis.session.SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)
public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
 }

至此 SqlSessionFactory 创建的源码分析完毕,希望可以帮助到有需要的小伙伴。

欢迎提出建议及对错误的地方指出纠正。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/729333.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深度学习500问——Chapter12:网络搭建及训练(3)

文章目录 12.3.5 Caffe有哪些接口 12.4 网络搭建有什么原则 12.4.1 新手原则 12.4.2 深度优先原则 12.4.3 卷积核size一般为奇数 12.4.4 卷积核不是越大越好 12.5 有哪些经典的网络模型值得我们去学习的 12.6 网络训练有哪些技巧 12.6.1 合适的数据集 12.6.2 合适的预…

【数据库】数据库脚本编写规范(Word原件)

编写本文档的目的是保证在开发过程中产出高效、格式统一、易阅读、易维护的SQL代码。 1 编写目的 2 SQL书写规范 3 SQL编写原则 软件全套资料获取进主页或者本文末个人名片直接获取。

[图解]企业应用架构模式2024新译本讲解15-行数据入口

1 00:00:01,060 --> 00:00:02,770 数据算完了 2 00:00:03,070 --> 00:00:07,720 接下来就是我们这一节的主要内容了 3 00:00:08,500 --> 00:00:13,630 应用服务调用第三方的&#xff0c;Email 4 00:00:13,640 --> 00:00:18,280 包括集成应用的接口来发Email 5 …

Springboot获取resources中的文件

1.Springboot以文件的形式获取resources中的文件 import com.google.gson.JsonIOException; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonSyntaxException; import org.springframework.util.ResourceUtils; import j…

【Linux】进程信号2——阻塞信号,捕捉信号

1.阻塞信号 1.1. 信号其他相关常见概念 在开始内容之前&#xff0c;先介绍一些信号的专业名词&#xff1a; 实际执行信号的处理动作称为信号递达&#xff08;Delivery&#xff09;信号从产生到递达之间的状态&#xff0c;称为信号未决&#xff08;Pending&#xff09;&#…

Swift Combine — zip和combineLatest的理解与使用

Publisher 上还有一些其他的操作&#xff0c;比如 zip 和 combineLatest&#xff0c;能让我们在时序上对控制多个 Publisher 的结果进行类似 and 和 or 的合并&#xff0c;它们在构建复杂 Publisher 逻辑时也十分有用。 zip Publisher 中的 zip 和 Sequence 的 zip 相类似&am…

【备考指南】CDA Level Ⅰ 最全备考攻略

很多考生朋友在报名前后&#xff0c;一直不知道需要怎么备考&#xff0c;这里给大家盘点一下最全的备考攻略&#xff0c;希望对你有用&#xff1a; 1、需要准备好之后再报名吗&#xff1f; 不需要&#xff0c;CDA认证考试是报名后自行预约考试的&#xff0c;您可以先报名同时…

qml:一个基础的界面设计

文章目录 文章说明效果图重要代码说明组件矩形卡片窗口最大化后组件全部居中菜单栏Repeater实现重复8行图片加载直接加载图片文本转图片FluentUI中可供选择的图标 文章说明 qt6.5.3 qml写的一个界面配置设计软件&#xff0c;目前不含任何c代码&#xff0c;纯qml。windoms风格的…

WebStorm 配置 PlantUML

1. 安装 PlantUML 插件 在 WebStorm 插件市场搜索 PlantUML Integration 并安装&#xff0c;重启 WebStorm 使插件生效。 2. 安装 Graphviz PlantUML 需要 Graphviz 来生成图形。使用 Homebrew 安装 Graphviz&#xff1a; 打开终端&#xff08;Terminal&#xff09;。确保你…

mac 常用工具命令集合

Iterm2 Command T&#xff1a;新建标签 Command W&#xff1a;关闭当前标签 Command ← →&#xff1a;在标签之间切换 Control U&#xff1a;清除当前行 Control A&#xff1a;跳转到行首 Control E&#xff1a;跳转到行尾 Command F&#xff1a;查找 Command …

数据结构5---矩阵和广义表

一、矩阵的压缩存储 特殊矩阵:矩阵中很多值相同的元素并且它们的分布有一定的规律。 稀疏矩阵:矩阵中有很多零元素。压缩存储的基本思想是: (1)为多个值相同的元素只分配一个存储空间; (2)对零元素不分配存储空间。 1、特殊矩阵的压缩存储 &#xff08;1&#xff09;对称矩…

云渲染可以渲染SketchUp吗?

最近有很多人在问&#xff0c;云渲染可以渲染sketchup吗&#xff1f;答案是可以的&#xff0c;不过只有两三家支持&#xff0c;大部分云渲染是还是不支持的&#xff0c;今天就给大家介绍国内最新支持sketchup渲染的云渲染——炫云云渲染的使用方法。 炫云云渲染目前支持sketchu…

C++开发基础之频繁使用`std::endl`可能导致性能问题

前言 你是否曾经注意过这个问题&#xff0c;频繁使用std::endl可能导致性能问题。在C开发中&#xff0c;许多开发者习惯于使用std::endl来换行输出并刷新缓冲区。然而&#xff0c;这种习惯性操作可能会在高频率输出场景中带来显著的性能瓶颈。接下来&#xff0c;我们将深入探讨…

多模态大模型时代下的文档图像智能分析与处理

0. 前言 随着人工智能技术的不断发展&#xff0c;尤其是深度学习技术的广泛应用&#xff0c;多模态数据处理和大模型训练已成为当下研究的热点之一&#xff0c;这些技术也为文档图像智能处理和分析领域带来了新的发展机遇。 多模态大模型时代下的文档图像智能分析与处理的研究…

怎么使用Consul当配置中心和动态刷新配置

一、背景 由于Eureka官方已经正式宣布&#xff0c;自2.0起不再维护该项目&#xff0c;如果需要使用2.x&#xff0c;自行承担风险。 项目之前使用的Eureka&#xff0c;现在不维护了则需要寻找替代方案。现在Spring Cloud官方推荐Spring Cloud Consul替换Eureka作为注册中心&…

网络协议安全:TCP/IP协议栈的安全问题和解决方案

「作者简介」:北京冬奥会网络安全中国代表队,CSDN Top100,就职奇安信多年,以实战工作为基础对安全知识体系进行总结与归纳,著作适用于快速入门的 《网络安全自学教程》,内容涵盖Web安全、系统安全等12个知识域的一百多个知识点,持续更新。 这一章节我们需要知道TCP/IP每…

openresty(Nginx) 301重定向域名 http访问强制使用https

1 访问http 2 修改配置访问 server {listen 80;server_name example.cn;return 301 https://$server_name$request_uri;access_log /data/logs/czgzzfjgsup_access.log access;error_log /data/logs/czgzzfjg_error.log error;#location / {root /usr/local/open…

领域驱动设计(DDD)微服务架构模式总结

part1. Domain Driven Design(Strategic Design,Tactical Design) Top Down focus on business or activityy domain Ubiquitous Language:统一语言 Tactical Design Tools&#xff1a;战术性设计工具 Implementing Domain Driven Design(Event storming,DDD in code) DDD总结…

大模型基础——从零实现一个Transformer(4)

大模型基础——从零实现一个Transformer(1)-CSDN博客 大模型基础——从零实现一个Transformer(2)-CSDN博客 大模型基础——从零实现一个Transformer(3)-CSDN博客 一、前言 上一篇文章已经把Encoder模块的单个EncodeBlock已经实现了 本文我们继续了解Transformer中剩下的其他…

Flutter【组件】富文本组件

简介 flutter 富文本组件。 github地址&#xff1a; https://github.com/ThinkerJack/jac_uikit 使用方式 运行 flutter pub add jac_uikit组件文档 使用方式&#xff1a; HighlightedTextWidget.builder(text: "全部文案包含高亮文案测试用",highlights: [Hig…