在上幾章內容中,我們講述了Mapper資源文件的解析,在最后關頭的MapperStatement構建中,我們有一個重要的地方沒有涉及到,今天我們就來深入這一塊內容。那這是什么呢?其實就是sql動態(tài)語句的構建。在MyBatis中,這塊功能也是它的亮點之一。
在Mapper解析過程中,我們會有這樣一個調用方法:
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
1. langDriver從何而來
我們先來看它的初始化方法:
LanguageDriver langDriver = getLanguageDriver(lang);
private LanguageDriver getLanguageDriver(String lang) {
Class<? extends LanguageDriver> langClass = null;
if (lang != null) {
langClass = resolveClass(lang);
}
return configuration.getLanguageDriver(langClass);
}
public LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass) {
if (langClass == null) {
return languageRegistry.getDefaultDriver();
}
languageRegistry.register(langClass);
return languageRegistry.getDriver(langClass);
}
這里我們一般都不會設置特定的LanguageDriver,那么默認的LanguageDriver是哪里設置進來的呢?
是在Configuration的構造函數中:
public Configuration() {
。。。
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
所以我們知道了如果沒有特殊指定,那我們將會使用XMLLanguageDriver來構建SqlSource。
2. SqlSource的構建
我們進入createSqlSource方法:
//XMLLanguageDriver
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
我們有看到了一個Builder相關類,我們繼續(xù)來看這個源碼。
2.1 XMLScriptBuilder 源碼分析
我們先來看它的屬性和構造方法:
public class XMLScriptBuilder extends BaseBuilder {
private final XNode context;
private boolean isDynamic;
private final Class<?> parameterType;
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;
//<1> 初始化nodeHandler
initNodeHandlerMap();
}
。。。
}
這里關鍵的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());
}
看到這里就有點欣喜了,因為這就是我們常用的動態(tài)Sql的節(jié)點。我們繼續(xù)來看它的方法:
public SqlSource parseScriptNode() {
//<1> 解析sqlNode節(jié)點
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
這里中的<1>方法是最重要的:
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));
// <1>如果不包含其他標簽或者是純文本節(jié)點
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
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) { // 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);
}
這里這樣強看的話,會讓人很不舒服,也不能理解,所以我們結合例子來看。
假設我們的資源文件內容是(這是源碼中的Test,路徑為org/apache/ibatis/builder/AuthorMapper.xml):
<select id="selectAllAuthors" resultType="org.apache.ibatis.domain.blog.Author">
select * from author
</select>
OK,根據這個例子,我們可以看到在parseDynamicTags進入了<1>中的邏輯,也就是:
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
而在判斷isDynamic方法,我們進入查看:
public boolean isDynamic() {
DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
GenericTokenParser parser = createParser(checker);
parser.parse(text);
return checker.isDynamic();
}
private GenericTokenParser createParser(TokenHandler handler) {
return new GenericTokenParser("${", "}", handler);
}
因為我們是純文本的sql,所以我們直接會進入添加StaticTextSqlNode方法:
contents.add(new StaticTextSqlNode(data));
//StaticTextSqlNode
public class StaticTextSqlNode implements SqlNode {
private final String text;
public StaticTextSqlNode(String text) {
this.text = text;
}
@Override
public boolean apply(DynamicContext context) {
context.appendSql(text);
return true;
}
}
//之后直接返回了
return new MixedSqlNode(contents);
如果接的理不清楚的話,建議調試代碼進行理解。
那么退出來之后我們又是進入了:
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
我們知道isDynamic是false,那么我們繼續(xù)來看RawSqlSource這塊的邏輯:
public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
this(configuration, getSql(configuration, rootSqlNode), parameterType);
}
private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
DynamicContext context = new DynamicContext(configuration, null);
rootSqlNode.apply(context);
return context.getSql();
}
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> clazz = parameterType == null ? Object.class : parameterType;
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
}
我們繼續(xù)會進入到sqlSourceParser.parse方法:
public class SqlSourceBuilder extends BaseBuilder {
private static final String PARAMETER_PROPERTIES = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";
public SqlSourceBuilder(Configuration configuration) {
super(configuration);
}
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql = parser.parse(originalSql);
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
其中這里的StaticSqlSource為
public class StaticSqlSource implements SqlSource {
private final String sql;
private final List<ParameterMapping> parameterMappings;
private final Configuration configuration;
public StaticSqlSource(Configuration configuration, String sql) {
this(configuration, sql, null);
}
public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.configuration = configuration;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}
}
3. 今日總結
今天我們大致分析了createSqlSource的大致流程,其中有很多我們肯定還是沒有搞懂,我大致整理下了我們欠下的技術債
sqlNode
nodeHandler
sqlSource
ParameterMapping
之后我們會慢慢分析。