MyBatis06-Paquete de secuencias de comandos de notas "Guía general del código fuente: explicación detallada del código fuente de MyBatis"

Esta serie de artículos son mis notas y un resumen del libro "Guía general de código fuente: explicación detallada del código fuente de MyBatis
" . Este libro se basa en la versión MyBatis-3.5.2. El autor del libro es Brother Yi . El enlace es Weibo del hermano Yi en CSDN. Pero entre todos los artículos que leí, sólo hubo uno que presentaba brevemente este libro. No revela demasiado sobre el encanto del libro. A continuación, registraré mi resumen de aprendizaje. Si el autor cree que he infringido los derechos de autor, comuníquese conmigo para eliminarlo. Gracias nuevamente al hermano Yi por proporcionar materiales de aprendizaje. Esta explicación acompañará toda la serie de artículos. Respete la originalidad . He comprado el libro revisado en WeChat Reading.
Declaración de derechos de autor: este artículo es un artículo original del blogger de CSDN "Architect Yi Ge" y sigue el acuerdo de derechos de autor CC 4.0 BY-SA. Adjunte el enlace de la fuente original y esta declaración al reimprimir.
Enlace original: https://blog.csdn.net/onlinedct/article/details/107306041

MyBatis admite una forma muy flexible de construir declaraciones SQL. Podemos usar foreach, donde, si y otras etiquetas para completar el trabajo de ensamblaje de declaraciones complejas al construir declaraciones SQL.

<select id="selectUsers" resultMap="userMapFull">
    SELECT *
    FROM `user`
    WHERE `id` IN
    <foreach item="id" collection="array" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

La declaración eventualmente se analizará en la declaración SQL más básica antes de que pueda ser recibida por la base de datos. Este proceso de análisis lo completa principalmente el paquete de secuencias de comandos.

1.OGNL

OGNL (lenguaje de navegación de gráficos de objetos) es un potente lenguaje de expresión (Expression Language, EL). A través de él, se pueden completar operaciones como seleccionar objetos de colecciones, leer y escribir propiedades de objetos, llamar a métodos de objetos y clases, evaluar y juzgar expresiones, etc. OGNL se usa ampliamente, por ejemplo, también es posible obtener los atributos de un objeto en un Mapa, que se expresa en lenguaje Java de la siguiente manera.

userMap.get("user2").getName();

La expresión que usa OGNL es:

#user2.name

Además de ser simple y claro, OGNL tiene una mayor adaptabilidad ambiental. Podemos aplicar expresiones OGNL a archivos de configuración, archivos XML, etc., y solo usar OGNL al analizar estos archivos.
OGNL tiene un kit de herramientas Java, siempre que se introduzca, podrá utilizar las funciones de OGNL en Java. Puede utilizar Java para analizar varios documentos que presentan OGNL. Antes de presentar el uso de OGNL, primero introduzcamos tres conceptos importantes que deben abordarse al analizar OGNL.

  • Expresión: es una cadena con significado gramatical y es el contenido central de todo el OGNL. Utilice expresiones para determinar las operaciones OGNL requeridas.
  • Objeto raíz (raíz): puede entenderse como el objeto operado por OGNL. La operación expresada en la expresión se amplía en este objeto.
  • Contexto: el contexto de todo el procesamiento OGNL, que es un objeto de mapa. Antes de realizar el procesamiento OGNL, podemos pasar un entorno de contexto inicializado.
    public static void main(String[] args) {
    
    
        try {
    
    
            User user01 = new User(1, "易哥", 18);
            User user02 = new User(2, "莉莉", 15);
            Map<String, User> userMap = new HashMap<>();
            userMap.put("user1", user01);
            userMap.put("user2", user02);

            // Java方式读取列表中对象的属性值
            String userName = userMap.get("user2").getName();
            System.out.println(userName);

            readAndWriteProps(userMap);
            callFunction();
            runAfterParse(userMap);
        } catch (Exception ex) {
    
    
            ex.printStackTrace();
        }

    }

    public static void readAndWriteProps(Map<String, User> userMap) throws Exception {
    
    
        // 使用表达式读写根对象中信息的示例
        // 该示例中要用到的OGNL函数:
        // getValue(String expression, Object root) :对root内容执行expression中的操作,并返回结果

        // 读取根对象的属性值
        Integer age = (Integer) Ognl.getValue("age", userMap.get("user1"));
        System.out.println("读取根对象属性,得到age:" + age);
        // 设置根对象的属性值
        Ognl.getValue("age = 19", userMap.get("user1"));
        age = (Integer) Ognl.getValue("age", userMap.get("user1"));
        System.out.println("设置根对象属性后,得到age:" + age);

        // 使用表达式读写环境中信息的示例
        // 该示例中要用到的OGNL函数:
        // getValue(String expression, Map context, Object root) :在context环境中对root内容执行expression中的操作,并返回结果

        // 读取环境中的信息
        String userName2 = (String) Ognl.getValue("#user2.name", userMap, new Object());
        System.out.println("读取环境中的信息,得到user2的name:" + userName2);
        // 读取环境中的信息,并进行判断
        Boolean result = (Boolean) Ognl.getValue("#user2.name != '丽丽'", userMap, new Object());
        System.out.println("读取环境中的信息,并进行判断,得到:" + result);
        // 设置环境中的信息
        Ognl.getValue("#user2.name = '小华'", userMap, new Object());
        String newUserName = (String) Ognl.getValue("#user2.name", userMap, new Object());
        System.out.println("设置环境中的信息后,得到user2的name:" + newUserName);
    }
/*
莉莉
读取根对象属性,得到age:18
设置根对象属性后,得到age:19
读取环境中的信息,得到user2的name:莉莉
读取环境中的信息,并进行判断,得到:true
设置环境中的信息后,得到user2的name:小华
*/

Java usa OGNL para completar las operaciones de lectura y escritura de información de objetos e información de contexto, y todo el proceso de operación también admite operaciones lógicas.
OGNL no solo puede leer y escribir información, sino también llamar a métodos en objetos y clases.

