总结
LanguageDriver
借助XMLScriptBuilder
解析标签,创建SqlSource
XMLScriptBuilder
负责解析XML标签,也就是这里负责解析,封装成SqlNode
,然后在调用的时候,再根据参数解析,拼接成最后的SQL
引入
在执行器执行query()
方法的时候,会直接在MappedStatement
里面获取BoundSql
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 直接从MappedStatement获取BoundSql
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
接下来进入到MappedStatement
里面的getBoundSql()
public BoundSql getBoundSql(Object parameterObject) {
// 获取BoundSql 这个流程在之前讲过,中间会经历 动态编译/静态编译 在加上参数映射处理
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
// 参数映射为空,重新创建
if (parameterMappings == null || parameterMappings.isEmpty()) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
// check for nested result maps in parameter mappings (issue #30)
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
return boundSql;
}
上面看到我们之前使用的sqlSource
是直接存在的,然后会进行编译,那么就代表我们解析完DML标签,生成MapperStatement
的时候,SqlSource
就已经创建好了,那么接下来就来看一下如何创建的SqlSource
,以及如何把SQL语句变成对应的SqlNode
接下来又回来了XMLStatementBuilder
的parseStatementNode()
,在其中会获取DML标签的各种属性,然后创建MapperStatement
,其中SqlSource
就是借助LanguageDriver
来解析的
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
// 1.获取到语言驱动
LanguageDriver langDriver = getLanguageDriver(lang);
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String nodeName = context.getNode().getNodeName();
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);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
// 2.创建SqlSource
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
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;
}
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
// 获取语言驱动
private LanguageDriver getLanguageDriver(String lang) {
Class<? extends LanguageDriver> langClass = null;
if (lang != null) {
langClass = resolveClass(lang);
}
// lang一般为空 所以直接会获取
return configuration.getLanguageDriver(langClass);
}
上面的代码中会经历两步,
- 通过
lang
属性来配置语言驱动,但是我在官网上并没有找到关于该属性的配置,可以推测,该属性已经不建议使用,但是代码还保留,我们用的时候肯定直接创建默认的 - 通过
langDriver
来创建SqlSource
LanguageDriver
public interface LanguageDriver {
// 创建参数映射
ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
// 创建数据源
SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
// 创建数据源
SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}
LanguageDriverRegistry 驱动注册
用于语言驱动注册,提供了一些简答的方法,包括注册、获取等,内容很简单
public class LanguageDriverRegistry {
// 注册的语言驱动Map
// key为Class value为对象
private final Map<Class<? extends LanguageDriver>, LanguageDriver> LANGUAGE_DRIVER_MAP = new HashMap<>();
// 默认的语言驱动 默认为XMLLanguageDriver
private Class<? extends LanguageDriver> defaultDriverClass;
// 注册 已有class 根据反射实例化 然后存入Map
public void register(Class<? extends LanguageDriver> cls) {
if (cls == null) {
throw new IllegalArgumentException("null is not a valid Language Driver");
}
MapUtil.computeIfAbsent(LANGUAGE_DRIVER_MAP, cls, k -> {
try {
return k.getDeclaredConstructor().newInstance();
} catch (Exception ex) {
throw new ScriptingException("Failed to load language driver for " + cls.getName(), ex);
}
});
}
// 注册 已有实体对象 获取class 然后存入Map
public void register(LanguageDriver instance) {
if (instance == null) {
throw new IllegalArgumentException("null is not a valid Language Driver");
}
Class<? extends LanguageDriver> cls = instance.getClass();
if (!LANGUAGE_DRIVER_MAP.containsKey(cls)) {
LANGUAGE_DRIVER_MAP.put(cls, instance);
}
}
public LanguageDriver getDriver(Class<? extends LanguageDriver> cls) {
return LANGUAGE_DRIVER_MAP.get(cls);
}
public LanguageDriver getDefaultDriver() {
return getDriver(getDefaultDriverClass());
}
public Class<? extends LanguageDriver> getDefaultDriverClass() {
return defaultDriverClass;
}
public void setDefaultDriverClass(Class<? extends LanguageDriver> defaultDriverClass) {
register(defaultDriverClass);
this.defaultDriverClass = defaultDriverClass;
}
}
初始化
我们的LanguageDriver
只有2个实现字类,那么注册类里面的Map最多也只有2个,那么它在哪里初始化的呢 答案在Configuration
的构造方法里面
public Configuration() {
// 上面省略一些代码...
// 默认语言驱动为 XMLLanguageDriver
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
// 注册另一个驱动
languageRegistry.register(RawLanguageDriver.class);
}
XMLLanguageDriver 动态SQL语言驱动
// Mybatis默认XML驱动类为XMLLanguageDriver
// 其主要作用于解析select|update|insert|delete节点为完整的SQL语句
public class XMLLanguageDriver implements LanguageDriver {
@Override
public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
// 创建默认的DefaultParameterHandler
// 我们之前使用的参数处理器 都是在这里创建返回的
return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
}
// 我们在 XMLStatementBuilder 里面创建SqlSource的时候,会调用该方法
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
// 这里创建了建造类
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
// 当sql中包含有${} 或者 动态SQL标签名 就认为是动态SQL
// 如果是动态返回 {@link DynamicSqlSource} ,否则 {@link RawSqlSource}
return builder.parseScriptNode();
}
// 上面的参数是XNode 这里面是String 需要先转成XNode
@Override
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
// issue #3
// 此处兼容XML方式的解析,条件以<script>为头结点
if (script.startsWith("<script>")) {// 将内容转换成XNode 然后在创建
XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
} else {
// issue #127
// 解析Configuration#variable变量,将有${...}形式的字符串转换成对应字符串
script = PropertyParser.parse(script, configuration.getVariables());
// 根据TextSqlNode的内部属性isDynamic来进行解析帮助类的分配
TextSqlNode textSqlNode = new TextSqlNode(script);
if (textSqlNode.isDynamic()) {
return new DynamicSqlSource(configuration, textSqlNode);
} else {
return new RawSqlSource(configuration, script, parameterType);
}
}
}
}
RawLanguageDriver 静态SQL语句驱动
默认XML语言是能够识别静态语句并创建一个{@link RawSqlSource},所以没有需要使用原始的,除非你想确保没有任何动态标记任何理由。
所以该类的使用频率比较少
// 继承了XMLLanguageDriver
public class RawLanguageDriver extends XMLLanguageDriver {
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
SqlSource source = super.createSqlSource(configuration, script, parameterType);
checkIsNotDynamic(source);
return source;
}
@Override
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
SqlSource source = super.createSqlSource(configuration, script, parameterType);
checkIsNotDynamic(source);
return source;
}
private void checkIsNotDynamic(SqlSource source) {
if (!RawSqlSource.class.equals(source.getClass())) {
throw new BuilderException("Dynamic content is not allowed when using RAW language");
}
}
}
XMLScriptBuilder XML解析
SqlSource 是基于XML解析而来,解析的底层是使用Dom4j 把XML解析成一个个子节点,在通过 XMLScriptBuilder 遍历这些子节点最后生成对应的SqlSource
。其解析流程如下图:
从图中可以看出这是一种递归式的访问 所有节点,如果是文本节点就会直接创建TextNode 或StaticSqlNode。否则就会创建动态脚本节点如IfSqlNode等。这里每种动态节点都会对应的处理器(NodeHandler
)来创建。创建好之后又会继续访问子节点,让递归继续下去。
public class XMLScriptBuilder extends BaseBuilder {
// 整个的DML标签 包括标签名 内容 也就是未解析之前的内容
private final XNode context;
// 是否动态SQL
private boolean isDynamic;
// 参数类型
private final Class<?> parameterType;
// 节点处理Map
private final Map<String, NodeHandler> nodeHandlerMap = new HashMap<>();
public XMLScriptBuilder(Configuration configuration, XNode context) {
this(configuration, context, null);
}
public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
super(configuration);
this.context = context;
this.parameterType = parameterType;
initNodeHandlerMap();
}
private void initNodeHandlerMap() {
nodeHandlerMap.put("trim", new TrimHandler());
nodeHandlerMap.put("where", new WhereHandler());
nodeHandlerMap.put("set", new SetHandler());
nodeHandlerMap.put("foreach", new ForEachHandler());
nodeHandlerMap.put("if", new IfHandler());
nodeHandlerMap.put("choose", new ChooseHandler());
nodeHandlerMap.put("when", new IfHandler());
nodeHandlerMap.put("otherwise", new OtherwiseHandler());
nodeHandlerMap.put("bind", new BindHandler());
}
public SqlSource parseScriptNode() {
// 转换动态标签
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
// 动态则创建 DynamicSqlSource
// 静态则创建 RawSqlSource
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<>();
// 子节点集合
NodeList children = node.getNode().getChildNodes();
// 循环处理
for (int i = 0; i < children.getLength(); i++) {
// XNode.newXNode(Node node):新建一个XNode,XNode只是mybatis对Node实例的封装,用于方便操作
// 并不是添加node节点的功能
XNode child = node.newXNode(children.item(i));
/**
* Node.CDATA_SECTION_NODE:代表文档中的 CDATA 部(不会由解析器解析的文本)
* Node.TEXT_NODE:代表元素或属性中的文本内容
*/
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
// 文本数据
String data = child.getStringBody("");
// 创建TextSqlNode
TextSqlNode textSqlNode = new TextSqlNode(data);
// 判断是否为动态节点 也就是判断是否包含 ${}
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
// 添加静态文本节点
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // Node.ELEMENT_NODE:代表元素
// 获取到子节点名
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);
// 并且标记为动态SQL
isDynamic = true;
}
}
return new MixedSqlNode(contents);
}
}
NodeHandler 标签节点处理器
private interface NodeHandler {
// nodeToHandle 子节点
// targetContents 目标节点
void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);
}
BindHandler
private class BindHandler implements NodeHandler {
public BindHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
final String name = nodeToHandle.getStringAttribute("name");
final String expression = nodeToHandle.getStringAttribute("value");
final VarDeclSqlNode node = new VarDeclSqlNode(name, expression);
targetContents.add(node);
}
}
TrimHandler
private class TrimHandler implements NodeHandler {
public TrimHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
// 递归处理子节点
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
String prefix = nodeToHandle.getStringAttribute("prefix");
String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");
String suffix = nodeToHandle.getStringAttribute("suffix");
String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");
// 创建后存入targetContents
TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);
targetContents.add(trim);
}
}
WhereHandler
private class WhereHandler implements NodeHandler {
public WhereHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
// 递归
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
targetContents.add(where);
}
}
SetHandler
private class SetHandler implements NodeHandler {
public SetHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
// 递归
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
SetSqlNode set = new SetSqlNode(configuration, mixedSqlNode);
targetContents.add(set);
}
}
ForEachHandler
private class ForEachHandler implements NodeHandler {
public ForEachHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
String collection = nodeToHandle.getStringAttribute("collection");
String item = nodeToHandle.getStringAttribute("item");
String index = nodeToHandle.getStringAttribute("index");
String open = nodeToHandle.getStringAttribute("open");
String close = nodeToHandle.getStringAttribute("close");
String separator = nodeToHandle.getStringAttribute("separator");
ForEachSqlNode forEachSqlNode = new ForEachSqlNode(configuration, mixedSqlNode, collection, index, item, open, close, separator);
targetContents.add(forEachSqlNode);
}
}
IfHandler
private class IfHandler implements NodeHandler {
public IfHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
String test = nodeToHandle.getStringAttribute("test");
IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
targetContents.add(ifSqlNode);
}
}
OtherwiseHandler
private class OtherwiseHandler implements NodeHandler {
public OtherwiseHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
targetContents.add(mixedSqlNode);
}
}
ChooseHandler
private class ChooseHandler implements NodeHandler {
public ChooseHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
List<SqlNode> whenSqlNodes = new ArrayList<>();
List<SqlNode> otherwiseSqlNodes = new ArrayList<>();
handleWhenOtherwiseNodes(nodeToHandle, whenSqlNodes, otherwiseSqlNodes);
SqlNode defaultSqlNode = getDefaultSqlNode(otherwiseSqlNodes);
ChooseSqlNode chooseSqlNode = new ChooseSqlNode(whenSqlNodes, defaultSqlNode);
targetContents.add(chooseSqlNode);
}
private void handleWhenOtherwiseNodes(XNode chooseSqlNode, List<SqlNode> ifSqlNodes, List<SqlNode> defaultSqlNodes) {
List<XNode> children = chooseSqlNode.getChildren();
for (XNode child : children) {
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler instanceof IfHandler) {
handler.handleNode(child, ifSqlNodes);
} else if (handler instanceof OtherwiseHandler) {
handler.handleNode(child, defaultSqlNodes);
}
}
}
private SqlNode getDefaultSqlNode(List<SqlNode> defaultSqlNodes) {
SqlNode defaultSqlNode = null;
if (defaultSqlNodes.size() == 1) {
defaultSqlNode = defaultSqlNodes.get(0);
} else if (defaultSqlNodes.size() > 1) {
throw new BuilderException("Too many default (otherwise) elements in choose statement.");
}
return defaultSqlNode;
}
}
andleNode(child, ifSqlNodes);
} else if (handler instanceof OtherwiseHandler) {
handler.handleNode(child, defaultSqlNodes);
}
}
}
private SqlNode getDefaultSqlNode(List<SqlNode> defaultSqlNodes) {
SqlNode defaultSqlNode = null;
if (defaultSqlNodes.size() == 1) {
defaultSqlNode = defaultSqlNodes.get(0);
} else if (defaultSqlNodes.size() > 1) {
throw new BuilderException("Too many default (otherwise) elements in choose statement.");
}
return defaultSqlNode;
}
}