前言:
前文我们简单的回顾了 MyBatis 的基本概念,有聊到核心组件,工作流程等,本篇我们开始深入剖析 MyBatis 的核心源码,欢迎大家持续关注。
Mybatis 知识传送门
初识 MyBatis 【MyBatis 核心概念】
MyBatis 源码解析
为了方便我们分析 MyBatis 源码,我们先写一段简单的小程序,来辅助我们分析 MyBatis 源码,如下:
public class MyBatisTest {@Testpublic 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构建一个SqlSessionSqlSession 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) {//SqlSessionFactorySqlSessionFactory 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"));//返回 Configurationreturn 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 赋值给 confiurationthis.settingsElement(settings);//加载环境配置this.environmentsElement(root.evalNode("environments"));//加载数据库标识this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));//加载 typeHandlerthis.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");//获取 mapperClassString 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();//循环遍历每个 Mapperwhile(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));//解析注解方式的 MapperMapperAnnotationBuilder 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"));//加载 resourcethis.configuration.addLoadedResource(this.resource);//为名称空间绑定映射器 其实就是创建 MapperProxyFactorythis.bindMapperForNamespace();}//结果集this.parsePendingResultMaps();//缓存this.parsePendingCacheRefs();//待处理的sqlthis.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 {//获取 namespaceString namespace = context.getStringAttribute("namespace");if (namespace != null && !namespace.isEmpty()) {//设置 namespacethis.builderAssistant.setCurrentNamespace(namespace);//解析 cache-ref 标签this.cacheRefElement(context.evalNode("cache-ref"));//解析 cache 标签this.cacheElement(context.evalNode("cache"));//解析映射参数 parameterMap this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));//解析结果集 resultMapthis.resultMapElements(context.evalNodes("/mapper/resultMap"));//解析 sqlthis.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 idString databaseId = this.context.getStringAttribute("databaseId");//启用的数据库和sql节点配置的是否匹配if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {//匹配//获取 nodeNameString 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);//适用到 nodeincludeParser.applyIncludes(this.context.getNode());//获取参数类型String parameterType = this.context.getStringAttribute("parameterType");//解析参数类型的 classClass<?> 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);//拼接idString keyStatementId = id + "!selectKey";//名称空间+.+keyStatementIdkeyStatementId = this.builderAssistant.applyCurrentNamespace(keyStatementId, true);//获取 keyGeneratorObject 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()));//获取 fetchSizeInteger fetchSize = this.context.getIntAttribute("fetchSize");//获取 timeoutInteger timeout = this.context.getIntAttribute("timeout");//获取入参 parameterMapString parameterMap = this.context.getStringAttribute("parameterMap");//获取 resultTypeString resultType = this.context.getStringAttribute("resultType");//获取 resultType 的类型Class<?> resultTypeClass = this.resolveClass(resultType);//获取 resultMapString resultMap = this.context.getStringAttribute("resultMap");//获取 resultSetTypeString resultSetType = this.context.getStringAttribute("resultSetType");//获取 ResultSetTypeResultSetType resultSetTypeEnum = this.resolveResultSetType(resultSetType);if (resultSetTypeEnum == null) {//获取默认的 ResultSetTyperesultSetTypeEnum = this.configuration.getDefaultResultSetType();}//获取 keyPropertyString keyProperty = this.context.getStringAttribute("keyProperty");//获取 keyColumnString keyColumn = this.context.getStringAttribute("keyColumn");//获取 resultSetsString 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 解析 SQLXMLScriptBuilder 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;//是否是动态 sqlif (this.isDynamic) {//创建动态 SQL DynamicSqlSource sqlSource = new DynamicSqlSource(this.configuration, rootSqlNode);} else {//普通 SQL RawSqlSourcesqlSource = 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("");//重点关注这里 将节点封装成 TextSqlNodeTextSqlNode 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() {//创建一个 DynamicCheckerTokenParserTextSqlNode.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;//创建 statementBuilderorg.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 赋值 parameterMapstatementBuilder.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 创建的源码分析完毕,希望可以帮助到有需要的小伙伴。
欢迎提出建议及对错误的地方指出纠正。