// 调用对象方法
 Integer hashCode = (Integer) Ognl.getValue("hashCode()", "yeecode");
 System.out.println("对字符串对象调用hashCode方法得到:" + hashCode);
 // 调用类方法
 Double result = (Double)Ognl.getValue("@java.lang.Math@random()", null);
 System.out.println("调用Math类中的静态方法random,得到:" + result);
/*
对字符串对象调用hashCode方法得到:-1293325242
调用Math类中的静态方法random,得到:0.2308049275052454
*/

OGNL admite la precompilación de expresiones, que evita el trabajo de compilación antes de cada ejecución de la expresión, lo que puede mejorar significativamente la eficiencia de ejecución de OGNL.

 String userName;

// 先对表达式解析,然后再执行可以提高效率
long time1 = new Date().getTime();
// 解析表达式
Object expressionTree = Ognl.parseExpression("#user2.name");
// 重复运行多次
for (int i = 0; i < 10000; i++) {
    
    
    userName = (String) Ognl.getValue(expressionTree, userMap, new Object());
}
long time2 = new Date().getTime();
// 直接重复运行多次
for (int i = 0; i < 10000; i++) {
    
    
    userName = (String) Ognl.getValue("#user2.name", userMap, new Object());
}
long time3 = new Date().getTime();

System.out.println("编译之后再执行,共花费" + (time2 - time1) + "ms");
System.out.println("不编译直接执行,共花费" + (time3 - time2) + "ms");

/*
编译之后再执行,共花费19ms
不编译直接执行,共花费306ms
*/

Se puede ver que si desea ejecutar una expresión varias veces, es más eficiente compilarla primero y luego ejecutarla. A menudo vemos expresiones OGNL en JSP y XML, y el análisis de estas expresiones se lleva a cabo mediante el método presentado en esta sección. Se puede ver que OGNL es un lenguaje amplio, conveniente y poderoso.

2. Interfaz de controlador de idioma y registro de controlador de idioma

LanguageDriver es la interfaz de la clase de controlador de idioma, que define un total de tres métodos.

// 脚本语言解释器
// 在接口上注解的SQL语句,就是由它进行解析的
// @Select("select * from `user` where id = #{id}")
//User queryUserById(Integer id);
public interface LanguageDriver {
    
    
  /**
   * 创建参数处理器。参数处理器能将实参传递给JDBC statement。
   * @param mappedStatement 完整的数据库操作节点
   * @param parameterObject 参数对象
   * @param boundSql 数据库操作语句转化的BoundSql对象
   * @return 参数处理器
   */
  ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
  /**
   * 创建SqlSource对象(基于映射文件的方式)。该方法在MyBatis启动阶段,读取映射接口或映射文件时被调用
   * @param configuration 配置信息
   * @param script 映射文件中的数据库操作节点
   * @param parameterType 参数类型
   * @return SqlSource对象
   */
  SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
  /**
   * 创建SqlSource对象(基于注解的方式)。该方法在MyBatis启动阶段,读取映射接口或映射文件时被调用
   * @param configuration 配置信息
   * @param script 注解中的SQL字符串
   * @param parameterType 参数类型
   * @return SqlSource对象,具体来说是DynamicSqlSource和RawSqlSource中的一种
   */
  SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}

La interfaz LanguageDriver tiene dos implementaciones de forma predeterminada, a saber, XMLLanguageDriver y RawLanguageDriver
, y RawLanguageDriver es una subclase de XMLLanguageDriver. Todas las operaciones de la clase RawLanguageDriver se completan llamando a la clase principal XMLLanguageDriver. Y una vez que la clase XMLLanguageDriver completa la operación, el SqlSource obtenido mediante la verificación del método checkIsNotDynamic debe ser RawSqlSource. Por lo tanto, la clase RawLanguageDriver en realidad adapta las funciones de la clase XMLLanguageDriver a través del método checkIsNotDynamic, de modo que solo admita SqlSource del tipo RawSqlSource.

public class RawLanguageDriver extends XMLLanguageDriver {
    
    
  @Override
  public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    
    
    // 调用父类方法完成操作
    SqlSource source = super.createSqlSource(configuration, script, parameterType);
    // 校验得到的SqlSource是RawSqlSource
    checkIsNotDynamic(source);
    return source;
  }
  @Override
  public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
    
    
    // 调用父类方法完成操作
    SqlSource source = super.createSqlSource(configuration, script, parameterType);
    // 校验得到的SqlSource是RawSqlSource
    checkIsNotDynamic(source);
    return source;
  }
  /**
   * 校验输入的SqlSource是RawSqlSource,否则便抛出异常
   * @param source 输入的SqlSource对象
   */
  private void checkIsNotDynamic(SqlSource source) {
    
    
    if (!RawSqlSource.class.equals(source.getClass())) {
    
    
      throw new BuilderException("Dynamic content is not allowed when using RAW language");
    }
  }
}

En el diseño orientado a objetos, las subclases generalmente extienden más métodos basándose en la herencia de los métodos de la clase principal, por lo que las funciones de la subclase son un superconjunto de las funciones de la clase principal. La clase RawLanguageDriver ha adaptado las funciones de su clase principal, XMLLanguageDriver, de modo que sus propias funciones sean un subconjunto de las funciones de la clase principal. Este es un enfoque de diseño que primero es complejo y luego simplificado. Cuando encontremos necesidades similares durante el desarrollo, podemos consultar este método de diseño.

MyBatis también permite a los usuarios proporcionar su propia clase de implementación LanguageDriver y especificarla como controlador de script predeterminado a través del atributo defaultScriptingLanguage en el archivo de configuración. La compatibilidad con esta característica se implementa mediante el código fuente en la clase XMLConfigBuilder. Aquí MyBatis intentará configurar el controlador de idioma predeterminado según la configuración del usuario en defaultScriptingLanguage.

configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
//在Configuration类中给定了默认语言驱动
public void setDefaultScriptingLanguage(Class<? extends LanguageDriver> driver) {
    
    
 if (driver == null) {
    
    
   driver = XMLLanguageDriver.class;
 }
 getLanguageRegistry().setDefaultDriverClass(driver);
}

