Análisis de código fuente de mybaits (7) SqlSource y análisis dinámico de SQL análisis detallado

 

SqlSource y mybaits de análisis dinámico de sql
    se pueden interpolar por # {} o empalmar por $ {}. Otra característica más poderosa es que puede analizar dinámicamente declaraciones de sql, independientemente del análisis dinámico o estático, se pasa sql Se empaquetan diferentes implementaciones de SqlSource Este artículo explicará en profundidad el análisis dinámico de SQL.

    1. Análisis de análisis y carga de SQL

1. El método de análisis principal XMLScriptBuilder's parseScriptNode.

 a) Analizar el nodo Nodo de la etiqueta de instrucción <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) Analizar sqlparseDynamicTags dinámicos

 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. Resumen del proceso
        A partir del proceso de análisis principal, sabemos que el análisis sql de mybaits es finalmente analizar el nodo nodo en la etiqueta de la declaración en SqlNode, y SqlNode contiene nodos de texto, si nodos, etc., si solo tenemos SqlNode estático Luego, finalmente, estos SqlNode se empaquetarán como StaticSqlSource, de lo contrario, se empaquetarán como DynamicSqlSource. Primero, introduzcamos la estructura de clases principal de SqlNode y SqlSource para facilitar el análisis detallado posterior.

La siguiente figura muestra la combinación de nodos sql después del análisis.

      3. Introducción a SqlNode

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

     Este es el método de interfaz de SqlNode. El método de aplicación es principalmente para procesar los fragmentos de SQL de SqlNode para formar el uso final de las sentencias preparadas de SQL. El análisis de SQL dinámico se basa principalmente en el análisis OGNL. Primero imaginamos DynamicContext como un mapa para almacenar parámetros operativos externos, que es el propósito de empalmar nodos SQL.        

 La implementación principal:
             IfSqlNode : si es un paquete de nodo, la lógica de aplicación es determinar si la expresión se analiza con el parámetro operativo OGNL y decidir si empalmar este nodo sql en el futuro.
             TextSqlNode : nodo de texto, que contiene $ {} marcadores de posición, aplicar lógica es ejecutar el parámetro OGNL para analizar y reemplazar los marcadores de posición .
             StaticTextSqlNode : nodo de texto estático puro, solo almacena fragmentos sql.
             MixedSqlNode : una lista de múltiples nodos SqlNode. La lógica de aplicación es llamar a cada SqlNode para aplicar.
             ...
        El método de aplicación de análisis final de SqlNode en tiempo de ejecución es usar OGNL para procesar los parámetros de marcador de posición $ {} para los nodos de texto y agregar nodos de texto estático directamente. Si el nodo dinámico es un nodo if, entonces use OGNL para analizar el valor de prueba con parámetros operativos y devuelve verdadero. Simplemente agregue el sql de este nodo. La lógica de aplicación de otros nodos dinámicos es similar. Además, todo el sql se empalma y se procesarán # {} marcadores de posición. ¿Este es un reemplazo directo? para analizar paramterMapping. Se explicará cuando se introduzca la clase SqlSource.

  4 Introducción a SqlSource

Interfaz de nivel superior:

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

Realización principal:

        DynamicSqlSource : El sqlSource dinámico contiene un MixedSqlNode combinado. Llame a aplicar y el sql empalmado también se procesará. # {}
        StaticSqlSource : El sqlSource estático contiene directamente la declaración final preparada.
        RawSqlSource : Empaquetando sqlSource estático, principalmente procesando # {}, analizado en? A partir de esto, y construyendo ParameterMapping.

Clase dependiente:
        BoundSql : sql final del paquete, paramterMapping, valor del parámetro.

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

 

     En segundo lugar, la etapa de uso de SqlSource

Después de analizar SqlSource, ¿dónde se almacena? ¿Dónde se usa? Entramos en la interpretación de la fase de uso de SqlSource.

 1. ¿Dónde se almacena?
     Ya sea que SqlSource sea SQL dinámico o SQL no dinámico, su ubicación de almacenamiento debe corresponder a un objeto de declaración, y el objeto de declaración es MappedStatement,

    public final class MappedStatement {
          private SqlSource sqlSource;

2. ¿Dónde se usa?

Revisamos el proceso de ejecución principal de mybaits, creamos Sqlsession, al ejecutar una consulta, sqlsession encontrará el MappedStatement específico de acuerdo con el espacio de nombres + id del método, sacamos el SqlSource del MappedStatement y combinamos los parámetros pasados ​​externamente, y procesamos el sqlSource para analizarlo en una declaración preparada + paquete de mapeo de parámetros BoundSql es el proceso de uso específico.

 Método selectList de sqlsession

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

      Método de consulta del ejecutor

 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. Análisis dinámico de sqlNode

  Desde getBoundSql de SqlSource, puede ver que el sqlnode superior ejecutará apply, y cada nodo de elemento está compuesto por MixedNode. Esto llamará al método de aplicación de cada sqlNode de arriba a abajo. El parámetro DynamicContext del método de aplicación es responsable de empaquetar los parámetros de SQL en tiempo de ejecución, y es responsable de empalmar declaraciones SQL, y proporciona algunas extensiones de ONGL. Veamos el método de aplicación de la siguiente parte de sqlNode.

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

}

(Se agregará un caso de prueba básico después del uso de OGNL)

4. La realización concreta de parámetros de análisis

El método getBoundSql de sqlsource dinámico se ha introducido anteriormente. Nos preocupa cómo se analizan los parámetros en ParameterMapping en tiempo de ejecución.

// fragmento del método getBoundSql

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

  // analizar # {}

    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 se ha introducido antes, es para llamar al procesamiento del procesador apropiado de acuerdo con el marcador de posición, miramos directamente la implementación del procesador.

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


  para resumir
    

    La implementación de SQL dinámico de mybaits se divide en cuando se crea, los fragmentos de SQL se empaquetan en nodos SqlNode individuales. Si este nodo SqlNode es analizado por el nodo de elemento, entonces es un SqlNode combinado; de lo contrario, es un solo SqlNode, y el SqlNode se empaquetará en DynamicSqlSource Si es tiempo de ejecución, se llamará al método de aplicación del nodo superior, es decir, todos los nodos ejecutarán el método de aplicación y finalmente usarán Ognl para analizar los parámetros reales de estos puntos dinámicos, y También hay un proceso de empalme sql en este proceso. Después de empalmar todo Después del nodo sql, finalmente se realiza el proceso de # {}, se analiza ParameterMapping y # {} se reemplaza con?, y la declaración preparada y ParameterMapping son finalmente empaquetado en BoundSql para su uso posterior. '

 

 Suplemento: prueba de uso básico de 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 + "]";
	}
	
}

 

 

 

 

fin !

 

 

Supongo que te gusta

Origin blog.csdn.net/shuixiou1/article/details/113732103
Recomendado
Clasificación