Mybaitsソースコード分析(7)SqlSourceおよび動的SQL分析の詳細分析

 

SqlSourceと動的SQL解析
    mybaitsは、#{}で補間するか、$ {}でスプライスできます。もう1つの最も強力な機能は、動的分析または静的分析に関係なく、SQLステートメントを動的に解析できることです。sqlは渡されます。さまざまなSqlSource実装がパッケージ化されています。この記事では、動的SQL解析について詳しく説明します。

    1.SQLの解析と読み込みの分析

1.メインの解析メソッドXMLScriptBuilderのparseScriptNode。

 a)<select>ステートメントタグのノードノードを解析します

	  public SqlSource parseScriptNode() {
	    // 核心: 解析动态sql , 并且动态sql的存储形式是以SqlNode节点列表的方式
	    List<SqlNode> contents = parseDynamicTags(context);
	    // SqlNode的节点
	    MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
	    SqlSource sqlSource = null;
	    if (isDynamic) {  // 创建动态SqlSource
	      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
	    } else {  // 创建静态SqlSource (RawSqlSource是StaticSqlSource简单包装)
	      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
	    }
	    return sqlSource;
	  }

   b)動的sqlparseDynamicTagsを解析します

 private List<SqlNode> parseDynamicTags(XNode node) {
	    List<SqlNode> contents = new ArrayList<SqlNode>();
	    NodeList children = node.getNode().getChildNodes(); // <select>的所有子节点
	    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 textSqlNode = new TextSqlNode(data);
	        if (textSqlNode.isDynamic()) {
	          contents.add(textSqlNode);
	          isDynamic = true;
	        } else {
              // 否则,另外包装成StaticTextSqlNode节点
	          contents.add(new StaticTextSqlNode(data));
	        }
	      // 如果是元素节点
	      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
	        String nodeName = child.getNode().getNodeName();
	        NodeHandler handler = nodeHandlers.get(nodeName);
	        if (handler == null) {
	          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
	        }
	       	// 处理此if等动态节点,内部会递归调用parseDynamicTags,将解析的List<SqlNode>包装成MixedSqlNode。
	        handler.handleNode(child, contents);
	        isDynamic = true; // 只要存在一个动态形式,那么isDynamic就是动态的。
	      }
	    }
	    return contents;
	  }

 2.プロセスの概要
        メインの分析プロセスから、mybaitsのSQL分析は、ステートメントラベルのノードノードを最終的に解析してSqlNodeにすることであり、静的SqlNodeしかない場合は、ノードなどの場合、SqlNodeにテキストノードが含まれることがわかります。 、最後に、これらのSqlNodeはStaticSqlSourceとしてパッケージ化されます。それ以外の場合は、DynamicSqlSourceとしてパッケージ化されます。最初にSqlNodeとSqlSourceのメインクラス構造を紹介して、その後の詳細な分析を容易にします。

次の図は、解析後のSQLノードの組み合わせを示しています。

      3.SqlNodeの概要

public interface SqlNode {
	 	  // 备注: apply是sql运行时期用的,因为这个时候才有sql参数,才可以解析OGNL,返回false,不进行后续节点的解析。
		  boolean apply(DynamicContext context);
		}

     これは、SqlNodeのインターフェイスメソッドです。applyメソッドは、主にSqlNodeのSQLフラグメントを処理して、SQLプリペアドステートメントの最終的な使用法を形成します。動的SQLの分析は、主にOGNL分析に依存します。最初に、DynamicContextを、SQLノードをスプライシングする目的である外部操作パラメーターを格納するためのマップとして想像します。        

 主な実装:
             IfSqlNode:ノードパッケージングの場合、適用ロジックは、式が操作パラメーターOGNLで解析されるかどうかを決定し、将来このノードsqlをスプライスするかどうかを決定することです。
             TextSqlNode:$ {}プレースホルダーを含むテキストノードは、パラメーターOGNLを実行してプレースホルダーを解析および置換するロジックを適用します
             StaticTextSqlNode:純粋な静的テキストノード。SQLフラグメントのみを保存します。
             MixedSqlNode:複数のSqlNodeノードのリスト。適用ロジックは、各SqlNodeを呼び出して適用することです。
             ...実行時の
        SqlNodeの最終的な分析適用メソッドは、 OGNL使用してテキストノードの$ {}プレースホルダーパラメーターを処理し、静的テキストノードを直接追加することです。動的ノードがifノードの場合は、OGNLを使用してテスト値を解析します。このノードのsqlを追加するだけです。他の動的ノードの適用ロジックも同様です。さらに、すべてのsqlがスプライスされ、#{}プレースホルダーが処理されます。これは、解析するための直接の置き換えですか? paramterMapping。SqlSourceクラスが導入されたときに説明されます。

  4SqlSourceの概要