Clase LanguageDriverRegistry, que gestiona todos los controladores de idiomas como un registro de controladores de idiomas. La clase LanguageDriverRegistry incluye principalmente métodos para registrar y seleccionar controladores, y la implementación es relativamente simple.

3. Construcción del árbol de nodos SQL

<select id="selectUsersByNameOrSchoolName" parameterMap="userParam01" resultType="User">
    SELECT * FROM `user`
    <where>
        <if test="name != null">
            `name` = #{name}
        </if>
        <if test="schoolName != null">
            AND `schoolName` = #{schoolName}
        </if>
    </where>
</select>

Para analizar este árbol, lo primero que debe hacer es leer la información en el XML y luego construir el árbol XML en un árbol de nodos SQL en la memoria. La construcción del árbol de nodos SQL la maneja la clase XMLScriptBuilder. Lista de propiedades:

  // 当前要处理的XML节点
  private final XNode context;
  // 当前节点是否为动态节点
  private boolean isDynamic;
  // 输入参数的类型
  private final Class<?> parameterType;
  // 节点类型和对应的处理器组成的Map
  private final Map<String, NodeHandler> nodeHandlerMap = new HashMap<>();

En la clase XMLScriptBuilder, se define una interfaz NodeHandler. Tiene un método handleNode responsable de ensamblar nodos en el árbol de nodos.

  private interface NodeHandler {
    
    
    /**
     * 该方法将当前节点拼装到节点树中
     * @param nodeToHandle 要被拼接的节点
     * @param targetContents 节点树
     */
    void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);
  }

Cada tipo de nodo SQL tiene una clase de implementación NodeHandler. NodeHandlerMap almacena la relación correspondiente entre los nodos SQL y las clases de implementación de NodeHandler.
Diagrama de clases de la interfaz NodeHandler y su clase de implementación:
Insertar descripción de la imagen aquí
tomando IfHandler como ejemplo, veremos cómo construir un árbol de nodos SQL basado en información XML.

/**
 * 该方法将当前节点拼装到节点树中
 * @param nodeToHandle 要被拼接的节点
 * @param targetContents 节点树
 */
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
    
    
  // 解析该节点的下级节点
  MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
  // 获取该节点的test属性
  String test = nodeToHandle.getStringAttribute("test");
  // 创建一个IfSqlNode
  IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
  // 将创建的IfSqlNode放入到SQL节点树中
  targetContents.add(ifSqlNode);
}

Después de comprender la interfaz NodeHandler y su clase de implementación, echemos un vistazo a cómo construir un árbol de nodos SQL a partir del nodo raíz. El método de entrada es el método parseScriptNode y las operaciones principales se llevan a cabo en el método parseDynamicTags.

/**
* 解析节点生成SqlSource对象
* @return SqlSource对象
*/
public SqlSource parseScriptNode() {
    
    
 // 解析XML节点节点,得到节点树MixedSqlNode
 MixedSqlNode rootSqlNode = parseDynamicTags(context);
 SqlSource sqlSource;
 // 根据节点树是否为动态,创建对应的SqlSource对象
 if (isDynamic) {
    
    
   sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
 } else {
    
    
   sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
 }
 return sqlSource;
}

/**
* 将XNode对象解析为节点树
* @param node XNode对象,即数据库操作节点
* @return 解析后得到的节点树
*/
protected MixedSqlNode parseDynamicTags(XNode node) {
    
    
 // XNode拆分出的SqlNode列表
 List<SqlNode> contents = new ArrayList<>();
 // 输入XNode的子XNode
 NodeList children = node.getNode().getChildNodes();
 for (int i = 0; i < children.getLength(); i++) {
    
    
   // 循环遍历每一个子XNode
   XNode child = node.newXNode(children.item(i));
   if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
    
     // CDATASection类型或者Text类型的XNode节点
     // 获取XNode内的信息
     String data = child.getStringBody("");
     TextSqlNode textSqlNode = new TextSqlNode(data);
     // 只要有一个TextSqlNode对象是动态的,则整个MixedSqlNode是动态的
     if (textSqlNode.isDynamic()) {
    
    
       contents.add(textSqlNode);
       isDynamic = true;
     } else {
    
    
       contents.add(new StaticTextSqlNode(data));
     }
   } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) {
    
     // 子XNode仍然是Node类型
     String nodeName = child.getNode().getNodeName();
     // 找到对应的处理器
     NodeHandler handler = nodeHandlerMap.get(nodeName);
     if (handler == null) {
    
    
       throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
     }
     // 用处理器处理节点
     handler.handleNode(child, contents);
     isDynamic = true;
   }
 }
 // 返回一个混合节点,其实就是一个SQL节点树
 return new MixedSqlNode(contents);
}

parseDynamicTags analizará los nodos en el archivo XML paso a paso y utilizará la implementación NodeHandler correspondiente para procesar el nodo y, finalmente, integrará todos los nodos en un objeto MixedSqlNode. El objeto MixedSqlNode es un árbol de nodos SQL. En el proceso de integración del árbol de nodos, siempre que haya un nodo dinámico, el árbol de nodos SQL es dinámico. El árbol de nodos SQL dinámico se utilizará para crear objetos DynamicSqlSource; de ​​lo contrario, se creará un objeto RawSqlSource.

4. Análisis del árbol de nodos SQL

Analizar el árbol de nodos SQL ensamblado es una tarea muy importante en MyBatis. Esta parte del trabajo se completa principalmente en el subpaquete xmltags del paquete de secuencias de comandos.
El análisis de estas expresiones OGNL se completa según el paquete OGNL. Puedes ver esto en el archivo pom.xml de MyBatis:

<dependency>
 <groupId>ognl</groupId>
 <artifactId>ognl</artifactId>
 <version>3.2.10</version>
 <scope>compile</scope>
 <optional>true</optional>
</dependency>

Para completar mejor el trabajo de análisis de OGNL, también se configuran tres clases relacionadas en el subpaquete xmltags.

  1. La clase DefaultClassResolver es una clase definida en OGNL. OGNL puede leer clases a través de esta clase, es decir, convertir el nombre de la clase en una clase. OgnlClassResolver hereda la clase DefaultClassResolver y anula toClassForName en ella. OGNL puede usar la clase Recursos en MyBatis para completar la lectura de la clase mientras trabaja.
