BoundSql
BoundSql只是一个简单的java对象,有两个属性比较重要
- sql:从解析时可以看出这个sql不是配置文件中的sql,这个sql已经经过了处理(如:占用位符的处理、动态语句的解析if、foreach等待)
- parameterObject:客户端执行sql时传入的参数
- parameterMappings: sql中的参数映射对应#{id,jdbcType=INTEGER}
标签sql对应的参数列表
字段
““java
public class BoundSql {
/**
* 经过处理的sql,这个sql已经可以被数据库执行了
*/
private String sql;
/**
* sql中的参数映射对应#{id,jdbcType=INTEGER}
*/
private List<ParameterMapping> parameterMappings;
/**
* 客户端执行sql时传入的参数
*/
private Object parameterObject;
private Map<String, Object> additionalParameters;
private MetaObject metaParameters;
}
““
SqlNode
java
public interface SqlNode {
boolean apply(DynamicContext context);
}
该方法的含义为,将sql的处理结果,append到DynamicContext context对象中,DynamicContext可以理解为StringBuilder对象的功能,它的作用就是计算sql片段并append到一起,形成最终的sql。
类似的实现还有whereSQLNode SetSqlNode ForEachSqlNode
等
VarDeclSqlNode
处理动态sql标签的SqlNode类。
@Override
public boolean apply(DynamicContext context) {
final Object value = OgnlCache.getValue(expression, context.getBindings());
// 由于没有sql可append,仅是把bind标签的变量名和值保存至上下文参数列表内
context.bind(name, value);
return true;
}
MixedSqlNode
意为混合的SqlNode,它保存了其他多种SqlNode的集合,可以看做是一个List列表
SqlNode的组合设计模式
SqlNode,采用了组合设计模式,组合设计模式可以用来表示经典的树型结构
前面说的MixedSqlNode,就代表了List集合
NodeHandler
SqlNode是由NodeHandler创建出来的。
private interface NodeHandler {
void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);
}
BindHandler
用来生成VarDeclSqlNode
@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
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
List<SqlNode> contents = parseDynamicTags(nodeToHandle);
MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
String prefix = nodeToHandle.getStringAttribute("prefix");
String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");
String suffix = nodeToHandle.getStringAttribute("suffix");
String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");
TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);
targetContents.add(trim);
}
ChooseHandler
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
List<SqlNode> whenSqlNodes = new ArrayList<SqlNode>();
List<SqlNode> otherwiseSqlNodes = new ArrayList<SqlNode>();
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 = nodeHandlers(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;
}
可以清楚看出,标签是和、标签配合使用的,创建ChooseSqlNode时,就同时创建了when、otherwise的逻辑,而when会转换为if标签处理,otherwise则转换为SqlNode处理,一般是StaticTextSqlNode。
LanguageDriver
LanguageDriver是一个辅助工具类,用于创建SqlSource。
- XMLLanguageDriver:用于创建动态、静态SqlSource。
- RawLanguageDriver:在确保只有静态sql时,可以使用,不得含有任何动态sql的内容,否则,请使用XMLLanguageDriver。它其实是对XMLLanguageDriver创建的结果进行唯静态sql检查而已,发现有动态sql的内容,就抛异常。
DynamicContext
对传入的parameterObject进行map化,
字段
““java
public static final String PARAMETER_OBJECT_KEY = "_parameter";
public static final String DATABASE_ID_KEY = "_databaseId";
static {
OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor());
}
private final ContextMap bindings;
private final StringBuilder sqlBuilder = new StringBuilder();
private int uniqueNumber = 0;
““
ContextAccessor也是DynamicContext的内部类,实现了Ognl中的PropertyAccessor接口,为Ognl提供了如何使用ContextMap参数对象的说明
构造方法
public DynamicContext(Configuration configuration, Object parameterObject) {
if (parameterObject != null && !(parameterObject instanceof Map)) {//如果参数是map
MetaObject metaObject = configuration.newMetaObject(parameterObject);
bindings = new ContextMap(metaObject);
} else {
bindings = new ContextMap(null);
}
bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
}
在DynamicContext的构造函数中,可以看到,根据传入的参数对象是否为Map类型,有两个不同构造ContextMap的方式。而ContextMap作为一个继承了HashMap的对象,作用就是用于统一参数的访问方式:用Map接口方法来访问数据。具体来说,当传入的参数对象不是Map类型时,Mybatis会将传入的POJO对象用MetaObject对象来封装,当动态计算sql过程需要获取数据时,用Map接口的get方法包装 MetaObject对象的取值过程。
参数传递和使用过程
OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor());
将传入的参数对象统一封装为ContextMap对象(继承了HashMap对象),然后Ognl运行时环境在动态计算sql语句时,会按照ContextAccessor中描述的Map接口的方式来访问和读取ContextMap对象,获取计算过程中需要的参数。ContextMap对象内部可能封装了一个普通的POJO对象,也可以是直接传递的Map对象,当然从外部是看不出来的,因为都是使用Map的接口来读取数据。
sqlSource
在Mybatis中,每一个select|insert|update|delete标签,都会被解析为一个MappedStatement对象,SqlSource就是MappedStatement对象中一个属性,其最终执行的sql字符串就是由SqlSource提供的。
public interface SqlSource {
BoundSql getBoundSql(Object parameterObject);
}
sqlSource为一个接口,只有一个方法通过传入执行parameterObject参数返回BoundSql
sqlSource的实现有6中
sqlSource的实现
- DynamicSqlSource:处理动态sql。
- RawSqlSource:处理静态sql,其内部装饰StaticSqlSource。
- StaticSqlSource:处理静态sql,无论是静态sql,还是动态sql,最终的处理结果,都是静态sql。
- ProviderSqlSource:处理注解Annotation形式的sql。
DynamicSqlSource和StaticSqlSource的最大区别在于:StaticSqlSource的String sql,可以直接获取使用,而DynamicSqlSource的String sql需要逐一根据条件解析并拼接出最终的sql,方能使用。
DynamicSqlSource
DynamicSqlSource中的SqlNode rootSqlNode属性,通常都是MixedSqlNode对象(完全是静态sql时,可能是一个StaticTextSqlNode),而MixedSqlNode对象又保存了所有的List集合,这也是通过一个rootSqlNode,就能找到所有SqlNode的深层原因