トップレベルのインターフェース:

	public interface SqlSource {
		  BoundSql getBoundSql(Object parameterObject);
		}

主な実現:

        DynamicSqlSource:動的sqlSourceには結合されたMixedSqlNodeが含まれます。applyを呼び出すと、スプライスされたsqlも処理されます#{}
        StaticSqlSource:静的sqlSourceには、最終的なプリペアドステートメントが直接含まれます。
        RawSqlSource:静的sqlSourceをパッケージ化し、主に#{}を処理し、これから解析して、ParameterMappingを構築します。

依存クラス:
        BoundSql:パッケージの最終SQL、paramterMapping、パラメーター値。

public class DynamicSqlSource implements SqlSource {
          private Configuration configuration;
          private SqlNode rootSqlNode;  // 顶级SqlNode
          public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
            this.configuration = configuration;
            this.rootSqlNode = rootSqlNode;
          }
          public BoundSql getBoundSql(Object parameterObject) {
            DynamicContext context = new DynamicContext(configuration, parameterObject); // 实际参数添加到DynamicContext
            // 所有SqlNode的apply操作,动态node采用CGNL解析,最后拼接sql到context的sql属性
            rootSqlNode.apply(context); 
            SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
            Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
            // 因为上面处理的sql没有处理#{}的占位,并且#{}可以解析出ParameterMapping,替换成?
            SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
            BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
            // 实际参数放到_parameter为key的AdditionalParameter的Map,添加到boundSql
            for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) { 
              boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
            }
            return boundSql;
          }
        }

 

     第二に、SqlSourceの使用段階

SqlSourceが解析された後、どこに保存されますか?どこで使われていますか?SqlSource使用フェーズの解釈に入ります。

 1.どこに保管されていますか?
     SqlSourceが動的SQLであるか非動的SQLであるかにかかわらず、その格納場所はステートメントオブジェクトに対応している必要があり、ステートメントオブジェクトはMappedStatementです。

    public final class MappedStatement {
          private SqlSource sqlSource;

2.どこで使用されますか?

mybaitsのメイン実行プロセスを確認し、Sqlsessionを作成します。クエリを実行すると、sqlsessionは名前空間+メソッドIDに従って特定のMappedStatementを検索し、MappedStatementからSqlSourceを取り出して、外部から渡されたパラメーターを結合し、sqlSourceを次のように処理します。プリペアドステートメント+パラメータマッピングパッケージに解析します。BoundSqlは特定の使用プロセスです。

 SqlsessionのselectListメソッド

 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
	      MappedStatement ms = configuration.getMappedStatement(statement); // 取出MappedStatement
	      List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
	      return result;
	  }

      エグゼキュータのクエリメソッド

 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
	    BoundSql boundSql = ms.getBoundSql(parameter); // 解析最终预处理语句+ParameterMapping
	    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
	    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
	  }

   3.動的sqlNode分析

  SqlSourceのgetBoundSqlから、最上位のsqlnodeがapplyを実行し、各要素ノードがMixedNodeで構成されていることがわかります。これにより、各sqlNodeのapplyメソッドが上から下に呼び出されます。applyメソッドのパラメーターDynamicContext担当します。ランタイムSQLパラメータをパッケージ化し、SQLステートメントのスプライシングを担当し、ONGLのいくつかの拡張機能を提供します。sqlNodeの次の部分のapplyメソッドを見てみましょう。