@Override
protected Class toClassForName(String className) throws ClassNotFoundException {
    
    
  return Resources.classForName(className);
}
  1. La interfaz MemberAccess es una interfaz de enlace proporcionada por OGNL. OGNL utiliza esta interfaz para prepararse para acceder a las propiedades del objeto. La clase OgnlMemberAccess implementa la interfaz MemberAccess y proporciona la función de modificar la accesibilidad de los atributos del objeto según la reflexión. Luego, OGNL puede prepararse para acceder a las propiedades del objeto en función de estas funciones.
 /**
  * 设置属性的可访问性
  * @param context 环境上下文
  * @param target 目标对象
  * @param member 目标对象的目标成员
  * @param propertyName 属性名称
  * @return 属性的可访问性
  */
 @Override
 public Object setup(Map context, Object target, Member member, String propertyName) {
    
    
   Object result = null;
   if (isAccessible(context, target, member, propertyName)) {
    
     // 如果允许修改属性的可访问性
     AccessibleObject accessible = (AccessibleObject) member;
     if (!accessible.isAccessible()) {
    
     // 如果属性原本不可访问
       result = Boolean.FALSE;
       // 将属性修改为可访问
       accessible.setAccessible(true);
     }
   }
   return result;
 }
  1. Clase OgnlCache Para mejorar la eficiencia operativa de OGNL, MyBatis también proporciona una clase OgnlCache de almacenamiento en caché para OGNL.
public final class OgnlCache {
    
    
  // MyBatis提供的OgnlMemberAccess对象
  private static final OgnlMemberAccess MEMBER_ACCESS = new OgnlMemberAccess();
  // MyBatis提供的OgnlClassResolver对象
  private static final OgnlClassResolver CLASS_RESOLVER = new OgnlClassResolver();
  // 缓存解析后的OGNL表达式,用以提高效率
  private static final Map<String, Object> expressionCache = new ConcurrentHashMap<>();
  private OgnlCache() {
    
    
    // Prevent Instantiation of Static Class
  }

  /**
   * 读取表达式的结果
   * @param expression 表达式
   * @param root 根环境
   * @return 表达式结果
   */
  public static Object getValue(String expression, Object root) {
    
    
    try {
    
    
      // 创建默认的上下文环境
      Map context = Ognl.createDefaultContext(root, MEMBER_ACCESS, CLASS_RESOLVER, null);
      // 依次传入表达式树、上下文、根,从而获得表达式的结果
      return Ognl.getValue(parseExpression(expression), context, root);
    } catch (OgnlException e) {
    
    
      throw new BuilderException("Error evaluating expression '" + expression + "'. Cause: " + e, e);
    }
  }
  /**
   * 解析表达式,得到解析后的表达式树
   * @param expression 表达式
   * @return 表达式树
   * @throws OgnlException
   */
  private static Object parseExpression(String expression) throws OgnlException {
    
    
    // 先从缓存中获取
    Object node = expressionCache.get(expression);
    if (node == null) {
    
    
      // 缓存没有则直接解析,并放入缓存
      node = Ognl.parseExpression(expression);
      expressionCache.put(expression, node);
    }
    return node;
  }
}

En la clase OgnlCache, la expresión se analiza previamente utilizando el método parseExpression y el resultado del análisis de la expresión se almacena en caché en el atributo expresiónCache. De esta manera, cada vez que se analiza una expresión, los resultados analizados se consultarán primero desde el atributo expresiónCache. Esto evita análisis repetidos y mejora la eficiencia de las operaciones de OGNL.

4.1 Evaluador de expresiones

MyBatis no expone directamente la herramienta OGNL a cada nodo SQL, sino que encapsula aún más la herramienta OGNL para facilitar su uso y obtiene la clase ExpressionEvaluator, que es un evaluador de expresiones. La clase ExpressionEvaluator proporciona dos métodos, uno es el método evaluaBoolean. Este método puede evaluar expresiones cuyos resultados tienen la forma de verdadero o falso. Por ejemplo, "<if prueba="nombre! =null">"Los juicios de verdadero y falso en el nodo se pueden completar llamando directamente a este método.

  /**
   * 对结果为true/false形式的表达式进行求值
   * @param expression 表达式
   * @param parameterObject 参数对象
   * @return 求值结果
   */
  public boolean evaluateBoolean(String expression, Object parameterObject) {
    
    
    // 获取表达式的值
    Object value = OgnlCache.getValue(expression, parameterObject);
    if (value instanceof Boolean) {
    
     // 如果确实是Boolean形式的结果
      return (Boolean) value;
    }
    if (value instanceof Number) {
    
     // 如果是数值形式的结果
      return new BigDecimal(String.valueOf(value)).compareTo(BigDecimal.ZERO) != 0;
    }
    return value != null;
  }

El otro es el método evaluaIterable. Este método evalúa una expresión cuyo resultado está en forma iterativa. De esta manera, el juicio de iteración en el nodo "<foreach item="id" collection="array" open="("separator=", "close=")">#{id} </foreach>" puede ser llamado directamente El método está completo.

  /**
   * 对结果为迭代形式的表达式进行求值
   * @param expression 表达式
   * @param parameterObject 参数对象
   * @return 求值结果
   */
  public Iterable<?> evaluateIterable(String expression, Object parameterObject) {
    
    
    // 获取表达式的结果
    Object value = OgnlCache.getValue(expression, parameterObject);
    if (value == null) {
    
    
      throw new BuilderException("The expression '" + expression + "' evaluated to a null value.");
    }
    if (value instanceof Iterable) {
    
     // 如果结果是Iterable
      return (Iterable<?>) value;
    }
    if (value.getClass().isArray()) {
    
     // 结果是Array
      // 原注释:得到的Array可能是原始的,因此调用Arrays.asList()可能会抛出ClassCastException。因此要手工转为ArrayList
      int size = Array.getLength(value);
      List<Object> answer = new ArrayList<>();
      for (int i = 0; i < size; i++) {
    
    
        Object o = Array.get(value, i);
        answer.add(o);
      }
      return answer;
    }
    if (value instanceof Map) {
    
     // 结果是Map
      return ((Map) value).entrySet();
    }
    throw new BuilderException("Error evaluating expression '" + expression + "'.  Return value (" + value + ") was not iterable.");
  }

