【Mybatis源码分析 6】XMLConfigBuilder将Mybatis全局配置文件.xml中设置的节点提取赋给Configuration对象中讲到SqlSessionFactoryBuilder创建SqlSessionFactory对象时,
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resource);
将mybatis的全局配置文件中的11个节点提取并处理后赋给Configuration对象,其中就有mappers映射器的提取和处理
org.apache.ibatis.builder.xml.XMLConfigBuilder调用parseConfiguration方法提取配置文件中的11个节点
private void parseConfiguration(XNode root) {
try {
this.propertiesElement(root.evalNode("properties"));
Properties settings = this.settingsAsProperties(root.evalNode("settings"));
this.loadCustomVfs(settings);
this.loadCustomLogImpl(settings);
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"));
this.settingsElement(settings);
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
this.typeHandlerElement(root.evalNode("typeHandlers"));
// 提取和处理映射器
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
提取和处理映射器,mappers下可以定义多个mapper子标签,引用多个.xml或接口
注意:不能同时使用resource和url,否则抛出BuilderException异常
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// 获取所有mapper子节点的迭代器
Iterator var2 = parent.getChildren().iterator();
while(true) {
// 遍历所有mapper子节点,提取sql字符串和参数,并对sql字符串格式化(处理${},#{})
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String resource;
// package子标签表示扫描该包下面所有的mapper接口
if ("package".equals(child.getName())) {
// 获取包名
resource = child.getStringAttribute("name");
// 将该包下面所有的mapper接口注册到configuration对象的映射器注册器中
this.configuration.addMappers(resource);
} else {
// 单个mapper文件(.xml或接口)
resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
XMLMapperBuilder mapperParser;
InputStream inputStream;
// 如果是resource引用,则规定该.xml在资源文件夹下,否则扫描不到
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
// 加载.xml文件的二进制数据流
inputStream = Resources.getResourceAsStream(resource);
// 创建xml映射文件解析器
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
//重点方法:解析处理sql语句和statementid节点传入的参数
mapperParser.parse();
}
// url引用本地或网络资源,但是要保证resource和url只能使用一个,否则抛出BuilderException异常
else if (resource == null && url != null && mapperClass == null) {
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.");
}
// 引用mapper接口
Class<?> mapperInterface = Resources.classForName(mapperClass);
//重点方法:扫描mapper接口,解析处理注解描述的sql语句和方法传入的参数
this.configuration.addMapper(mapperInterface);
}
}
}
return;
}
}
}
org.apache.ibatis.builder.xml.XMLMapperBuilder::parse解析处理sql语句和statementid节点传入的参数
public void parse() {
if (!this.configuration.isResourceLoaded(this.resource)) {
// 解析statementid
this.configurationElement(this.parser.evalNode("/mapper"));
this.configuration.addLoadedResource(this.resource);
this.bindMapperForNamespace();
}
this.parsePendingResultMaps();
this.parsePendingCacheRefs();
this.parsePendingStatements();
}
解析statementid
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace != null && !namespace.equals("")) {
this.builderAssistant.setCurrentNamespace(namespace);
this.cacheRefElement(context.evalNode("cache-ref"));
this.cacheElement(context.evalNode("cache"));
// java传入的参数包装为key-value的map
this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 查询结果集,按照某种关系映射到java对象
this.resultMapElements(context.evalNodes("/mapper/resultMap"));
// 提取sql字符串
this.sqlElement(context.evalNodes("/mapper/sql"));
// 重点方法:根据上下文解析select|insert|update|delete的sql语句
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);
}
}
根据上下文解析select|insert|update|delete的sql语句
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
Iterator var3 = list.iterator();
while(var3.hasNext()) {
XNode context = (XNode)var3.next();
XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);
try {
// 解析statementid
statementParser.parseStatementNode();
} catch (IncompleteElementException var7) {
this.configuration.addIncompleteStatement(statementParser);
}
}
}
org.apache.ibatis.builder.xml.XMLStatementBuilder::parseStatementNode解析mapper.xml中的statementid
public void parseStatementNode() {
// 获取statementid
String id = this.context.getStringAttribute("id");
// 获取databaseId数据库标识
String databaseId = this.context.getStringAttribute("databaseId");
if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
String nodeName = this.context.getNode().getNodeName();
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);
boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", false);
// 解析参数等
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(this.configuration, this.builderAssistant);
includeParser.applyIncludes(this.context.getNode());
String parameterType = this.context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = this.resolveClass(parameterType);
String lang = this.context.getStringAttribute("lang");
LanguageDriver langDriver = this.getLanguageDriver(lang);
this.processSelectKeyNodes(id, parameterTypeClass, langDriver);
String keyStatementId = id + "!selectKey";
keyStatementId = this.builderAssistant.applyCurrentNamespace(keyStatementId, true);
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;
}
// 根据上下文和参数处理sql字符串(处理${}和#{})
SqlSource sqlSource = langDriver.createSqlSource(this.configuration, this.context, parameterTypeClass);
//最终根据namesppace+id为key封装成MappedStatement保存到Configuration
StatementType statementType = StatementType.valueOf(this.context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = this.context.getIntAttribute("fetchSize");
Integer timeout = this.context.getIntAttribute("timeout");
String parameterMap = this.context.getStringAttribute("parameterMap");
String resultType = this.context.getStringAttribute("resultType");
Class<?> resultTypeClass = this.resolveClass(resultType);
String resultMap = this.context.getStringAttribute("resultMap");
String resultSetType = this.context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = this.resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = this.configuration.getDefaultResultSetType();
}
String keyProperty = this.context.getStringAttribute("keyProperty");
String keyColumn = this.context.getStringAttribute("keyColumn");
String resultSets = this.context.getStringAttribute("resultSets");
this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
}
org.apache.ibatis.scripting.xmltags.XMLLanguageDriver::createSqlSource
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
org.apache.ibatis.scripting.xmltags.XMLScriptBuilder::parseScriptNode 根据上下文和参数处理sql字符串(处理${}和#{})
public SqlSource parseScriptNode() {
//先判断是否是${}传值,并解析sql
MixedSqlNode rootSqlNode = this.parseDynamicTags(this.context);
Object sqlSource;
if (this.isDynamic) {
sqlSource = new DynamicSqlSource(this.configuration, rootSqlNode);
} else {
// 如果是#{}传值
sqlSource = new RawSqlSource(this.configuration, rootSqlNode, this.parameterType);
}
return (SqlSource)sqlSource;
}
先判断是否是${}传值,并解析sql
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 = new TextSqlNode(nodeName);
// 调用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,并设置${}和参数处理器,对sql语句解析
TextSqlNode.DynamicCheckerTokenParser checker = new TextSqlNode.DynamicCheckerTokenParser();
GenericTokenParser parser = this.createParser(checker);
parser.parse(this.text);
return checker.isDynamic();
}
设置token和参数处理器
private GenericTokenParser createParser(TokenHandler handler) {
return new GenericTokenParser("${", "}", handler);
}
org.apache.ibatis.parsing.GenericTokenParser::parse核心方法,真正替换完成${}或#{}解析的动作
public String parse(String text) {
if (text != null && !text.isEmpty()) {
// 定位"${"的索引 -- 初步判断是否存在${}
int start = text.indexOf(this.openToken);
if (start == -1) {
return text;
} else {
// 转字符串数组
char[] src = text.toCharArray();
// 偏移量,用以indexof寻找下一个"${"或者"}"
int offset = 0;
// 创建容器保存字符串
StringBuilder builder = new StringBuilder();
// 遍历所有的"${"
for(StringBuilder expression = null; start > -1; start = text.indexOf(this.openToken, offset)) {
if (start > 0 && src[start - 1] == '\\') {
builder.append(src, offset, start - offset - 1).append(this.openToken);
offset = start + this.openToken.length();
} else {
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
// 保留offset起到"${"左边的内容
builder.append(src, offset, start - offset);
offset = start + this.openToken.length();
int end;
// 寻找匹配的"}"
for(end = text.indexOf(this.closeToken, offset); end > -1; end = text.indexOf(this.closeToken, offset)) {
if (end <= offset || src[end - 1] != '\\') {
// expression记录"${"和"}"之间的参数名
expression.append(src, offset, end - offset);
break;
}
expression.append(src, offset, end - offset - 1).append(this.closeToken);
offset = end + this.closeToken.length();
}
if (end == -1) {
builder.append(src, start, src.length - start);
offset = src.length;
} else {
// 调用handler的handleToken方法,获取参数值
builder.append(this.handler.handleToken(expression.toString()));
offset = end + this.closeToken.length();
}
}
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
} else {
return "";
}
}
org.apache.ibatis.scripting.xmltags.TextSqlNode.DynamicCheckerTokenParser::handleToken获取参数值,并将isDynamic置true.
一开始我看着这里觉得很奇怪,${}是直接将参数值拼接到sql字符串中的,这里怎么返回null呢, 后来明白, 现在还只是加载mapper映射器的阶段,还没有运行具体的sql语句,没有参数值,怎么赋值呢,所以在加载配置文件解析映射器阶段直接返回null了。
public String handleToken(String content) {
this.isDynamic = true;
return null;
}
org.apache.ibatis.scripting.xmltags.XMLScriptBuilder::parseDynamicTags方法中判断textSqlNode.isDynamic()
调用org.apache.ibatis.parsing.GenericToken::parse方法中如果找到${}则返回true, org.apache.ibatis.scripting.xmltags.XMLScriptBuilder::isDynamic则置true
如果是#{}传参,org.apache.ibatis.scripting.xmltags.XMLScriptBuilder::isDynamic为默认值false,则创建org.apache.ibatis.scripting.defaults.RawSqlSource对象
public SqlSource parseScriptNode() {
// 如果是${}传值,在这里通过isDynamic()方法已经解析完成
MixedSqlNode rootSqlNode = this.parseDynamicTags(this.context);
Object sqlSource;
if (this.isDynamic) {
sqlSource = new DynamicSqlSource(this.configuration, rootSqlNode);
} else {
// 如果是#{}传值
sqlSource = new RawSqlSource(this.configuration, rootSqlNode, this.parameterType);
}
return (SqlSource)sqlSource;
}
org.apache.ibatis.scripting.defaults.RawSqlSource的构造方法中创建SqlSourceBuilder对象,解析sql
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> clazz = parameterType == null ? Object.class : parameterType;
this.sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap());
}
org.apache.ibatis.builder.SqlSourceBuilder::parse方法中创建GenericTokenParser对象,设置token为#{}
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
SqlSourceBuilder.ParameterMappingTokenHandler handler = new SqlSourceBuilder.ParameterMappingTokenHandler(this.configuration, parameterType, additionalParameters);
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql = parser.parse(originalSql);
return new StaticSqlSource(this.configuration, sql, handler.getParameterMappings());
}
org.apache.ibatis.parsing.GenericTokenParser::parse详见上面的解析
public String parse(String text) {
//。。。
if (end == -1) {
builder.append(src, start, src.length - start);
offset = src.length;
} else {
// ParameterMappingTokenHandler对象handler调用handleToken方法,
// 将参数名添加到参数集中,并返回"?"占位符拼接到sql语句中
builder.append(this.handler.handleToken(expression.toString()));
// 然后offset继续往后移动一个"}"的长度,进入下一个循环,匹配下一个"${}"
offset = end + this.closeToken.length();
}
//。。。
}
org.apache.ibatis.builder.SqlSourceBuilder.ParameterMappingTokenHandler::handleToken将参数名添加到参数集中,并返回"?"占位符拼接到sql语句中
public String handleToken(String content) {
this.parameterMappings.add(this.buildParameterMapping(content));
return "?";
}
${}和#{}传参差别
1. ${prama}:获取参数的值,拼接到SQL中。存在SQL注入风险。
#{prama}:获取参数的值,预编译到SQL中。安全。
2. ${}使用statement执行sql语句
#{}使用PreparedStatement预编译sql语句,使用占位符注入参数
3. ${}采用拼接的方式,需要注意单引号问题
#{}采用通配符占位设置参数的方式,自动加上单引号
4. 当存在多个参数时(当传入多个参数时,mybatis默认将这些参数放在集合中)
${}可以通过${参数名}, ${param1}、${param2}、、、${paramN}两种方式给sql语句注入参数
#{}可以通过#{参数名}, #{param1}、#{param2}、、、#{paramN-1},#{0}、#{1}、、、#{N-1}三种方式给sql语句注入参数