Mybatis analiza el análisis de código fuente SQL dos

Mybatis analiza el análisis de código fuente SQL dos

Programación de tecnología Java TSMYK

Análisis del código fuente del nodo resultMap en el archivo de configuración
Mybatis Mapper.xml Análisis Mybatis Análisis del código fuente SQL - Análisis del código fuente de la
interfaz Mybatis Mapper Análisis del código fuente del
grupo de conexiones de la base de datos Mybatis Análisis del
código fuente de la conversión del tipo
Mybatis Análisis del código fuente del archivo de configuración del análisis Mybatis Análisis del código fuente

Prefacio

En los dos últimos artículos Mybatis analiza el análisis del código fuente SQL I y el análisis del código fuente del nodo resultMap en el archivo de configuración Mybatis Mapper.xml, analicé cómo Mybatis analiza el archivo de configuración Mapper.xml. El nodo SQL configurado en el archivo de configuración se analiza en uno Un objeto MappedStatement se coloca en el objeto de configuración global Configuración, donde la declaración SQL se analizará en un objeto SqlSource. Este paso se lleva a cabo cuando se carga Mybatis; y en tiempo de ejecución, ¿cómo pasa Mybatis el objeto SqlSource a us ¿Analizado en una declaración SQL completa que puede ser ejecutada por la base de datos? Echemos un vistazo al código fuente de esta parte y veamos cómo se analiza Mybatis.

El análisis de esta parte implicará la aplicación de patrones combinados y expresiones OGNL

SqlSource

En el artículo Mybatis analizando el análisis del código fuente SQL, sabemos que las sentencias SQL en el archivo de configuración se analizarán en objetos SqlSource, y los nodos SQL dinámicos definidos en las sentencias SQL, como <where>, <if>, están relacionados a la clase de implementación SqlNode para representar.

Ahora echemos un vistazo a la definición de la interfaz SqlSource:


1public interface SqlSource {
2
3  BoundSql getBoundSql(Object parameterObject);
4
5}

Tiene solo un método getBoundSql (), el valor de retorno de este método es un objeto BoundSql, que contiene declaraciones SQL de? Marcadores de posición y parámetros reales vinculados, y analizaremos esta clase más adelante.

La interfaz SqlSource tiene 4 clases de implementación:
Mybatis analiza el análisis de código fuente SQL dos

Entre ellos, DynamicSqlSource es responsable de procesar las declaraciones SQL dinámicas y RawSqlSource es responsable de procesar las declaraciones SQL estáticas. Eventualmente encapsularán StaticSqlSource y devolverán el SQL procesado. ¿Puede contener el SQL contenido en StaticSqlSource? Los marcadores de posición pueden ser ejecutados directamente por la base de datos, y el SQL en DynamicSqlSource necesita más análisis antes de que pueda ser ejecutado por la base de datos.

Veamos estas categorías más adelante, ahora veamos el análisis de nodos SQL dinámicos.

DynamicContext

DynamicContext Esta clase se utiliza principalmente para almacenar fragmentos de sentencias SQL generadas al analizar sentencias SQL dinámicas. Por ejemplo, al analizar la etiqueta <if>, se pueden agregar palabras clave como yyo delante de ella. Se utiliza para almacenar estos SQL fragmentos.


 1public class DynamicContext {
 2
 3  // 有的SQL直接使用了该字面值,如 #{_parameter}
 4  public static final String PARAMETER_OBJECT_KEY = "_parameter";
 5  // 数据库ID,可以忽略
 6  public static final String DATABASE_ID_KEY = "_databaseId";
 7  // ................
 8  // 运行时传入的参数,是一个 map,内部类
 9  private final ContextMap bindings;
10
11  // 重要,用来拼接 SQL 语句的,每解析完一个动态SQL标签的时候,会把SQL片段拼接到该属性中,最后形成完整的SQL
12  private final StringBuilder sqlBuilder = new StringBuilder();
13
14  // 构造方法,就是把传进行的参数封装为 map
15  public DynamicContext(Configuration configuration, Object parameterObject) {
16    if (parameterObject != null && !(parameterObject instanceof Map)) {
17      MetaObject metaObject = configuration.newMetaObject(parameterObject);
18      bindings = new ContextMap(metaObject);
19    } else {
20      bindings = new ContextMap(null);
21    }
22    bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
23    bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
24  }
25  // .............
26
27  // 添加 SQL 
28  public void appendSql(String sql) {
29    sqlBuilder.append(sql);
30    sqlBuilder.append(" ");
31  }
32
33  // 返回 SQL
34  public String getSql() {
35    return sqlBuilder.toString().trim();
36  }
37
38  // 存放参数的 map,继承了 HashMap,重写 get
39  static class ContextMap extends HashMap<String, Object> {
40
41    private MetaObject parameterMetaObject;
42
43    public ContextMap(MetaObject parameterMetaObject) {
44      this.parameterMetaObject = parameterMetaObject;
45    }
46
47    @Override
48    public Object get(Object key) {
49      String strKey = (String) key;
50      if (super.containsKey(strKey)) {
51        return super.get(strKey);
52      }
53      // 从运行参数中查找对应的属性
54      if (parameterMetaObject != null) {
55        return parameterMetaObject.getValue(strKey);
56      }
57      return null;
58    }
59  }
60}