El evaluador de expresiones basado en la encapsulación OGNL es una poderosa herramienta para el análisis del árbol de nodos SQL. Puede emitir juicios correctos sobre el valor de las expresiones según el contexto. Esto es muy importante para analizar declaraciones de operaciones complejas de bases de datos en declaraciones SQL puras.

4.2 Contexto dinámico

Por un lado, al analizar el árbol de nodos SQL, es necesario guardar continuamente los fragmentos de SQL analizados; por otro lado, al analizar el árbol de nodos SQL, también se necesitan algunos parámetros e información ambiental como base para el análisis. Las dos funciones anteriores son proporcionadas por el contexto dinámico DynamicContext.

// 上下文环境
  private final ContextMap bindings;
  // 用于拼装SQL语句片段
  private final StringJoiner sqlBuilder = new StringJoiner(" ");
  // 解析时的唯一编号,防止解析混乱
  private int uniqueNumber = 0;

  /**
   * DynamicContext的构造方法
   * @param configuration 配置信息
   * @param parameterObject 用户传入的查询参数对象
   */
  public DynamicContext(Configuration configuration, Object parameterObject) {
    
    
    if (parameterObject != null && !(parameterObject instanceof Map)) {
    
    
      // 获得参数对象的元对象
      MetaObject metaObject = configuration.newMetaObject(parameterObject);
      // 判断参数对象本身是否有对应的类型处理器
      boolean existsTypeHandler = configuration.getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass());
      // 放入上下文信息
      bindings = new ContextMap(metaObject, existsTypeHandler);
    } else {
    
    
      // 上下文信息为空
      bindings = new ContextMap(null, false);
    }
    // 把参数对象放入上下文信息
    bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
    // 把数据库id放入上下文信息
    bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
  }

La siguiente información se almacena en el atributo de enlaces de contexto.

  • identificación de la base de datos. Por lo tanto, al escribir declaraciones SQL, podemos usar directamente la variable DATABASE_ID_KEY para hacer referencia al valor de la identificación de la base de datos.
  • Objeto de parámetro. Al escribir declaraciones SQL, podemos usar directamente la variable PARAMETER_OBJECT_KEY para hacer referencia a todo el objeto de parámetro.
  • Metadatos para el objeto de parámetro. Los metadatos basados ​​en objetos de parámetros pueden hacer referencia conveniente a los valores de atributos del objeto de parámetro, por lo que puede hacer referencia directamente a los atributos del objeto de parámetro al escribir declaraciones SQL.

También hay un ContextMap en DynamicContext, que es una subclase de HashMap. Al realizar una consulta de datos, DynamicContext primero consultará desde HashMap. Si la consulta falla, consultará desde las propiedades del objeto de parámetro. En base a esto, podemos hacer referencia directamente a las propiedades del objeto de parámetro al escribir declaraciones SQL. Código fuente de la operación de consulta de datos de la clase DynamicContext:

    /**
     * 根据键索引值。会尝试从HashMap中寻找,失败后会再尝试从parameterMetaObject中寻找
     * @param key 键
     * @return 值
     */
    @Override
    public Object get(Object key) {
    
    
      String strKey = (String) key;
      // 如果HashMap中包含对应的键,直接返回
      if (super.containsKey(strKey)) {
    
    
        return super.get(strKey);
      }

      // 如果Map中不含有对应的键,尝试从参数对象的原对象中获取
      if (parameterMetaObject == null) {
    
    
        return null;
      }

      if (fallbackParameterObject && !parameterMetaObject.hasGetter(strKey)) {
    
    
        return parameterMetaObject.getOriginalObject();
      } else {
    
    
        return parameterMetaObject.getValue(strKey);
      }

    }

Después de leer el código fuente del entorno de contexto dinámico, sabemos por qué podemos hacer referencia directamente tanto a los parámetros reales como a las propiedades de los parámetros reales al escribir el archivo de mapeo.

4.3 Nodos SQL y su análisis

Una ventaja importante de MyBatis es que admite nodos dinámicos. Pero la base de datos en sí no conoce estos nodos, por lo que MyBatis procesará estos nodos primero y luego los entregará a la base de datos para su ejecución. Estos nodos se definen como SqlNode en MyBatis.
SqlNode es una interfaz y solo se define un método de aplicación en la interfaz. Este método es responsable de completar el análisis del nodo en sí y fusionar los resultados del análisis en el contexto proporcionado por los parámetros de entrada.
MyBatis admite muchos tipos de nodos en declaraciones SQL, como if,where,foreach,etc., que son todas subclases de SqlNode.
Insertar descripción de la imagen aquí
A continuación, tome los comunes y típicos IfSqlNode, ForEachSqlNode y TextSqlNode como ejemplos para presentar la clase de implementación de la interfaz SqlNode.

4.3.1 IfSqlNodo

IfSqlNode corresponde al nodo if en el nodo de operación de la base de datos. A través del nodo if, MyBatis puede decidir si escribir un fragmento de SQL en función de parámetros y otra información.

<select id="selectUsersByNameOrSchoolName" parameterMap="userParam01" resultType="User">
    SELECT * FROM `user`
    <where>
        <if test="name != null">
            `name` = #{name}
        </if>
        <if test="schoolName != null">
            AND `schoolName` = #{schoolName}
        </if>
    </where>
</select>
public class IfSqlNode implements SqlNode {
    
    
  // 表达式评估器
  private final ExpressionEvaluator evaluator;
  // if判断时的测试条件
  private final String test;
  // if成立时,要被拼接的SQL片段信息
  private final SqlNode contents;

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

  /**
   * 完成该节点自身的解析
   * @param context 上下文环境,节点自身的解析结果将合并到该上下文环境中
   * @return 解析是否成功
   */
  @Override
  public boolean apply(DynamicContext context) {
    
    
    // 判断if条件是否成立
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
    
    
      // 将contents拼接到context
      contents.apply(context);
      return true;
    }
    return false;
  }
}

