前言:
前文我们简单的回顾了 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 创建的源码分析完毕,希望可以帮助到有需要的小伙伴。
欢迎提出建议及对错误的地方指出纠正。