SqlNode

Al analizar el archivo de configuración, conoce los nodos SQL dinámicos definidos en la declaración SQL, como <where>, <if> y <foreach>, que están representados por clases de implementación relacionadas con SqlNode. La definición de SqlNode es la siguiente:


1public interface SqlNode {
2  boolean apply(DynamicContext context);
3}

Solo tiene un método, el método apply (context), que analizará el nodo SQL dinámico representado por SqlNode de acuerdo con los parámetros pasados, y llamará al método context.appendSql () para agregar el fragmento SQL analizado a la propiedad sqlBuilder para almacenamiento. Una vez analizados todos los SqlNodes del nodo SQL, puede llamar a context.getSql () para obtener un SQL completo.

Ahora piense en cuántos nodos SQL dinámicos tiene Mybatis, como <where>, <if>, <set>, <foreach>, <choose>, etc., y probablemente haya tantas clases de implementación correspondientes de SqlNode.

La clase de implementación de SqlNode tiene 10 clases de implementación, correspondientes a sus nodos SQL dinámicos:
Mybatis analiza el análisis de código fuente SQL dos

A continuación, observe cómo se analiza cada nodo SQL dinámico.

StaticTextSqlNode

StaticTextSqlNode representa un nodo SQL de texto estático, este tipo de nodo no necesita ser analizado, simplemente agregue la declaración SQL correspondiente directamente a la propiedad DynamicContext.sqlBuilder.


 1public class StaticTextSqlNode implements SqlNode {
 2  // 对应的SQL片段
 3  private String text;
 4  public StaticTextSqlNode(String text) {
 5    this.text = text;
 6  }
 7  // 直接把 SQL 片段添加到 sqlBuilder 属性中即可
 8  @Override
 9  public boolean apply(DynamicContext context) {
10    context.appendSql(text);
11    return true;
12  }
13}

MixedSqlNode

MixedSqlNode indica que hay varios nodos SqlNode, y el método apply () llama al método apply () del nodo SqlNode correspondiente a su vez:


 1public class MixedSqlNode implements SqlNode {
 2  private List<SqlNode> contents;
 3  public MixedSqlNode(List<SqlNode> contents) {
 4    this.contents = contents;
 5  }
 6  // 依次调用每个 SqlNode 的 apply 方法添加 SQL 片段
 7  @Override
 8  public boolean apply(DynamicContext context) {
 9    for (SqlNode sqlNode : contents) {
10      sqlNode.apply(context);
11    }
12    return true;
13  }
14}

TextSqlNode

TextSqlNode representa una declaración SQL dinámica que contiene $ {} marcadores de posición. Llamará a la clase de herramienta GenericTokenParser para analizar los marcadores de posición $ {}. Para la clase de herramienta GenericTokenParser, consulte el análisis del código fuente del archivo de configuración de análisis de Mybatis