El método de aplicación de IfSqlNode es muy simple: llame directamente al evaluador de expresiones para calcular el valor de la expresión en el nodo if. Si el valor de la expresión es verdadero, el contenido del nodo if se agrega al final del contexto del entorno. .

4.3.2 Para cada nodo Sql

El nodo ForEachSqlNode corresponde al nodo foreach en el nodo de operación de la base de datos. Este nodo puede atravesar los elementos individuales de la colección y ensamblar cada elemento en un nuevo fragmento de SQL.

<select id="selectUsers" resultMap="userMapFull">
    SELECT *
    FROM `user`
    WHERE `id` IN
    <foreach item="id" collection="array" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

El método de aplicación de la clase ForEachSqlNode. El proceso principal es analizar el elemento iterado para obtener el objeto de iteración, luego agregar la información del objeto de iteración al contexto y luego unir la cadena de acuerdo con la información del contexto. Finalmente, una vez completado el empalme de cadenas, las variables temporales generadas por esta operación se limpiarán para evitar cualquier impacto en el entorno contextual.

  public static final String ITEM_PREFIX = "__frch_";

  // 表达式求值器
  private final ExpressionEvaluator evaluator;
  // collection属性的值
  private final String collectionExpression;
  // 节点内的内容
  private final SqlNode contents;
  // open属性的值,即元素左侧插入的字符串
  private final String open;
  // close属性的值,即元素右侧插入的字符串
  private final String close;
  // separator属性的值,即元素分隔符
  private final String separator;
  // item属性的值,即元素
  private final String item;
  // index属性的值,即元素的编号
  private final String index;
  // 配置信息
  private final Configuration configuration;

  /**
   * 完成该节点自身的解析
   * @param context 上下文环境,节点自身的解析结果将合并到该上下文环境中
   * @return 解析是否成功
   */
  @Override
  public boolean apply(DynamicContext context) {
    
    
    // 获取环境上下文信息
    Map<String, Object> bindings = context.getBindings();
    // 交给表达式求值器解析表达式,从而获得迭代器
    final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
    if (!iterable.iterator().hasNext()) {
    
     // 没有可以迭代的元素
      // 不需要拼接信息,直接返回
      return true;
    }
    boolean first = true;
    // 添加open字符串
    applyOpen(context);
    int i = 0;
    for (Object o : iterable) {
    
    
      DynamicContext oldContext = context;
      if (first || separator == null) {
    
     // 第一个元素
        // 添加元素
        context = new PrefixedContext(context, "");
      } else {
    
    
        // 添加间隔符
        context = new PrefixedContext(context, separator);
      }
      int uniqueNumber = context.getUniqueNumber();
      // Issue #709
      if (o instanceof Map.Entry) {
    
     // 被迭代对象是Map.Entry
        // 将被迭代对象放入上下文环境中
        Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
        applyIndex(context, mapEntry.getKey(), uniqueNumber);
        applyItem(context, mapEntry.getValue(), uniqueNumber);
      } else {
    
    
        // 将被迭代对象放入上下文环境中
        applyIndex(context, i, uniqueNumber);
        applyItem(context, o, uniqueNumber);
      }
      // 根据上下文环境等信息构建内容
      contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
      if (first) {
    
    
        first = !((PrefixedContext) context).isPrefixApplied();
      }
      context = oldContext;
      i++;
    }
    // 添加close字符串
    applyClose(context);
    // 清理此次操作对环境的影响
    context.getBindings().remove(item);
    context.getBindings().remove(index);
    return true;
  }

4.3.3 Nodo SQL de texto

La clase TextSqlNode corresponde a los nodos de cadena. Los nodos de cadena se utilizan ampliamente. Los nodos de cadena también se incluyen en los nodos if y foreach.

<select id="selectUser_B" resultMap="userMap">
  select * FROM `user` WHERE `id` = #{id}
</select>

Parece que el objeto TextSqlNode en sí es muy puro y no necesita ser analizado, pero no es así. El análisis del objeto TextSqlNode es necesario porque reemplaza los marcadores de posición "${}".

Antes de presentar el análisis de objetos TextSqlNode, primero presentamos sus dos clases internas: la clase BindingTokenParser y la clase DynamicCheckerTokenParser. La clase BindingTokenParser y la clase DynamicCheckerTokenParser son subclases de la interfaz TokenHandler. La
Insertar descripción de la imagen aquí
interfaz TokenHandler del diagrama de clases de la clase relacionada TextSqlNode se utilizará junto con el analizador de marcador de posición general GenericTokenParser. Cuando GenericTokenParser analiza un marcador de posición coincidente, El contenido se entrega al método handleToken del objeto TokenHandler para su procesamiento. En el objeto TextSqlNode, el marcador de posición es el símbolo " ". Entonces se encuentra el símbolo "{}". Entonces encuentro "" símbolo. Entonces, cuando encuentre el símbolo " {}", ¿qué pasará con el objeto BindingTokenParser y el objeto DynamicCheckerTokenParser respectivamente?

  • BindingTokenParser: el método handleToken de este objeto extraerá la variable en el marcador de posición y luego usará la variable como clave para encontrar el valor correspondiente en el entorno de contexto. Luego, el marcador de posición se reemplaza con el valor encontrado. Por lo tanto, este objeto puede completar el trabajo de reemplazo del marcador de posición.
  • DynamicCheckerTokenParser: el método handleToken de este objeto asigna el atributo de miembro isDynamic. Por tanto, el objeto puede registrar si ha encontrado un marcador de posición.

El método de aplicación de la clase TextSqlNode:

  /**
   * 完成该节点自身的解析
   * @param context 上下文环境,节点自身的解析结果将合并到该上下文环境中
   * @return 解析是否成功
   */
  @Override
  public boolean apply(DynamicContext context) {
    
    
    // 创建通用的占位符解析器
    GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
    // 替换掉其中的${}占位符
    context.appendSql(parser.parse(text));
    return true;
  }
 /**
   * 创建一个通用的占位符解析器,用来解析${}占位符
   * @param handler 用来处理${}占位符的专用处理器
   * @return 占位符解析器
   */
  private GenericTokenParser createParser(TokenHandler handler) {
    
    
    return new GenericTokenParser("${", "}", handler);
  }

