美文网首页
MyBatis印象阅读之 SqlSource和Parameter

MyBatis印象阅读之 SqlSource和Parameter

作者: 向光奔跑_ | 来源:发表于2019-08-08 16:55 被阅读0次

    在上面两节中我们解决了关于NodeHandler和SqlNode的技术债,本章我们继续来还关于SqlSource和ParameterMapping的技术债。

    1. SqlSource源码解析

    这是一个接口,非常简单,我们来看下源码:

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

    最主要的是我们来看它的子类:


    SqlSource子类

    在之前我们看过关于RawSqlSource的源码,接下来我们来看下比较难得动态的DynamicSqlSource源码,我们接下来一起展示:

    
    public class DynamicSqlSource implements SqlSource {
    
      private final Configuration configuration;
      private final SqlNode rootSqlNode;
    
      public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
        this.configuration = configuration;
        this.rootSqlNode = rootSqlNode;
      }
    
      @Override
      public BoundSql getBoundSql(Object parameterObject) {
        DynamicContext context = new DynamicContext(configuration, parameterObject);
        rootSqlNode.apply(context);
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
        Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
        SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        context.getBindings().forEach(boundSql::setAdditionalParameter);
        return boundSql;
      }
    
    }
    

    第一个要注意的就是DynamicContext类,这个类具体是干嘛的呢?我们不进入源码进行分析,如果你有好奇心,可自行调试。这个类的作用是存储parameterObject相关的信息的,方便拿取parameterObject对应信息。

    第二个要注意的就是SqlSourceBuilder类了,我们进入源码看下,先看构造方法和属性:

    public class SqlSourceBuilder extends BaseBuilder {
    
      private static final String PARAMETER_PROPERTIES = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";
    
      public SqlSourceBuilder(Configuration configuration) {
        super(configuration);
      }
    
      public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
        // <1> 创建 ParameterMappingTokenHandler 对象
        ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
        // <2> 创建 GenericTokenParser 对象
        GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
        // <3> 执行解析
        String sql = parser.parse(originalSql);
        // <4> 创建 StaticSqlSource 对象
        return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
      }
    }
    

    这里又涉及到了一个内部类ParameterMappingTokenHandler:

    private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {
    
        private List<ParameterMapping> parameterMappings = new ArrayList<>();
        private Class<?> parameterType;
        private MetaObject metaParameters;
    
        public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) {
          super(configuration);
          this.parameterType = parameterType;
          this.metaParameters = configuration.newMetaObject(additionalParameters);
        }
    
        public List<ParameterMapping> getParameterMappings() {
          return parameterMappings;
        }
    
        @Override
        public String handleToken(String content) {
          parameterMappings.add(buildParameterMapping(content));
          return "?";
        }
    
        private ParameterMapping buildParameterMapping(String content) {
          Map<String, String> propertiesMap = parseParameterMapping(content);
          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;
          } else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
            propertyType = java.sql.ResultSet.class;
          } else if (property == null || Map.class.isAssignableFrom(parameterType)) {
            propertyType = Object.class;
          } else {
            MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
            if (metaClass.hasGetter(property)) {
              propertyType = metaClass.getGetterType(property);
            } else {
              propertyType = Object.class;
            }
          }
          ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
          Class<?> javaType = propertyType;
          String typeHandlerAlias = null;
          for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
            String name = entry.getKey();
            String value = entry.getValue();
            if ("javaType".equals(name)) {
              javaType = resolveClass(value);
              builder.javaType(javaType);
            } else if ("jdbcType".equals(name)) {
              builder.jdbcType(resolveJdbcType(value));
            } else if ("mode".equals(name)) {
              builder.mode(resolveParameterMode(value));
            } else if ("numericScale".equals(name)) {
              builder.numericScale(Integer.valueOf(value));
            } else if ("resultMap".equals(name)) {
              builder.resultMapId(value);
            } else if ("typeHandler".equals(name)) {
              typeHandlerAlias = value;
            } else if ("jdbcTypeName".equals(name)) {
              builder.jdbcTypeName(value);
            } else if ("property".equals(name)) {
              // Do Nothing
            } else if ("expression".equals(name)) {
              throw new BuilderException("Expression based parameters are not supported yet");
            } else {
              throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}.  Valid properties are " + PARAMETER_PROPERTIES);
            }
          }
          if (typeHandlerAlias != null) {
            builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
          }
          return builder.build();
        }
    
        private Map<String, String> parseParameterMapping(String content) {
          try {
            return new ParameterExpression(content);
          } catch (BuilderException ex) {
            throw ex;
          } catch (Exception ex) {
            throw new BuilderException("Parsing error was found in mapping #{" + content + "}.  Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex);
          }
        }
      }
    

    看了这个类是不是有点找到思路了,因为我们看到了在handleToken方法中我们返回了?,这个和我们创建动态Sql是不是有点关联了?

    这里我们看到了ParameterMapping类,我们也先来看下

    2. ParameterMapping源码学习

    这里我们目前就只介绍它的属性和构造方法,他内置的Builder涉及到的方法太多,并且也都是取值设置值,不影响心情还是不贴了:

    public class ParameterMapping {
    
      private Configuration configuration;
    
      private String property;
      private ParameterMode mode;
      private Class<?> javaType = Object.class;
      private JdbcType jdbcType;
      private Integer numericScale;
      private TypeHandler<?> typeHandler;
      private String resultMapId;
      private String jdbcTypeName;
      private String expression;
    
      private ParameterMapping() {
      }
    }
    

    我们再来看构造出ParameterMapping的方法buildParameterMapping:

    在这个方法中我们主要讲下流程:

    1. 解析传进来的content内容,看是否有变量,如果有从对应参数类中获取这个属性的类型,
    2. 解析其存在他其他如javaType等参数设置到ParameterMapping内部的Builder方法中
    3. 通过Builder构建ParameterMapping

    3. SqlSourceBuilder类总结

    我们来总结这个类的作用。在这个类的解析方法中,我们会把整个content这样的动态语句放进去,之后通过GenericTokenParser扫描在sql中的#{}括起来的变量,把它变成?,这个流程大家应该比较熟悉,之后呢?把它封装成ParameterMapping类来记录我们取出来的,用#{}括起来的属性的相关信息,最后生成一个静态的StaticSqlSource。这样一部操作,我们整个动态Sql就已经生成完毕了。

    4. 今日总结

    今天我们分析了关于MyBatis解析动态sql中的#{}解析的过程,希望大家能有所收获~~~~

    相关文章

      网友评论

          本文标题:MyBatis印象阅读之 SqlSource和Parameter

          本文链接:https://www.haomeiwen.com/subject/qbgtjctx.html