Por ejemplo, si una parte de SQL es name = $ {name} y el parámetro es name = zhangsan, el fragmento de SQL analizado por TextSqlNode es name = zhangsan y el fragmento de SQL se agrega al DynamicContext. El código fuente es el siguiente:


 1public class TextSqlNode implements SqlNode {
 2  // 要解析的动态SQL
 3  private String text;
 4  private Pattern injectionFilter;
 5  public TextSqlNode(String text, Pattern injectionFilter) {
 6    this.text = text;
 7    this.injectionFilter = injectionFilter;
 8  }
 9  // 解析SQL 
10  @Override
11  public boolean apply(DynamicContext context) {
12    GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
13    // 实际上调用 BindingTokenParser 的handleToken方法进行解析
14    context.appendSql(parser.parse(text));
15    return true;
16  }
17  // 添加 ${ }
18  private GenericTokenParser createParser(TokenHandler handler) {
19    return new GenericTokenParser("${", "}", handler);
20  }
21
22  private static class BindingTokenParser implements TokenHandler {
23
24    private DynamicContext context;
25    private Pattern injectionFilter;
26
27    public BindingTokenParser(DynamicContext context, Pattern injectionFilter) {
28      this.context = context;
29      this.injectionFilter = injectionFilter;
30    }
31   // 解析 ${}
32    @Override
33    public String handleToken(String content) {
34      // 获取参数
35      Object parameter = context.getBindings().get("_parameter");
36      if (parameter == null) {
37        context.getBindings().put("value", null);
38      } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
39        context.getBindings().put("value", parameter);
40      }
41      // 获取值
42      Object value = OgnlCache.getValue(content, context.getBindings());
43      String srtValue = (value == null ? "" : String.valueOf(value)); 
44      checkInjection(srtValue); // 校验合法性
45      return srtValue;
46    }
47  }
48  // 判断是否是动态SQL
49  public boolean isDynamic() {
50    DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
51    GenericTokenParser parser = createParser(checker);
52    parser.parse(text);
53    return checker.isDynamic();
54  }
55  private static class DynamicCheckerTokenParser implements TokenHandler {
56    private boolean isDynamic;
57    public DynamicCheckerTokenParser() {
58    }
59    public boolean isDynamic() {
60      return isDynamic;
61    }
62     // 调用该类就是动态SQL??
63    @Override
64    public String handleToken(String content) {
65      this.isDynamic = true;
66      return null;
67    }
68  }
69}

IfSqlNode

Si se usaSqlNode para analizar la etiqueta <if>, echemos un vistazo al uso de la etiqueta <if>:


1 <if test="username != null">
2    username=#{username}
3</if>

El análisis es el siguiente:


 1public class IfSqlNode implements SqlNode {
 2  // 用来判断 test 条件true|false的,可以忽略不看
 3  private ExpressionEvaluator evaluator;
 4  // test 表达式
 5  private String test;
 6  // <if> 的子节点
 7  private SqlNode contents;
 8
 9  public IfSqlNode(SqlNode contents, String test) {
10    this.test = test;
11    this.contents = contents;
12    this.evaluator = new ExpressionEvaluator();
13  }
14
15  @Override
16  public boolean apply(DynamicContext context) {
17    // 如果 test 表达式为true,才会执行解析SQL
18    if (evaluator.evaluateBoolean(test, context.getBindings())) {
19      contents.apply(context);
20      return true;
21    }
22    return false;
23  }
24}

TrimSqlNode

TrimSqlNode se usa para analizar el nodo <trim>, agregará o eliminará el prefijo y sufijo correspondientes según el resultado del análisis del nodo hijo.

Veamos primero el escenario de uso del nodo <trim>, si existe el siguiente SQL:


 1<select id="queryUser" resultType="User">
 2  SELECT * FROM user
 3  WHERE 
 4  <if test="name!= null">
 5    name= #{name}
 6  </if> 
 7  <if test="address!= null">
 8    AND address like #{address}
 9  </if>
10</select>

Si no se cumplen las condiciones, o solo se cumplen las condiciones de la dirección, el SQL analizado es SELECT FROM user WHERE o SELECT FOMR user WHERE AND address ...

Puede usar la etiqueta <where> para resolver este problema. La etiqueta <where> solo insertará la cláusula WHERE cuando haya al menos un elemento secundario que devuelva la cláusula SQL. Además, si el comienzo de la declaración es AND u OR, el elemento where también los eliminará.

Además, también podemos usar <trim> en su lugar, como se muestra a continuación:


1<trim prefix="WHERE" prefixOverrides="AND |OR ">
2  ... 
3</trim>