DynamicContext 拼接sql的方法 
public void appendSql(String sql) {
    sqlBuilder.append(sql);
    sqlBuilder.append(" ");
  }

  public String getSql() {
    return sqlBuilder.toString().trim();
  }
组合节点,内部调用每个组合节点apply
public class MixedSqlNode implements SqlNode {
  private List<SqlNode> contents;

  public MixedSqlNode(List<SqlNode> contents) {
    this.contents = contents;
  }

  public boolean apply(DynamicContext context) {
    for (SqlNode sqlNode : contents) {
      sqlNode.apply(context);
    }
    return true;
  }
}
文本节点(含${})
public class TextSqlNode implements SqlNode {
  private String text;

  public TextSqlNode(String text) {
    this.text = text;
  }
  
  public boolean apply(DynamicContext context) {
   // 处理${}
    GenericTokenParser parser = createParser(new BindingTokenParser(context));
   // 拼接sql
    context.appendSql(parser.parse(text));
    return true;
  }
  
  // 处理${}
  private GenericTokenParser createParser(TokenHandler handler) { 
    return new GenericTokenParser("${", "}", handler);
  }
  // handler方法实现
  private static class BindingTokenParser implements TokenHandler {

    private DynamicContext context;

    public BindingTokenParser(DynamicContext context) {
      this.context = context;
    }
    
    public String handleToken(String content) {
      // 从map的_parameter属性取出外部参数
      Object parameter = context.getBindings().get("_parameter");
      if (parameter == null) {
        context.getBindings().put("value", null);
      } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
        // 设置简单类型的参数到value
        context.getBindings().put("value", parameter);
      }
      // 用ognl取值
      Object value = OgnlCache.getValue(content, context.getBindings());
      return (value == null ? "" : String.valueOf(value)); // issue #274 return "" instead of "null"
    }
  }
if节点
public class IfSqlNode implements SqlNode {
  private ExpressionEvaluator evaluator; 
  private String test;
  private SqlNode contents;

  public IfSqlNode(SqlNode contents, String test) {
    this.test = test;
    this.contents = contents;
    this.evaluator = new ExpressionEvaluator();
  }

  public boolean apply(DynamicContext context) {
    // ognl取test的表达式计算结果
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
      // 此if节点的内部MixedNode全部applye
      contents.apply(context);
      return true;
    }
    return false;
  }

}
纯静态节点StaticTextSqlNode
public class StaticTextSqlNode implements SqlNode {
  private String text;

  public StaticTextSqlNode(String text) {
    this.text = text;
  }
 
  public boolean apply(DynamicContext context) {
    // 直接拼接sql
    context.appendSql(text);
    return true;
  }

}

(OGNLの使用後に基本的なテストケースが追加されます)

4.構文解析パラメータの具体的な実現

動的sqlsourceのgetBoundSqlメソッドは上記で紹介されています。実行時にパラメーターがParameterMappingに解析される方法について懸念しています。

