1、使用ognl表达式,替换掉 ${...} 类型的值 2、根据调用数据《擦除》xml标签 3、替换占位符 井{..} 为 占位符 ?,同时解析出绑定的参数 4、创建 BoundSql 对象
org.apache.ibatis.scripting.xmltags.DynamicSqlSource
org.apache.ibatis.scripting.xmltags.TrimSqlNode
org.apache.ibatis.scripting.xmltags.IfSqlNode
org.apache.ibatis.scripting.xmltags.ForEachSqlNode
。。。。
package cn.java.demo.ibatis.internal.sqlnode;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.builder.SqlSourceBuilder;
import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.scripting.xmltags.*;
import org.apache.ibatis.session.Configuration;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.util.*;
/**
*/
@Slf4j
public class SqlNodeDemo {
public static void main(String[] args) {
Configuration configuration = new Configuration();
configuration.setDatabaseId("");
String xmlStr =
"<update id=\"updateByExampleSelective\" parameterType=\"map\">\n" +
" update orders\n" +
" <set>\n" +
" <if test=\"record.id != null\">\n" +
" id = #{record.id,jdbcType=BIGINT},\n" +
" </if>\n" +
" <if test=\"record.userId != null\">\n" +
" user_id = #{record.userId,jdbcType=BIGINT},\n" +
" </if>\n" +
" <if test=\"record.orderNo != null\">\n" +
" order_no = #{record.orderNo,jdbcType=VARCHAR},\n" +
" </if>\n" +
" lock_version = lock_version + 1,\n" +
" <if test=\"record.updatedAt != null\">\n" +
" updated_at = #{record.updatedAt,jdbcType=TIMESTAMP},\n" +
" </if>\n" +
" </set>\n" +
" <if test=\"_parameter != null\">\n" +
" <where>\n" +
" <foreach collection=\"example.oredCriteria\" item=\"criteria\" separator=\"or\">\n" +
" <if test=\"criteria.valid\">\n" +
" <trim prefix=\"(\" prefixOverrides=\"and\" suffix=\")\">\n" +
" <foreach collection=\"criteria.criteria\" item=\"criterion\">\n" +
" <choose>\n" +
" <when test=\"criterion.noValue\">\n" +
" and ${criterion.condition}\n" +
" </when>\n" +
" <when test=\"criterion.singleValue\">\n" +
" and ${criterion.condition} #{criterion.value}\n" +
" </when>\n" +
" <when test=\"criterion.betweenValue\">\n" +
" and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}\n" +
" </when>\n" +
" <when test=\"criterion.listValue\">\n" +
" and ${criterion.condition}\n" +
" <foreach close=\")\" collection=\"criterion.value\" item=\"listItem\" open=\"(\" separator=\",\">\n" +
" #{listItem}\n" +
" </foreach>\n" +
" </when>\n" +
" </choose>\n" +
" </foreach>\n" +
" </trim>\n" +
" </if>\n" +
" </foreach>\n" +
" </where>\n" +
" </if>\n" +
" </update>";
/**
* @see org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource(org.apache.ibatis.session.Configuration, java.lang.String, java.lang.Class)
*/
XPathParser parser = new XPathParser(xmlStr, false, null, new XMLMapperEntityResolver());
XNode contents = parser.evalNode("/update");
/**
* @see org.apache.ibatis.scripting.xmltags.XMLScriptBuilder
*/
XMLScriptBuilder xmlScriptBuilder = new XMLScriptBuilder(configuration, contents);
SqlSource sqlSource = xmlScriptBuilder.parseScriptNode();
/**
* 获取BoundSql
*/
Map parameterObject = getParameterObject();
sqlSource.getBoundSql(parameterObject);
}
static Map getParameterObject() {
String parameterObjectStr = "{\"record\":{\"id\":1,\"userId\":1,\"orderNo\":\"orderNo_1\",\"updatedAt\":\"2020-01-01 01:01:01\"},\"example\":{\"oredCriteria\":[{\"valid\":true,\"criteria\":[{\"noValue\":true,\"condition\":\"id is not null\"},{\"singleValue\":true,\"condition\":\"userId = \",\"value\":\"1\"},{\"betweenValue\":true,\"condition\":\"id between\",\"value\":\"1\",\"secondValue\":\"2\"},{\"listValue\":true,\"condition\":\"order_no in\",\"value\":[\"1\",\"2\"]}]}]}}";
Map parameterObject = JSON.parseObject(parameterObjectStr, Map.class);
return parameterObject;
}
/**
* @see org.apache.ibatis.scripting.xmltags.DynamicSqlSource
*/
static class DynamicSqlSource implements SqlSource {
private Configuration configuration;
private SqlNode rootSqlNode;
public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
this.configuration = configuration;
this.rootSqlNode = rootSqlNode;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
/**
* 1、使用ognl表达式,替换掉 ${...} 类型的值
* 2、根据调用数据《擦除》xml标签
*/
DynamicContext context = new DynamicContext(configuration, parameterObject);
rootSqlNode.apply(context);
log.info("sql after do ognl, sql = {}", context.getSql());
/**
* 3、替换占位符 井{..} 为 占位符 ?,同时解析出绑定的参数
*/
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
/**
* 4、创建 BoundSql 对象
*/
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
}
return boundSql;
}
}
/**
* @see org.apache.ibatis.scripting.xmltags.XMLScriptBuilder
*/
static class XMLScriptBuilder {
protected final Configuration configuration;
private XNode context;
private boolean isDynamic;
public XMLScriptBuilder(Configuration configuration, XNode context) {
this.configuration = configuration;
this.context = context;
}
public SqlSource parseScriptNode() {
List<SqlNode> contents = parseDynamicTags(context);
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
SqlSource sqlSource = null;
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
return sqlSource;
}
private List<SqlNode> parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<SqlNode>();
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
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 = nodeHandlers(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
handler.handleNode(child, contents);
isDynamic = true;
}
}
return contents;
}
NodeHandler nodeHandlers(String nodeName) {
Map<String, NodeHandler> map = new HashMap<String, NodeHandler>();
map.put("trim", new TrimHandler());
map.put("where", new WhereHandler());
map.put("set", new SetHandler());
map.put("foreach", new ForEachHandler());
map.put("if", new IfHandler());
map.put("choose", new ChooseHandler());
map.put("when", new IfHandler());
map.put("otherwise", new OtherwiseHandler());
map.put("bind", new BindHandler());
return map.get(nodeName);
}
private interface NodeHandler {
void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);
}
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);
}
}
private class TrimHandler implements NodeHandler {
public TrimHandler() {
// Prevent Synthetic Access
}
@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);
}
}
private class WhereHandler implements NodeHandler {
public WhereHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
List<SqlNode> contents = parseDynamicTags(nodeToHandle); // 递归
MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
targetContents.add(where);
}
}
private class SetHandler implements NodeHandler {
public SetHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
List<SqlNode> contents = parseDynamicTags(nodeToHandle); // 递归
MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
SetSqlNode set = new SetSqlNode(configuration, mixedSqlNode);
targetContents.add(set);
}
}
private class ForEachHandler implements NodeHandler {
public ForEachHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
List<SqlNode> contents = parseDynamicTags(nodeToHandle); // 递归
MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
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);
}
}
private class IfHandler implements NodeHandler {
public IfHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
List<SqlNode> contents = parseDynamicTags(nodeToHandle);
MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
String test = nodeToHandle.getStringAttribute("test");
IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
targetContents.add(ifSqlNode);
}
}
private class OtherwiseHandler implements NodeHandler {
public OtherwiseHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
List<SqlNode> contents = parseDynamicTags(nodeToHandle);
MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
targetContents.add(mixedSqlNode);
}
}
private class ChooseHandler implements NodeHandler {
public ChooseHandler() {
// Prevent Synthetic Access
}
@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;
}
}
}
}