Su función es eliminar todo el contenido especificado en el atributo prefixOverrides e insertar el contenido especificado en el atributo prefix


 1public class TrimSqlNode implements SqlNode {
 2  // trim 的子节点
 3  private SqlNode contents;
 4  //为 <trim> 节点包含的SQL添加的前缀字符串
 5  private String prefix;
 6  //为 <trim> 节点包含的SQL添加的后缀字符串
 7  private String suffix;
 8  // 删除指定的前缀
 9  private List<String> prefixesToOverride;
10  // 删除指定的后缀
11  private List<String> suffixesToOverride;
12  private Configuration configuration;
13
14  // 构造方法,同时解析删除的前缀和后缀字符串
15  public TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, String prefixesToOverride, String suffix, String suffixesToOverride) {
16    this(configuration, contents, prefix, parseOverrides(prefixesToOverride), suffix, parseOverrides(suffixesToOverride));
17  }
18  // 解析删除的前缀和后缀字符串
19  private static List<String> parseOverrides(String overrides) {
20    if (overrides != null) {
21      // 按 | 分割,放到集合中
22      final StringTokenizer parser = new StringTokenizer(overrides, "|", false);
23      final List<String> list = new ArrayList<String>(parser.countTokens());
24      while (parser.hasMoreTokens()) {
25        list.add(parser.nextToken().toUpperCase(Locale.ENGLISH));
26      }
27      return list;
28    }
29
30  /...........
31  @Override
32  public boolean apply(DynamicContext context) {
33    FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
34    // 解析子节点
35    boolean result = contents.apply(filteredDynamicContext);
36    // 处理前缀和后缀
37    filteredDynamicContext.applyAll();
38    return result;
39  }
40    // 内部类
41  private class FilteredDynamicContext extends DynamicContext {
42    private DynamicContext delegate;
43    // 是否已经处理过前缀,默认为false
44    private boolean prefixApplied;
45    // 是否已经处理过后缀,默认为false
46    private boolean suffixApplied;
47     // SQL
48    private StringBuilder sqlBuffer;
49    // 
50    public void applyAll() {
51      // 获取子节点解析结果
52      sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
53      String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);
54      if (trimmedUppercaseSql.length() > 0) {
55        // 处理前缀
56        applyPrefix(sqlBuffer, trimmedUppercaseSql);
57        // 处理后缀
58        applySuffix(sqlBuffer, trimmedUppercaseSql);
59      }
60      // 最后拼接SQL
61      delegate.appendSql(sqlBuffer.toString());
62    }
63   // 处理前缀,处理后缀同理
64   private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
65      if (!prefixApplied) {
66        // 如果还没处理过,则处理
67        prefixApplied = true;
68        if (prefixesToOverride != null) {
69          for (String toRemove : prefixesToOverride) {
70            // 删除指定前缀
71            if (trimmedUppercaseSql.startsWith(toRemove)) {
72              sql.delete(0, toRemove.trim().length());
73              break;
74            }
75          }
76        }
77         // 添加指定前缀
78        if (prefix != null) {
79          sql.insert(0, " ");
80          sql.insert(0, prefix);
81        }
82      }
83    }
84}

WhereSqlNode

WhereSqlNode se utiliza para procesar la etiqueta <where>. Como se mencionó en la introducción de TrimSqlNode, la etiqueta <where> automáticamente tendrá el prefijo where, y se eliminará "y". De hecho, la etiqueta <where> se analiza utilizando la clase WhereSqlNode, mientras que WhereSqlNode es La subclase de TrimSqlNode es solo que el atributo de prefijo de la etiqueta de ajuste se establece en where, y prefixToOverride se establece en AND | OR.


1public class WhereSqlNode extends TrimSqlNode {
2
3  private static List<String> prefixList = Arrays.asList("AND ","OR ","AND\n", "OR\n", "AND\r", "OR\r", "AND\t", "OR\t");
4
5  public WhereSqlNode(Configuration configuration, SqlNode contents) {
6    super(configuration, contents, "WHERE", prefixList, null, null);
7  }
8}

Puede ver que el prefijo es donde, el prefijo que se eliminará es Y | O, y el sufijo y el sufijo que se eliminará son nulos.

SetSqlNode

