概述
Mybatis源码简单解读—构建
参考源代码:Github搜索mybatis源码第一个,中文注释
同时maven下载mybatis较新版本的源码对照阅读
参考博客:https://www.cnblogs.com/javazhiyin/p/12340498.html
其他的小知识点也借鉴了很多其他博客的内容
首先mybatis的工作流程主要分为两个部分:
- 构建(解析xml和注解,映射成对象形成配置类)
- 执行(执行sql,完成jdbc与数据库交互)
这一部分只讲解mybatis的构建
单独使用mybatis框架的时候,会需要两个配置文件,分别是mybatis-config.xml和mapper.xml,采用官网给出案例mybatis的xml
我们不难看出,在mybatis-config.xml这个文件主要是用于配置数据源、配置别名、加载mapper.xml,并且我们可以看到这个文件的<mappers>
节点中包含了一个<mapper>
,而这个mapper所指向的路径就是另外一个xml文件:DemoMapper.xml,而这个文件中写了我们查询数据库所用的SQL。
而,MyBatis实际上就是将这两个xml文件,解析成配置对象,在执行中去使用它。
xml配置:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
使用mybatis,调用代码如下:
public static void main(String[] args) throws Exception {
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//创建SqlSessionFacory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
/******************************分割线******************************/
SqlSession sqlSession = sqlSessionFactory.openSession();
//获取Mapper
try (SqlSession session = sqlSessionFactory.openSession()) {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
}
sqlSession.commit();
}
这段程序显示通过字节流读取了mybatis-config.xml文件,然后通过SqlSessionFactoryBuilder.build()
方法,创建了一个SqlSessionFactory(这里用到了工厂模式和构建者模式),前面说过,MyBatis就是通过我们写的xml配置文件,来构建配置对象的,那么配置文件所在的地方,就一定是构建开始的地方,也就是build方法。
进入build方法
创建XMLConfigBuilder解析配置信息,之后调用parser()解析返回Configuration对象,调用build方法来创建SqlSessionFactoryfactory
接口的默认实现DefaultSqlSessionFactory
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//创建出 XMLConfigBuilder
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//解析xml文件分析
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.
}
}
}
//build()方法--构建者模式创建工厂对象
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
XMLConfigBuilder类
创建XmlConfigbuilder对象,mybaitis中的构建起都继承了一个BaseBuilder的类,该类维护了三个变量
- Configuration 配置类–主配置
- TypeAliasRegistry 类型别名注册器
- TypeHandlerRegistry 类型处理器注册器(用于类型转换)
configuration实际上就是一个维护了mybatis配置信息和执行相关的执行器和处理器的配置类(配置信息映射关系)
//上面6个构造函数最后都合流到这个函数,传入XPathParser
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
//首先调用父类初始化Configuration
super(new Configuration());
//错误上下文设置成SQL Mapper Configuration(XML文件配置),以便后面出错了报错用吧
ErrorContext.instance().resource("SQL Mapper Configuration");
//将Properties全部设置到Configuration里面去
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
构建该类初始化了Configuration,用于后面解析配置类解析完注入,特别是多个继承了BaseBuilder的构建器都用来操作该Configuration
初始化完,调用parse()方法
//解析配置
public Configuration parse() {
//如果已经解析过了,报错
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// <?xml version="1.0" encoding="UTF-8" ?>
// <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
// "http://mybatis.org/dtd/mybatis-3-config.dtd">
// <configuration>
// <environments default="development">
// <environment id="development">
// <transactionManager type="JDBC"/>
// <dataSource type="POOLED">
// <property name="driver" value="${driver}"/>
// <property name="url" value="${url}"/>
// <property name="username" value="${username}"/>
// <property name="password" value="${password}"/>
// </dataSource>
// </environment>
// </environments>
// <mappers>
// <mapper resource="org/mybatis/example/BlogMapper.xml"/>
// </mappers>
// </configuration>
//根节点是configuration
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
该配置解析了主配置类中的configuration节点
xml解析使用了java提供的xml解析工具,mybatis进一步封装了相关的解析方法,原理就是解析xml语法树
初始化生成Configuration流程图
进一步查看parser.evalNode("/configuration")
该方法分步骤解析根节点下的配置信息(使用 Xpath解析器来解析)
//解析配置
private void parseConfiguration(XNode root) {
try {
//分步骤解析
//issue #117 read properties first
//1.properties
propertiesElement(root.evalNode("properties"));
//2.类型别名
typeAliasesElement(root.evalNode("typeAliases"));
//3.插件
pluginElement(root.evalNode("plugins"));
//4.对象工厂
objectFactoryElement(root.evalNode("objectFactory"));
//5.对象包装工厂
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//6.设置
settingsElement(root.evalNode("settings"));
// read it after objectFactory and objectWrapperFactory issue #631
//7.环境
environmentsElement(root.evalNode("environments"));
//8.databaseIdProvider
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//9.类型处理器
typeHandlerElement(root.evalNode("typeHandlers"));
//10.映射器
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
解析映射器Mappers
其他部分代码不是主要了解内容,我们主要了解mapper映射器以及映射器对应xxxMapper.xml文件时是如何被解析的
跟踪映射器部分代码
mapperElement(root.evalNode(“mappers”));
首先我们先了解映射器的几种配置,查看官方文档可知有如下配置:
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
mapperElement()方法中也分别解析了下面几种配置方法
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
//10.4自动扫描包下所有映射器
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
//10.1使用类路径
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
//映射器比较复杂,调用XMLMapperBuilder
//注意在for循环里每个mapper都重新new一个XMLMapperBuilder,来解析
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
//10.2使用绝对url路径
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
//映射器比较复杂,调用XMLMapperBuilder
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
//10.3使用java类名
Class<?> mapperInterface = Resources.classForName(mapperClass);
//直接把这个映射加入配置
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
分析上面代码可以看出:
- 映射器配置 使用类接口的-----直接调用addMapper()方法
- 映射器配置 使用xxxMapper.xml则调用XmlMapper来解析
分别分析这两种方式的
Mapper配置使用类接口
跟踪源码
MapperRegistry
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
//查找包下所有是superType的类
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
//看一下如何添加一个映射
public <T> void addMapper(Class<T> type) {
//mapper必须是接口!才会添加
if (type.isInterface()) {
if (hasMapper(type)) {
//如果重复添加了,报错
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(type));
//在运行解析器之前添加类型很重要
//否则,映射器解析器可能会自动尝试绑定。 如果类型是已知的,则不会尝试。
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
//如果加载过程中出现异常需要再将这个mapper从mybatis中删除,这种方式比较丑陋吧,难道是不得已而为之?
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
从这里发现映射器的关系通过MapperRegistry注册机来维护,该类维护了配置类和映射关系map,
public class MapperRegistry {
private Configuration config;
//将已经添加的映射都放入HashMap
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
public MapperRegistry(Configuration config) {
this.config = config;
}
...
}
将类加入注册机后,使用MapperAnnotationBuilder 来解析parse()
查看该方法
public void parse() {
//type即接口类,resource为该类全路径
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
//解析对应xxxMapper.xml,并标识为已加载了
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
//缓存
parseCache();
parseCacheRef();
//解析类上的接口
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// 如果不是桥接方法
if (!method.isBridge()) {
//解析method中的sql注解
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
Method.isBrige()桥接方法
查看loadXmlResource
private void loadXmlResource() {
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
//查找同包下的xml文件,并解析
String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
if (inputStream == null) {
// Search XML mapper that is not in the module but in the classpath.
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e2) {
// ignore, resource is not required
}
}
if (inputStream != null) {
//构建xml解析工具来解析
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
代码分析到这里,我们可以看出大概的流程:
- 将该类接口注入
MapperRegistry
中,通过Map<Class<?>, MapperProxyFactory<?>> knownMappers
集合来维护,把类接口和新生成的MapperProxyFactory代理工厂作为键值对 - 解析该接口类下的xml文件
- 解析类接口上的注解
Mapper配置使用xml文件
xml文件的方式首先通过XMLMapperBuilder解析xml文件【该类也在接口方式中调用】
这里跟踪下XMLMapperBuilder是如何解析信息的
public void parse() {
//判断文件是否之前解析过
if (!configuration.isResourceLoaded(resource)) {
//解析mapper文件节点(主要)(下面贴了代码)
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
//绑定Namespace里面的Class对象---实际上就是添加到MapperRegistry中
bindMapperForNamespace();
}
//重新解析之前解析不了的节点
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
//解析mapper文件里面的节点
// 拿到里面配置的配置项 最终封装成一个MapperedStatemanet
private void configurationElement(XNode context) {
try {
//获取命名空间 namespace,这个很重要,后期mybatis会通过这个动态代理我们的Mapper接口
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
//如果namespace为空则抛一个异常
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
//解析缓存节点
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
//解析parameterMap(过时)和resultMap <resultMap></resultMap>
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析<sql>节点
//<sql id="staticSql">select * from test</sql> (可重用的代码段)
//<select> <include refid="staticSql"></select>
sqlElement(context.evalNodes("/mapper/sql"));
//解析增删改查节点<select> <insert> <update> <delete>
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);
}
}
在这个parse()方法中,调用了一个configuationElement代码,用于解析XXXMapper.xml文件中的各种节点,包括<cache>
、<cache-ref>
、<paramaterMap>
(已过时)、<resultMap>
、<sql>
、还有增删改查节点,和上面相同的是,我们也挑一个主要的来说,因为解析过程都大同小异。
同时将xml文件上namespace对应的接口注册到MappperRegistry注册机中。
这里我们介绍下增删改查节点的方法——buildStatementFromContext(),和JDBC一样该Statement就是操作数据库的对象
//7.配置select|insert|update|delete
private void buildStatementFromContext(List<XNode> list) {
//调用7.1构建语句
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
//7.1构建语句
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
//构建所有语句,一个mapper下可以有很多select
//语句比较复杂,核心都在这里面,所以调用XMLStatementBuilder
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//核心XMLStatementBuilder.parseStatementNode--这部分和类接口中解析sql注解的逻辑一样
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
//如果出现SQL语句不完整,把它记下来,塞到configuration去
configuration.addIncompleteStatement(statementParser);
}
}
}
public void parseStatementNode() {
//获取<select id="xxx">中的id
String id = context.getStringAttribute("id");
//获取databaseId 用于多数据库,这里为null
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
//获取节点名 select update delete insert
String nodeName = context.getNode().getNodeName();
//根据节点名,得到SQL操作的类型
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
//判断是否是查询
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//是否刷新缓存 默认:增删改刷新 查询不刷新
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
//是否使用二级缓存 默认值:查询使用 增删改不使用
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
//是否需要处理嵌套查询结果 group by
// 三组数据 分成一个嵌套的查询结果
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
//替换Includes标签为对应的sql标签里面的值
includeParser.applyIncludes(context.getNode());
//获取parameterType名
String parameterType = context.getStringAttribute("parameterType");
//获取parameterType的Class
Class<?> parameterTypeClass = resolveClass(parameterType);
//解析配置的自定义脚本语言驱动 这里为null
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
//解析selectKey
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
//设置主键自增规则
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
/************************************************************************************/
//解析Sql(重要) 根据sql文本来判断是否需要动态解析 如果没有动态sql语句且 只有#{}的时候 直接静态解析使用?占位 当有 ${} 不解析
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
//获取StatementType,可以理解为Statement和PreparedStatement
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
//没用过
Integer fetchSize = context.getIntAttribute("fetchSize");
//超时时间
Integer timeout = context.getIntAttribute("timeout");
//已过时
String parameterMap = context.getStringAttribute("parameterMap");
//获取返回值类型名
String resultType = context.getStringAttribute("resultType");
//获取返回值烈性的Class
Class<?> resultTypeClass = resolveClass(resultType);
//获取resultMap的id
String resultMap = context.getStringAttribute("resultMap");
//获取结果集类型
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
//将刚才获取到的属性,封装成MappedStatement对象(代码贴在下面)
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
//将刚才获取到的属性,封装成MappedStatement对象
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 (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
//id = namespace
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//通过构造者模式+链式变成,构造一个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);
}
//通过构造者构造MappedStatement
MappedStatement statement = statementBuilder.build();
//将MappedStatement对象封装到Configuration对象中
configuration.addMappedStatement(statement);
return statement;
}
将xml中的节点解析,并封装一个MappedStatement对象,并添加在Configuration中和Map集合中
构建过程流程图:
SQL语句解析
在刚才过程中包含了SQL语句的生成,在这里进一步分析
//解析Sql(重要) 根据sql文本来判断是否需要动态解析 如果没有动态sql语句且 只有#{}的时候 直接静态解析使用?占位 当有 ${} 不解析
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
这里就是生成Sql的入口,以单步调试的角度接着往下看。
/*进入createSqlSource方法*/
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
//进入这个构造
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
//进入parseScriptNode
return builder.parseScriptNode();
}
/**
进入这个方法
*/
public SqlSource parseScriptNode() {
//#
//会先解析一遍
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
if (isDynamic) {
//如果是${}会直接不解析,等待执行的时候直接赋值
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
//用占位符方式来解析 #{} --> ?
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
protected MixedSqlNode parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<>();
//获取select标签下的子标签
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
//如果是查询
//获取原生SQL语句 这里是 select * from test where id = #{id}
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
//检查sql是否是${}
if (textSqlNode.isDynamic()) {
//如果是${}那么直接不解析
contents.add(textSqlNode);
isDynamic = true;
} else {
//如果不是,则直接生成静态SQL
//#{} -> ?
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
//如果是增删改
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
handler.handleNode(child, contents);
isDynamic = true;
}
}
return new MixedSqlNode(contents);
}
/*从上面的代码段到这一段中间需要经过很多代码,就不一段一段贴了*/
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
//这里会生成一个GenericTokenParser,传入#{}作为开始和结束,然后调用其parse方法,即可将#{}换为 ?
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
//这里可以解析#{} 将其替换为?
String sql = parser.parse(originalSql);
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
//经过一段复杂的解析过程
public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
// search open token
int start = text.indexOf(openToken);
if (start == -1) {
return text;
}
char[] src = text.toCharArray();
int offset = 0;
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
//遍历里面所有的#{} select ? ,#{id1} ${}
while (start > -1) {
if (start > 0 && src[start - 1] == '') {
// this open token is escaped. remove the backslash and continue.
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
// found open token. let's search close token.
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end - 1] == '') {
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {
expression.append(src, offset, end - offset);
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
//使用占位符 ?
//注意handler.handleToken()方法,这个方法是核心
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
//BindingTokenParser 的handleToken
//当扫描到${}的时候调用此方法 其实就是不解析 在运行时候在替换成具体的值
@Override
public String handleToken(String content) {
this.isDynamic = true;
return null;
}
//ParameterMappingTokenHandler的handleToken
//全局扫描#{id} 字符串之后 会把里面所有 #{} 调用handleToken 替换为?
@Override
public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}
首先这里会通过<select>
节点获取到我们的SQL语句,假设SQL语句中只有${}
,那么直接就什么都不做,在运行的时候直接进行赋值。
而如果扫描到了#{}
字符串之后,会进行替换,将#{}
替换为 ?
。
那么他是怎么进行判断的呢?
这里会生成一个GenericTokenParser,这个对象可以传入一个openToken和closeToken,如果是#{}
,那么openToken就是#{
,closeToken就是 }
,然后通过parse方法中的handler.handleToken()
方法进行替换。
在这之前由于已经进行过SQL是否含有#{}
的判断了,所以在这里如果是只有${}
,那么handler就是BindingTokenParser的实例化对象,如果存在#{}
,那么handler就是ParameterMappingTokenHandler的实例化对象。
分别进行处理。
小结
至此整个MyBatis的查询前构建的过程就基本说完了,简单地总结就是,MyBatis会在执行查询之前,对配置文件进行解析成配置对象:Configuration,以便在后面执行的时候去使用,而存放SQL的xml又会解析成MappedStatement对象,但是最终这个对象也会加入Configuration中,将Configuration对象通过build()方法来创建工厂对象
最后
以上就是震动发夹为你收集整理的Mybatis源码简单解读----构建Mybatis源码简单解读—构建的全部内容,希望文章能够帮你解决Mybatis源码简单解读----构建Mybatis源码简单解读—构建所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复