Mybatis的SqlSource & SqlNode & BoundSql

Study link

MyBatis SqlSource analysis

[Mybatis] SqlSource#getBoundSql of Mybatis source code to obtain precompiled SQL

Detailed explanation of SqlSource parsing process in Mybatis

Mybatis TypeHandler analysis

diagram

Mybatis的SqlSource&SqlNode - processon

insert image description here

DynamicSqlSource

public class DynamicSqlSource implements SqlSource {
    
    

  	private final Configuration configuration;
  	
  	private final SqlNode rootSqlNode;

  	public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
    
    
    	this.configuration = configuration;
    	this.rootSqlNode = rootSqlNode;
  	}
  	
	/**
	 * 获取一个BoundSql对象
	 *
	 * DynamicSqlSource和 RawSqlSource都会转化为 StaticSqlSource,然后才能给出一个 BoundSql对象。
	 *
	 * @param parameterObject 参数对象
	 * @return BoundSql对象
	 */
	@Override
	public BoundSql getBoundSql(Object parameterObject) {
    
    
	
	    // 创建DynamicSqlSource的辅助类,用来记录DynamicSqlSource解析出来的SQL片段信息和参数信息
	    DynamicContext context = new DynamicContext(configuration, parameterObject);
	    
	    // 这里会从根节点开始,对节点逐层调用apply方法,经过这一步后,动态节点"${}"都被替换,这样 DynamicSqlSource便不再是动态的,而是静态的。
	    rootSqlNode.apply(context);
	    
	    // 处理占位符,汇总参数信息
	    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);	    
	    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
	    
	    // 使用SqlSourceBuilder处理"#{}",将其转化为"?",然后创建ParameterMapping,最终生成了StaticSqlSource对象
	    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
	    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
	    
	    // 把context.getBindings()的参数放到boundSql的metaParameters中进行保存
	    context.getBindings().forEach(boundSql::setAdditionalParameter);
	    
	    return boundSql;
	}

}	

RawSqlSource

By observing the difference between RawSqlSource and DynamicSqlSource, you can notice:

  1. First, you need to determine whether a sql tag statement written in mapper.xml is a dynamic sqlSource or rawSqlSource? This has a parsing process in XMLScriptBuilder#parseDynamicTags. Anything containing ${} or dynamic tags (such as trim, where, if and other 9 tags) are dynamic.
  2. What are the similarities and differences between DynamicSqlSource and RawSqlSource? They all have a parsing phase and a running phase. During the parsing phase, RawSqlSource has already obtained StaticSqlSource in the construction method (because there is no content that needs dynamic parsing. Note that #{} does not belong to the category of dynamic parsing, only those 9 A dynamic tag and ${} are considered dynamic), and DynamicSqlSource needs to be passed parameters during the running phase to obtain StaticSqlSource, and the sql needs to be determined based on actual parameters passed, so it is called dynamic. So they will eventually obtain StaticSqlSource and participate in the subsequent process.
public class RawSqlSource implements SqlSource {
    
    

  private final SqlSource sqlSource;

  public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
    
    
    this(configuration, getSql(configuration, rootSqlNode), parameterType);
  }

  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<>());
  }

  private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
    
    
    DynamicContext context = new DynamicContext(configuration, null);
    rootSqlNode.apply(context);
    return context.getSql();
  }

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    
    
    return sqlSource.getBoundSql(parameterObject);
  }

}

GenericTokenParser#parse

/**
 * 该方法主要 完成占位符的定位工作,然后把占位符的替换工作交给与其关联的 TokenHandler 处理
 * @param text
 * @return
 */
public String parse(String text) {
    
    
    if (text == null || text.isEmpty()) {
    
    
        return "";
    }
    // search open token
    // 查找openToken的位置
    int start = text.indexOf(openToken);
    if (start == -1) {
    
    
        return text;
    }
    char[] src = text.toCharArray();
    int offset = 0;
    final StringBuilder builder = new StringBuilder();
    StringBuilder expression = null;
    // 当存在openToken时,才继续处理
    while (start > -1) {
    
    
        if (start > 0 && src[start - 1] == '\\') {
    
    
            // this open token is escaped. remove the backslash and continue.
            builder.append(src, offset, start - offset - 1).append(openToken);
            offset = start + openToken.length();
        } else {
    
    
            // found open token. let's search close token.
            if (expression == null) {
    
    
                expression = new StringBuilder();
            } else {
    
    
                expression.setLength(0);
            }
            // 拼接从0到openToken之前的字符
            builder.append(src, offset, start - offset);
            // 设置offset值为openToken结束的位置
            offset = start + openToken.length();
            // 从offset值之后开始找第一个closeToken的位置
            int end = text.indexOf(closeToken, offset);
            // 如果存在,则继续处理
            while (end > -1) {
    
    
                if (end > offset && src[end - 1] == '\\') {
    
    
                    // this close token is escaped. remove the backslash and continue.
                    expression.append(src, offset, end - offset - 1).append(closeToken);
                    offset = end + closeToken.length();
                    // 继续查找当前closeToken之后的closeToken
                    end = text.indexOf(closeToken, offset);
                } else {
    
    
                    expression.append(src, offset, end - offset);
                    break;
                }
            }
            // 如果不存在
            if (end == -1) {
    
    
                // close token was not found.
                // 拼接剩余的字符
                builder.append(src, start, src.length - start);
                // 设置offset为字符数组的长度
                offset = src.length;
            } else {
    
    
                /**
                 * DynamicCheckerTokenParser:如果存在,则设置当前SQL为动态的
                 * BindingTokenParser:获取$变量的值
                 * ParameterMappingTokenHandler:将#替换为?,并构建参数映射ParameterMapping
                 */
                builder.append(handler.handleToken(expression.toString()));
                // 设置offset值为closeToken结束的位置
                offset = end + closeToken.length();
            }
        }
        start = text.indexOf(openToken, offset);
    }
    // 拼接剩余的字符
    if (offset < src.length) {
    
    
        builder.append(src, offset, src.length - offset);
    }
    return builder.toString();
}

Guess you like

Origin blog.csdn.net/qq_16992475/article/details/132380737