// getBoundSqlメソッドフラグメント

 SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());

  // parse#{}

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

 // GenericTokenParserは以前に導入されました。プレースホルダーに従って適切なプロセッサー処理を呼び出すため、プロセッサーの実装を直接確認します。

 public String handleToken(String content) {
	      parameterMappings.add(buildParameterMapping(content)); // 每找到一个#{}创建一个parameterMapping
	      return "?";
	    }
	
	    private ParameterMapping buildParameterMapping(String content) {
	      Map<String, String> propertiesMap = parseParameterMapping(content); // 创建一个map
	      String property = propertiesMap.get("property");
	      Class<?> propertyType;
	      if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
	        propertyType = metaParameters.getGetterType(property);
	      } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
	        propertyType = parameterType; // 存在注册类型处理器的类型直接赋值给propertyType
	      } else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
	        propertyType = java.sql.ResultSet.class;
	      } else if (property != null) { // 不存在的类型,但是值不为空
	        MetaClass metaClass = MetaClass.forClass(parameterType); // 反射class
	        if (metaClass.hasGetter(property)) { // 存在get方法
	          propertyType = metaClass.getGetterType(property); // 设置类型为get方法的类型
	        } else {
	          propertyType = Object.class; // 其他情况就是Object的类型
	        }
	      } else {
	        propertyType = Object.class;
	      }
	           拿到参数值和参数类型,下面就是构建ParameterMapping的过程。
	      ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
	      // .....
	      return builder.build();
	    }


  総括する
    

    mybaitsの動的SQL実装は、作成時に分割され、SQLフラグメントは個々のSqlNodeノードにパッケージ化されます。このSqlNodeノードが要素ノードによって解析される場合は結合されたSqlNodeであり、それ以外の場合は単一のSqlNodeです。 SqlNodeはDynamicSqlSourceにパッケージ化されます。実行時に、最上位ノードのapplyメソッドが呼び出されます。つまり、すべてのノードがapplyメソッドを実行し、最後にOgnlを使用してこれらの動的ポイントの実際のパラメーターを解析します。このプロセスにはsqlをスプライシングするプロセスもあります。すべてをスプライシングした後sqlノードの後、#{}のプロセスが最終的に実行され、ParameterMappingが解析され、#{}が?に置き換えられ、準備されたステートメントとParameterMappingは次のようになります。最終的に、後で使用するためにBoundSqlにパッケージ化されます。'

 

 補足:Ognlの基本的な使用テスト。

/**
 * Ognl表达式测试
 * 
 * 概述.Ognl表达式语言
 * 
 * 从语言角度来说:它是一个功能强大的表达式语言,用来获取和设置 java 对象的属性 ,
 * 它旨在提供一个更高抽象度语法来对 java 对象图进行导航。另外,java 中很多可以
 * 做的事情,也可以使用 OGNL 来完成,例如:列表映射和选择。对于开发者来说,使用 
 * OGNL,可以用简洁的语法来完成对 java 对象的导航。通常来说:通过一个“路径”来完
 * 成对象信息的导航,这个“路径”可以是到 java bean 的某个属性,或者集合中的某个索
 * 引的对象,等等,而不是直接使用 get 或者 set 方法来完成。
 * 
 * 首先来介绍下OGNL的三要素:
 * 
 * 1、表达式:表达式(Expression)是整个OGNL的核心内容,所有的OGNL操作都是针对表达式解析
 * 后进行的。通过表达式来告诉OGNL操作到底要干些什么。因此,表达式其实是一个带有语法含义
 * 的字符串,整个字符串将规定操作的类型和内容。OGNL表达式支持大量的表达式,如“链式访问对象”、表达式计算s
 * 
 * 2、Root对象:
 *  OGNL的Root对象可以理解为OGNL的操作对象。当我们指定了一个表达式的时候,我们需要指定这个表达式针对的是
 * 哪个具体的对象。而这个具体的对象就是Root对象,这就意味着,如果有一个OGNL表达式,那么我们需要针对Root
 * 对象来进行OGNL表达式的计算并且返回结果。
 * 
 * 3、上下文环境
 *  有个Root对象和表达式,我们就可以使用OGNL进行简单的操作了,如对Root对象的赋值与取值操作。但是,实际上在
 * OGNL的内部,所有的操作都会在一个特定的数据环境中运行。这个数据环境就是上下文环境(Context)。OGNL的上下
 * 文环境是一个Map结构,称之为OgnlContext。Root对象也会被添加到上下文环境当中去。
 * 
 */
public class LearnOgnl {