Al reemplazar el marcador de posición " ", se usa la clase interna Binding Token Parser, que puede sacarlo del contexto. Al reemplazar el marcador de posición " {}", se usa la clase interna BindingTokenParser, que puede sacarlo del contexto. sacar"" Al reemplazar el marcador de posición, se utiliza la clase interna B in d in g To k e n P a rser , que puede extraer del contexto el valor de la variable correspondiente al nombre de la variable en el marcador de posición " {} ". Y TextSqlNode allí También hay un método isDynamic en la clase, que se utiliza para determinar si el TextSqlNode actual es dinámico. Para el objeto TextSqlNode, si contiene el marcador de posición "${}" en su interior, entonces es dinámico; de lo contrario, no es dinámico.

  /**
   * 判断当前节点是不是动态的
   * @return 节点是否为动态
   */
  public boolean isDynamic() {
    
    
    // 占位符处理器,该处理器并不会处理占位符,而是判断是不是含有占位符
    DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
    GenericTokenParser parser = createParser(checker);
    // 使用占位符处理器。如果节点内容中含有占位符,则DynamicCheckerTokenParser对象的isDynamic属性将会被置为true
    parser.parse(text);
    return checker.isDynamic();
  }

Por lo tanto, la clase interna BindingTokenParser tiene la capacidad de reemplazar cadenas y desempeñará un papel en la aplicación del método de análisis de la clase TextSqlNode; la clase interna DynamicCheckerTokenParser tiene la capacidad de grabación y desempeñará un papel en el método isDynamic de la clase TextSqlNode para determinar si es un método dinámico.

5.Fuente SQL

El trabajo principal completado por la clase de controlador de idioma es generar SqlSource. Entre los tres métodos de la interfaz del controlador de idioma LanguageDriver, se utilizan dos métodos para generar SqlSource. El trabajo de conversión de las subclases de SqlSource se completa principalmente en el paquete de secuencias de comandos.

Las cuatro clases de implementación de la interfaz SqlSource y sus diferencias:

  • DynamicSqlSource: sentencia SQL dinámica. La llamada declaración SQL dinámica se refiere a una declaración que contiene nodos SQL dinámicos (como nodos if) o contiene marcadores de posición "${}".
  • RawSqlSource: declaración SQL nativa. Se refiere a una declaración no dinámica, que puede contener marcadores de posición "#{}", pero no contiene nodos SQL dinámicos ni marcadores de posición "${}".
  • StaticSqlSource: declaración estática. La declaración puede contener "?" y puede enviarse directamente a la base de datos para su ejecución.
  • ProviderSqlSource: las anteriores son todas declaraciones SQL obtenidas a través de archivos XML, mientras que ProviderSqlSource es una declaración SQL obtenida mediante mapeo de anotaciones.

5.1 Generación de SqlSource

  1. Analice el archivo de mapeo para generar
    el método createSqlSource de la interfaz en LanguageDriver para analizar la información del nodo en el archivo de mapeo y obtener el objeto SqlSource.
  /**
   * 创建SqlSource对象(基于映射文件的方式)。该方法在MyBatis启动阶段,读取映射接口或映射文件时被调用
   * @param configuration 配置信息
   * @param script 映射文件中的数据库操作节点
   * @param parameterType 参数类型
   * @return SqlSource对象
   */
  SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);

Como se puede ver en la clase XMLLanguageDriver, el objeto SqlSource se genera principalmente mediante el método parseScriptNode de XMLScriptBuilder, y el objeto SqlSource generado por este método es un objeto DynamicSqlSource o un objeto RawSqlSource.

  /**
   * 创建SqlSource对象(基于映射文件的方式)。该方法在MyBatis启动阶段,读取映射接口或映射文件时被调用
   * @param configuration 配置信息
   * @param script 映射文件中的数据库操作节点
   * @param parameterType 参数类型
   * @return SqlSource对象
   */
  @Override
  public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    
    
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    return builder.parseScriptNode();
  }
  /**
   * 解析节点生成SqlSource对象
   * @return SqlSource对象
   */
  public SqlSource parseScriptNode() {
    
    
    // 解析XML节点节点,得到节点树MixedSqlNode
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource;
    // 根据节点树是否为动态,创建对应的SqlSource对象
    if (isDynamic) {
    
    
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
    
    
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }

Por lo tanto, el objeto SqlSource generado al analizar el archivo de mapeo es uno de los objetos DynamicSqlSource y RawSqlSource.

  1. Analizar la información de anotaciones para generar SqlSource.La
    clase ProviderSqlSource es una subclase de la interfaz SqlSource. Además, la clase ProviderSqlSource proporciona otra subclase SqlSource llamando al método createSqlSource(Configuration, String, Class<?>) en la interfaz LanguageDriver.
    createSqlSource (Configuración, Cadena, Clase<?>) en la interfaz LanguageDriver puede generar SqlSource en función de la información de la anotación.
/**
   * 创建SqlSource对象(基于注解的方式)。该方法在MyBatis启动阶段,读取映射接口或映射文件时被调用
   * @param configuration 配置信息
   * @param script 注解中的SQL字符串
   * @param parameterType 参数类型
   * @return SqlSource对象,具体来说是DynamicSqlSource和RawSqlSource中的一种
   */
  SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);

Implementado en la clase XMLLanguageDriver:

  // 创建SQL源码(注解方式)
  @Override
  public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
    
    
    if (script.startsWith("<script>")) {
    
    
      // 如果注解中的内容以<script>开头
      XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
      return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
    } else {
    
    
      // 如果注解中的内容不以<script>开头
      script = PropertyParser.parse(script, configuration.getVariables());
      TextSqlNode textSqlNode = new TextSqlNode(script);
      if (textSqlNode.isDynamic()) {
    
    
        return new DynamicSqlSource(configuration, textSqlNode);
      } else {
    
    
        return new RawSqlSource(configuration, script, parameterType);
      }
    }
  }

La información SQL en la anotación se divide en dos categorías según si la cadena en la anotación comienza con "<script>", para manejar las dos situaciones en el siguiente código respectivamente.