SetSqlNode se utiliza principalmente para analizar la etiqueta <set>. Al igual que la clase de análisis de la etiqueta <where>, también hereda la clase TrimSqlNode, excepto que los prefijos que deben agregarse y los sufijos que deben eliminarse se establecen en SET y comas.


1public class SetSqlNode extends TrimSqlNode {
2
3  private static List<String> suffixList = Arrays.asList(",");
4
5  public SetSqlNode(Configuration configuration,SqlNode contents) {
6    super(configuration, contents, "SET", null, null, suffixList);
7
8}

Entonces, al usar la etiqueta <set>, si no se cumple la última condición, la última coma convertida a SQL se eliminará automáticamente, como se muestra a continuación:


1<update id="updateAuthorIfNecessary">
2  update Author
3    <set>
4      <if test="username != null">username=#{username},</if>
5      <if test="password != null">password=#{password},</if>
6      <if test="bio != null">bio=#{bio}</if>
7    </set>
8  where id=#{id}
9</update>

Si no se cumple la condición bio, la última coma no afectará la ejecución de SQL, debe eliminarse automáticamente.

ForeachSqlNode

ForeachSqlNode se usa principalmente para analizar el nodo <foreach>, echemos un vistazo al uso del nodo <foreach>:


1<select id="queryUsers" resultType="User">
2  SELECT * FROM user WHERE ID in
3  <foreach item="item" index="index" collection="list" open="(" separator="," close=")">
4        #{item}
5  </foreach>
6</select>

Código fuente:
echemos un vistazo a sus dos clases internas:

PrefixedContext


 1  private class PrefixedContext extends DynamicContext {
 2
 3    private DynamicContext delegate;
 4    // 指定的前缀
 5    private String prefix;
 6    // 是否处理过前缀
 7    private boolean prefixApplied;
 8
 9    // .......
10
11    @Override
12    public void appendSql(String sql) {
13      // 如果还没有处理前缀,则添加前缀
14      if (!prefixApplied && sql != null && sql.trim().length() > 0) {
15        delegate.appendSql(prefix);
16        prefixApplied = true;
17      }
18       // 拼接SQL
19      delegate.appendSql(sql);
20    }
21}

FilteredDynamicContext

FilteredDynamicContext se utiliza para procesar # {} marcadores de posición, pero no hay parámetros vinculados, pero # {item} se convierte en # {_ frch_item_1} y otros marcadores de posición.


 1  private static class FilteredDynamicContext extends DynamicContext {
 2    private DynamicContext delegate;
 3    //对应集合项在集合的索引位置
 4    private int index;
 5    // item的索引
 6    private String itemIndex;
 7    // item的值
 8    private String item;
 9    //.............
10    // 解析 #{item}
11    @Override
12    public void appendSql(String sql) {
13      GenericTokenParser parser = new GenericTokenParser("#{", "}", new TokenHandler() {
14        @Override
15        public String handleToken(String content) {
16          // 把 #{itm} 转换为 #{__frch_item_1} 之类的
17          String newContent = content.replaceFirst("^\\s*" + item + "(?![^.,:\\s])", itemizeItem(item, index));
18           // 把 #{itmIndex} 转换为 #{__frch_itemIndex_1} 之类的
19          if (itemIndex != null && newContent.equals(content)) {
20            newContent = content.replaceFirst("^\\s*" + itemIndex + "(?![^.,:\\s])", itemizeItem(itemIndex, index));
21          }
22          // 再返回 #{__frch_item_1} 或 #{__frch_itemIndex_1}
23          return new StringBuilder("#{").append(newContent).append("}").toString();
24        }
25      });
26      // 拼接SQL
27      delegate.appendSql(parser.parse(sql));
28    }
29  private static String itemizeItem(String item, int i) {
30    return new StringBuilder("__frch_").append(item).append("_").append(i).toString();
31  }
32}

ForeachSqlNode

Después de comprender las dos clases internas de ForeachSqlNode, echemos un vistazo:


 1public class ForEachSqlNode implements SqlNode {
 2  public static final String ITEM_PREFIX = "__frch_";
 3  // 判断循环的终止条件
 4  private ExpressionEvaluator evaluator;
 5  // 循环的集合
 6  private String collectionExpression;
 7  // 子节点
 8  private SqlNode contents;
 9  // 开始字符
10  private String open;
11  // 结束字符
12  private String close;
13  // 分隔符
14  private String separator;
15  // 本次循环的元素,如果集合为 map,则index 为key,item为value
16  private String item;
17  // 本次循环的次数
18  private String index;
19  private Configuration configuration;
20
21  // ...............
22
23  @Override
24  public boolean apply(DynamicContext context) {
25    // 获取参数
26    Map<String, Object> bindings = context.getBindings();
27    final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
28    if (!iterable.iterator().hasNext()) {
29      return true;
30    }
31    boolean first = true;
32    // 添加开始字符串
33    applyOpen(context);
34    int i = 0;
35    for (Object o : iterable) {
36      DynamicContext oldContext = context;
37      if (first) {
38        // 如果是集合的第一项,则前缀prefix为空字符串
39        context = new PrefixedContext(context, "");
40      } else if (separator != null) {
41        // 如果分隔符不为空,则指定分隔符
42        context = new PrefixedContext(context, separator);
43      } else {
44          // 不指定分隔符,在默认为空
45          context = new PrefixedContext(context, "");
46      }
47      int uniqueNumber = context.getUniqueNumber();  
48      if (o instanceof Map.Entry) {
49        // 如果集合是map类型,则将集合中的key和value添加到bindings参数集合中保存
50        Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
51        // 所以循环的集合为map类型,则index为key,item为value,就是在这里设置的
52        applyIndex(context, mapEntry.getKey(), uniqueNumber);
53        applyItem(context, mapEntry.getValue(), uniqueNumber);
54      } else {
55        // 不是map类型,则将集合中元素的索引和元素添加到 bindings集合中
56        applyIndex(context, i, uniqueNumber);
57        applyItem(context, o, uniqueNumber);
58      }
59      // 调用 FilteredDynamicContext 的apply方法进行处理
60      contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
61      if (first) {
62        first = !((PrefixedContext) context).isPrefixApplied();
63      }
64      context = oldContext;
65      i++;
66    }
67     // 添加结束字符串
68    applyClose(context);
69    return true;
70  }
71
72  private void applyIndex(DynamicContext context, Object o, int i) {
73    if (index != null) {
74      context.bind(index, o); // key为idnex,value为集合元素
75      context.bind(itemizeItem(index, i), o); // 为index添加前缀和后缀形成新的key
76    }
77  }
78
79  private void applyItem(DynamicContext context, Object o, int i) {
80    if (item != null) {
81      context.bind(item, o);
82      context.bind(itemizeItem(item, i), o);
83    }
84  }
85}

En el ejemplo inicial:


1<select id="queryUsers" resultType="User">
2  SELECT * FROM user WHERE ID in
3  <foreach item="item" index="index" collection="list" open="(" separator="," close=")">
4        #{item}
5  </foreach>
6</select>

El SQL analizado es el siguiente: SELECT * FORM user WHERE ID in (# { frch_item_0}, # { frch_item_1})

ChooseSqlNode

ChooseSqlNode se usa para analizar el nodo <choose>, que es relativamente simple:


 1public class ChooseSqlNode implements SqlNode {
 2  // 对应 <otherwise> 节点
 3  private SqlNode defaultSqlNode;
 4  // 对应<when> 节点
 5  private List<SqlNode> ifSqlNodes;
 6
 7  public ChooseSqlNode(List<SqlNode> ifSqlNodes, SqlNode defaultSqlNode) {
 8    this.ifSqlNodes = ifSqlNodes;
 9    this.defaultSqlNode = defaultSqlNode;
10  }
11
12  @Override
13  public boolean apply(DynamicContext context) {
14    for (SqlNode sqlNode : ifSqlNodes) {
15      if (sqlNode.apply(context)) {
16        return true;
17      }
18    }
19    if (defaultSqlNode != null) {
20      defaultSqlNode.apply(context);
21      return true;
22    }
23    return false;
24  }
25}

SqlSourceBuilder

Después de que el nodo SQL sea analizado por cada SqlNode.apply (), la declaración SQL se pasará a SqlSourceBuilder para un análisis más detallado. SqlSourceBuilder completa principalmente dos partes: una es analizar los atributos en el marcador de posición # {}, el formato es similar a # {__ frc_item_0, javaType = int, jdbcType = number, typeHandler = MyTypeHander}, y la otra es reemplazar # {} en SQL con?.


 1public class SqlSourceBuilder extends BaseBuilder {
 2  // 参数属性
 3  private static final String parameterProperties = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";
 4
 5  public SqlSourceBuilder(Configuration configuration) {
 6    super(configuration);
 7  }
 8  // 解析SQL
 9  // originalSql 经过 SqlNode.apply() 解析后的SQL
10  // parameterType 传入的参数类型
11  // additionalParameters 形参和实参的对应关系,即 DynamicContex.bindings 参数集合
12  public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
13    // ParameterMappingTokenHandler 解析 #{}
14    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
15    // GenericTokenParser 与ParameterMappingTokenHandler 配合解析 #{}
16    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
17    // 得到含有 ? 占位符的SQL,
18    String sql = parser.parse(originalSql);
19    // 根据含有?占位符的SQL和参数创建StaticSqlSource对象
20    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
21  }
22  // 内部类,用来解析 #{}
23  private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {
24    // parameterMappings 记录了 #{} 中的属性,可以忽略
25    private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
26    private Class<?> parameterType;
27    private MetaObject metaParameters;
28
29    public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) {
30      super(configuration);
31      this.parameterType = parameterType;
32      this.metaParameters = configuration.newMetaObject(additionalParameters);
33    }
34
35    public List<ParameterMapping> getParameterMappings() {
36      return parameterMappings;
37    }
38
39    @Override
40    public String handleToken(String content) {
41      parameterMappings.add(buildParameterMapping(content));
42      // 替换 ? 占位符
43      return "?";
44    }
45}

Después de analizar mediante el SqlSourceBuilder anterior, se obtiene un objeto StaticSqlSource:

StaticSqlSource


 1public class StaticSqlSource implements SqlSource {
 2  // SQL
 3  private String sql;
 4  // 参数的属性集合
 5  private List<ParameterMapping> parameterMappings;
 6  private Configuration configuration;
 7
 8  public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) {
 9    this.sql = sql;
10    this.parameterMappings = parameterMappings;
11    this.configuration = configuration;
12  }
13  // 直接返回 BoundSql
14  @Override
15  public BoundSql getBoundSql(Object parameterObject) {
16    return new BoundSql(configuration, sql, parameterMappings, parameterObject);
17  }
18}