	/**
	 * 1、访问root对象, 使用Api是Ognl.getValue(express, root)
	 */
	@Test
	public void test1() {
		Person person = new Person("id", "name");
		MetaObject meta = MetaObject.forObject(person, new DefaultObjectFactory(), new DefaultObjectWrapperFactory());
		meta.setValue("child.name", "haha"); // 简化构建对象操作,用MetaObject创建子对象属性。
		try {
			System.out.println(Ognl.getValue("name", person)); // name
			System.out.println(Ognl.getValue("child", person)); // Person [id=null, name=haha, children=[], child=null]
			System.out.println(Ognl.getValue("child.name", person)); // haha
		} catch (OgnlException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 2、上下文对象访问,上下文对象访问需要用#获取,使用Api是Ognl.getValue(express, context, root)
	 */
	@Test
	public void test2() throws OgnlException {
		Person person = new Person("id", "name");
		MetaObject meta = MetaObject.forObject(person, new DefaultObjectFactory(), new DefaultObjectWrapperFactory());
		meta.setValue("child.name", "haha");
		Map<String, Object> context = new HashMap<String, Object>();
		context.put("person", person);
		System.out.println(Ognl.getValue("#person.id", context, person)); // id
		System.out.println(Ognl.getValue("#person.name", context, person)); // name
		System.out.println(Ognl.getValue("name", context, person)); // name
	}
	
	/**
	 * 3、方法调用, ognl可以调用方法,并且传入上下文参数。
	 */
	@Test
	public void test3() throws OgnlException {
		Person person = new Person();
		Map<String, Object> context = new HashMap<String, Object>();
		context.put("name", "张三");
		Ognl.getValue("setName(#name)", context, person);
		System.out.println(Ognl.getValue("getName()", context, person)); // 张三
	}
	
	/**
	 * 4、数组集合的访问 ,从此案例可以看出对一些运算符能够支持,
	 * 运算符常见支持如下:
	 * 减法、乘法、除法、取余 (2+5、6/2、6%2)
	 * 字符串相加 ( "str1" + "str2")
	 * 逻辑判断 (i == j)
	 */
	@Test
	public void test4() {
		Person person = new Person();
		Map<String, Object> context = new HashMap<String, Object>();
		String[] strings = { "aa", "bb" };
		ArrayList<String> list = new ArrayList<String>();
		list.add("aa");
		list.add("bb");
		Map<String, String> map = new HashMap<String, String>();
		map.put("key1", "value1");
		map.put("key2", "value2");
		context.put("list", list);
		context.put("strings", strings);
		context.put("map", map);
		try {
			System.out.println(Ognl.getValue("#strings[0]", context, person));
			System.out.println(Ognl.getValue("#list[0]", context, person));
			System.out.println(Ognl.getValue("#list[0 + 1]", context, person));
			System.out.println(Ognl.getValue("#map['key1']", context, person));
			System.out.println(Ognl.getValue("#map['key' + '2']", context, person));
		} catch (OgnlException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 5、演示mybaits解析test表达的表达式
	 */
	@Test
	public void test() throws OgnlException {
		String express = "begin!=null and end!=null"; 
		Map<String, String> map = new HashMap<String, String>();
		map.put("begin", null);
		map.put("end", null);
		Object ok = Ognl.getValue(express, map, map);
		System.out.println(ok); // false
		map.put("begin", "");
		map.put("end", "");
		ok = Ognl.getValue(express, map, map);
		System.out.println(ok); // true
	}
	
}

class Person{
	
	private String id;
	
	private String name;
	
	private List<Person> children = new ArrayList<Person>();
	
	private Person child;

	public Person(String id, String name) {
		this.id = id;
		this.name = name;
	}

	public Person() {
		super();
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public List<Person> getChildren() {
		return children;
	}

	public void setChildren(List<Person> children) {
		this.children = children;
	}

	public Person getChild() {
		return child;
	}

	public void setChild(Person child) {
		this.child = child;
	}

	@Override
	public String toString() {
		return "Person [id=" + id + ", name=" + name + ", children=" + children + ", child=" + child + "]";
	}
	
}

 

 

 

 

終わり !

 

 

おすすめ

転載: blog.csdn.net/shuixiou1/article/details/113732103