@Select("SELECT * FROM `user` WHERE `id` = #{id}")
User queryUserById(Integer id);
@Select("<script>" +
        "        SELECT *\n" +
        "        FROM `user`\n" +
        "        WHERE id IN\n" +
        "        <foreach item=\"id\" collection=\"array\" open=\"(\" separator=\",\" close=\")\">\n" +
        "            #{id}\n" +
        "        </foreach>\n" +
        "    </script>")
List<User> queryUsersByIds(int[] ids);
  • Para declaraciones SQL que comienzan con "<script>", se utilizará el mismo método de análisis que el archivo de mapeo para generar un objeto DynamicSqlSource o un objeto RawSqlSource;
  • Para declaraciones SQL que no comienzan con "<script>", los objetos DynamicSqlSource o RawSqlSource se generan directamente.

Primero, el objeto SqlSource generado al analizar la información de anotación es un objeto ProviderSqlSource; luego, el objeto ProviderSqlSource se convierte en un objeto DynamicSqlSource o un objeto RawSqlSource a través del método createSqlSource (Configuración, Cadena, Clase<?>) en la interfaz LanguageDriver.

Insertar descripción de la imagen aquí

5.2 Conversión de DynamicSqlSource

La clase DynamicSqlSource se encuentra en el subpaquete xmltags del paquete de secuencias de comandos y representa declaraciones que contienen nodos SQL dinámicos (como nodos if) o marcadores de posición "${}", es decir, declaraciones SQL dinámicas. Tanto DynamicSqlSource como RawSqlSource se convertirán a StaticSqlSource antes de que se pueda proporcionar un objeto BoundSql.

/**
 * SqlSource的重要实现,用以解析动态SQL语句。
 */
public class DynamicSqlSource implements SqlSource {
    
    
  /**
   * 获取一个BoundSql对象
   * @param parameterObject 参数对象
   * @return BoundSql对象
   */
  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    
    
    // 创建DynamicSqlSource的辅助类,用来记录DynamicSqlSource解析出来的
    // * SQL片段信息
    // * 参数信息
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    // 这里会逐层(对于mix的node而言)调用apply。最终不同的节点会调用到不同的apply,完成各自的解析
    // 解析完成的东西拼接到DynamicContext中,里面含有#{}
    // 在这里,动态节点和${}都被替换掉了。
    rootSqlNode.apply(context);
    // 处理占位符、汇总参数信息
    // RawSqlSource也会焦勇这一步
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    // 使用SqlSourceBuilder处理#{},将其转化为?
    // 相关参数放进了context.bindings
    // *** 最终生成了StaticSqlSource对象,然后由它生成BoundSql
    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;
  }
}

El método getBoundSql de la clase DynamicSqlSource tiene dos operaciones muy críticas:

  1. Aquí, el método de aplicación se llamará en cada nodo capa por capa comenzando desde el nodo raíz. Después de este paso, se reemplazarán todos los nodos dinámicos y "${}". De esta forma, DynamicSqlSource ya no es dinámico, sino estático.
rootSqlNode.apply(context);
  1. Esto completará el reemplazo del símbolo "#{}" y devolverá un objeto StaticSqlSource
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
  /**
   * 将DynamicSqlSource和RawSqlSource中的“#{}”符号替换掉,从而将他们转化为StaticSqlSource
   * @param originalSql sqlNode.apply()拼接之后的sql语句。已经不包含<if> <where>等节点,也不含有${}符号
   * @param parameterType 实参类型
   * @param additionalParameters 附加参数
   * @return 解析结束的StaticSqlSource
   */
  public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    
    
    // 用来完成#{}处理的处理器
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    // 通用的占位符解析器,用来进行占位符替换
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    // 将#{}替换为?的SQL语句
    String sql = parser.parse(originalSql);
    // 生成新的StaticSqlSource对象
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }

5.3 Conversión de RawSqlSource

En comparación con la clase DynamicSqlSource, la clase RawSqlSource es más simple porque no contiene nodos dinámicos ni marcadores de posición "${}", solo marcadores de posición "#{}". La clase RawSqlSource completa la conversión a StaticSqlSource en el método de construcción.

  public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
    
    
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> clazz = parameterType == null ? Object.class : parameterType;
    // 处理RawSqlSource中的“#{}”占位符,得到StaticSqlSource
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
  }

Se puede ver que RawSqlSource completó el procesamiento del marcador de posición "#{}" en el método de construcción, obtuvo el objeto StaticSqlSource y lo colocó en su propia propiedad sqlSource. En la operación getBoundSql posterior, el objeto BoundSql se devuelve directamente desde el objeto StaticSqlSource contenido en la propiedad sqlSource.

5.4 Resumen de clases de implementación de la interfaz SqlSource

La interfaz SqlSource tiene cuatro clases de implementación, de las cuales los objetos de tres clases de implementación se transforman en objetos StaticSqlSource mediante capas de transformación. Luego, el método abstracto getBoundSql definido en la interfaz SqlSource es realmente implementado por el objeto StaticSqlSource.
Insertar descripción de la imagen aquí
El proceso de conversión entre clases de implementación de la interfaz SqlSource es en realidad el proceso de análisis de las declaraciones de operación de la base de datos. Durante este proceso de conversión, las declaraciones SQL en las anotaciones se clasifican y procesan, las declaraciones dinámicas se expanden, se asigna un valor al marcador de posición "${}", se reemplaza el marcador de posición "#{}" y finalmente se puede obtener el resultado. se entregará al controlador de la base de datos. La instrucción SQL ejecutada contiene solo el marcador de posición del parámetro "?"

Al clasificar el proceso de transformación de clases y resumirlo en una imagen, podemos tener una comprensión clara e intuitiva del proceso de transformación de toda la declaración de operación de la base de datos. En el proceso de lectura del código fuente, resumir el proceso de transformación de clases, transformación de estado, transferencia de información y otros procesos en una imagen es una buena manera de evitar perderse en una lógica confusa.

Supongo que te gusta

Origin blog.csdn.net/d495435207/article/details/130900892
Recomendado
Clasificación