BoundSql


 1public class BoundSql {
 2  // SQL ,可能含有 ? 占位符
 3  private String sql;
 4  // 参数的属性集合
 5  private List<ParameterMapping> parameterMappings;
 6  // 传入的实际参数
 7  private Object parameterObject;
 8  // 空的hashmap,之后中复制 DynamicContext.bindings 中的内容
 9  private Map<String, Object> additionalParameters;
10  //additionalParameters集合对象的 MetaObject 对象
11  private MetaObject metaParameters;
12}

DynamicSqlSource

El último paso usa DynamicSqlSource para analizar declaraciones SQL dinámicas:


 1public class DynamicSqlSource implements SqlSource {
 2
 3  private Configuration configuration;
 4  private SqlNode rootSqlNode;
 5
 6  public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
 7    this.configuration = configuration;
 8    this.rootSqlNode = rootSqlNode;
 9  }
10
11  @Override
12  public BoundSql getBoundSql(Object parameterObject) {
13    // 创建DynamicContext,parameterObject为传进来的参数
14    DynamicContext context = new DynamicContext(configuration, parameterObject);
15    //rootSqlNode.apply 方法会调用整个树形结构中全部的SqlNode的apply方法,每个SqlNode的apply方法解析得到的SQL片段会添加到 context中,最后调用 getSql 得到完整的SQL
16    rootSqlNode.apply(context);
17    // 解析 #{} 参数属性,并将 #{} 替换为 ?
18    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
19    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
20    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
21    // 创建 BoundSql 
22    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
23    for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
24      boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
25    }
26    return boundSql;
27  }
28}

RawSqlSource

Además de DynamicSqlSource que analiza SQL dinámico, también existe RawSqlSource para analizar SQL estático El principio es similar.

En este punto, se analiza SQL.

Supongo que te gusta

Origin blog.51cto.com/15077536/2608632
Recomendado
Clasificación