LanguageDriver主要用于构造SqlSource和ParameterHandler,从LanguageDriver在Mybatis中调用链路来看,Configuration#newParameterHandler通过调用LanguageDriver#createParameterHandler完成构建ParameterHandler,通过调用LanguageDriver#createSqlSource来完成构建SqlSource。
LanguageDriver接口一共只定义了createParameterHandler和两个createSqlSource方法,其结构也相当简单,Mybatis只实现了RawLanguageDriver和XMLLanguageDriver两个类,且RawLanguageDriver还继承自XMLLanguageDriver,如下图所示。
- XMLLanguageDriver,XML语言驱动,主要帮助Mybatis实现通过XML标签描述的动态SQL。
- RawLanguageDriver,与RawSqlSource一样,主要表示支持静态SQL信息配置。
XMLLanguageDriver
XMLLanguageDriver以XML打头就已经明确说明该实现主要是为了支持使用XML标签描述的动态SQL,其完整实现了LanguageDriver接口定义的三个方法,其中createParameterHandler方法的实现仅是通过DefaultParameterHandler构造函数构造了一个ParameterHandler,所以就不再做过多的讨论。
@Override
public ParameterHandler createParameterHandler(
MappedStatement mappedStatement,
Object parameterObject, BoundSql boundSql) {
return new DefaultParameterHandler(
mappedStatement, parameterObject, boundSql);
}
createSqlSource
LanguageDriver接口定了两个createSqlSource方法从源代码来看二则唯一的区别就在于入参的第二个参数,一个接口定义所需要的XNode类型的script,另一个接口定义则需要String类型的script。Mybatis之所以这么设计,是因为一个方法是用于处理通过XML配置的SQL信息,另一个方法则用于处理通过注解配置的SQL信息。
- 处理XML配置的SQL信息
从源代码来看,用于处理XML配置的SQL信息的createSqlSource方法逻辑相对清晰,
@Override
public SqlSource createSqlSource(
Configuration configuration,
XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(
configuration, script, parameterType);
return builder.parseScriptNode();
}
- 通过所传入的参数构建一个用于处理XML配置的XMLScriptBuilder对象;
- 调用XMLScriptBuilder#parseScriptNode方法将SQL信息转换为SqlSource对象。
XMLLanguageDriver使用XMLScriptBuilder来构建了SqlSource对象,由此可见XMLScriptBuilder的职责就是解析XML配置信息构建对应的SqlSource对象,如下源代码所示,XMLScriptBuilder的构造函数在完成对属性赋值之后,又通过initNodeHandlerMap方法初始化了一批针对XML配置标签的处理器。之后在调用XMLScriptBuilder#parseScriptNode方法时通过解析结果中的isDynamic来分别构建DynamicSqlSource和RawSqlSource。
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;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
- 处理注解配置的SQL信息
处理注解配置的SQL信息的createSqlSource方法有两个处理分支,一个处理script文本中有<script>标签的SQL配置信息,另一个处理不带<script>标签的SQL配置信息。处理逻辑详情:
- 如果所传入的script文本中包含<script>标签,那么构造一个XPathParser用于将script文本构造成XNode对象,然后调用用于处理XML配置的SQL信息的createSqlSource;
如,@select(“<script> select * from a <where> <if b != null> and c = b </if> </where> </script>”) - 如果所传入的script文本中不包含<script>标签,那么直接通过判断当前是否是动态SQL配置信息来分别构建DynamicSqlSource和RawSqlSource。
@Override
public SqlSource createSqlSource(
Configuration configuration,
String script, Class<?> parameterType) {
if (script.startsWith("<script>")) {
XPathParser parser = new XPathParser(
script, false, configuration.getVariables(),
new XMLMapperEntityResolver());
return createSqlSource(
configuration, parser.evalNode("/script"), parameterType);
} else {
script = PropertyParser.parse(script, configuration.getVariables());
TextSqlNode textSqlNode = new TextSqlNode(script);
if (textSqlNode.isDynamic()) {
return new DynamicSqlSource(configuration, textSqlNode);
} else {
return new RawSqlSource(configuration, script, parameterType);
}
}
}
RawLanguageDriver
RawLanguageDriver继承自XMLLanguageDriver,所以它是完全依托于XMLLanguageDriver的实现来完成构建SqlSource。从源代码可见其逻辑只是在XMLLanguageDriver构建SqlSource的基础之上增加了一个是否是动态SQL配置的判断,如果是动态SQL配置,就会直接抛出BuilderException。
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");
}
}
}