Mybatis方法--动态创建SQL版权声明

原创
小哥 3年前 (2022-10-27) 阅读数 5 #大杂烩

这篇文章将带您分析Mybatis是如何动态生成SQL。
首先,根据源代码分析框架进行初始化。xml文件加载、解析、缓存过程。亮点 xml解析过程 和 使用解析后的结果,最后列出示例并控制源代码。 DeBug 分析:当 DAO 标签的解析、参数的创建SQL并对整个过程进行总结。

  • 数据处理

Mybatis对数据处理可以分为 使用动态参数进行装配sql对sql执行的结果被封装。 JavaBean

这里包括两个进程: 1. 在查询阶段,我们将java数据类型,已转换jdbc数据类型, preparedStatement.setXXX() 来设值 2. 另一种观点是正确的。resultset查询结果集jdbcType 数据转换java 数据类型,本文仅介绍第一个过程。

  • 基于传入参数的动态装配。sql

Mybatis 在,需要基于xml标签的语法编译了动力学。SQL,将在执行过程中根据标签进行解析,这里使用的是。 Ognl 解析动态构造的标签SQL语句

  • 分析 parseDynamicTags 解析过程:

SpringMybatis 整合需要配置 SqlSessionFactoryBean ,配置联接数据源并Mybatis xml配置文件路径和其他信息


    
    
    

其中 SqlSessionFactoryBean 实现了 Spring的InitializingBean 接口, InitializingBean 接口的 afterPropertiesSet 方法被调用 buildSqlSessionFactory 方法 该方法在内部使用。 XMLConfigBuilder 解析属性 configLocation 还将使用中配置的路径。 XMLMapperBuilder 属性解析 mapperLocations 每个属性xml文件在启动时将基于xml文件的配置路径已解析。xml文件,让我们看看加载时的一些源代码,并做简单的分析。读者可以关注一些带注释的代码:

XMLMapperBuilder:

/* 读者可以将注意力集中在 加注解 代码的一部分就足够了。  */
public class XMLMapperBuilder extends BaseBuilder {

  public void parse() {
        if(!this.configuration.isResourceLoaded(this.resource)) {
            //根据xpath解析mapper节点 
            this.configurationElement(this.parser.evalNode("/mapper"));
            this.configuration.addLoadedResource(this.resource);
            this.bindMapperForNamespace();
        }

        this.parsePendingResultMaps();
        this.parsePendingChacheRefs();
        this.parsePendingStatements();
    }

    /**
     * 根据xpath解析mapper节点
     */
    private void configurationElement(XNode context) {
        try {
            String namespace = context.getStringAttribute("namespace");
            if(namespace.equals("")) {
                throw new BuilderException("Mappers namespace cannot be empty");
            } else {
                //分配当前处理。mapper的namespace
                this.builderAssistant.setCurrentNamespace(namespace);
                //正在处理二级缓存
                this.cacheRefElement(context.evalNode("cache-ref"));
                this.cacheElement(context.evalNode("cache"));
                //处理parameterMap节点
              this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
                //处理resultMap节点
                this.resultMapElements(context.evalNodes("/mapper/resultMap"));
                //处理sql节点
                this.sqlElement(context.evalNodes("/mapper/sql"));
                //处理select|insert|update|delete节点
                this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
            }
        } catch (Exception var3) {
            throw new BuilderException("Error parsing Mapper XML. Cause: " + var3, var3);
        }
    }

    /**
     * 处理select|insert|update|delete节点
     */
    private void buildStatementFromContext(List list) {
        if(this.configuration.getDatabaseId() != null) {
            this.buildStatementFromContext(list, this.configuration.getDatabaseId());
        }

        this.buildStatementFromContext(list, (String)null);
    }

    /**
     * 处理select|insert|update|delete节点
     */
    private void buildStatementFromContext(List list, String requiredDatabaseId) {
        Iterator i$ = list.iterator();

        while(i$.hasNext()) {
            XNode context = (XNode)i$.next();
            //对于每个节点XMLStatementBuilder进行解析
            XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);
            try {
                //解析每个节点
                statementParser.parseStatementNode();
            } catch (IncompleteElementException var7) {
                this.configuration.addIncompleteStatement(statementParser);
            }
        }
    }
}

XMLStatementBuilder :

public class XMLStatementBuilder extends BaseBuilder {

   /**
    * 解析节点
    */
  public void parseStatementNode() {
        String id = this.context.getStringAttribute("id");
        String databaseId = this.context.getStringAttribute("databaseId");
        if(this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
            Integer fetchSize = this.context.getIntAttribute("fetchSize");
            Integer timeout = this.context.getIntAttribute("timeout");
            String parameterMap = this.context.getStringAttribute("parameterMap");
            String parameterType = this.context.getStringAttribute("parameterType");
            Class parameterTypeClass = this.resolveClass(parameterType);
            String resultMap = this.context.getStringAttribute("resultMap");
            String resultType = this.context.getStringAttribute("resultType");
            String lang = this.context.getStringAttribute("lang");

            //使用LanguageDriver进行解析SQL
            LanguageDriver langDriver = this.getLanguageDriver(lang);

            Class resultTypeClass = this.resolveClass(resultType);
            String resultSetType = this.context.getStringAttribute("resultSetType");
            StatementType statementType = StatementType.valueOf(this.context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
            ResultSetType resultSetTypeEnum = this.resolveResultSetType(resultSetType);
            String nodeName = this.context.getNode().getNodeName();
            SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
            boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
            boolean flushCache = this.context.getBooleanAttribute("flushCache", Boolean.valueOf(!isSelect)).booleanValue();
            boolean useCache = this.context.getBooleanAttribute("useCache", Boolean.valueOf(isSelect)).booleanValue();
            boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", Boolean.valueOf(false)).booleanValue();
            XMLIncludeTransformer includeParser = new XMLIncludeTransformer(this.configuration, this.builderAssistant);
            includeParser.applyIncludes(this.context.getNode());
            this.processSelectKeyNodes(id, parameterTypeClass, langDriver);

            //解析创建SQL
            SqlSource sqlSource = langDriver.createSqlSource(this.configuration, this.context, parameterTypeClass);

            //其他代码在此省略
    }  
}

将使用默认设置。XMLLanguageDriver创建SqlSource(Configuration在构造函数中设置)。

XMLLanguageDriver 创建 SqlSource

public class XMLLanguageDriver implements LanguageDriver {
    public SqlSource createSqlSource(Configuration configuration, XNode script, Class parameterType) {
        XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
        //使用XMLScriptBuilder的parseScriptNode该方法解析节点的SQL部分
        return builder.parseScriptNode();
    }
}

XMLScriptBuilder解析:

public class XMLScriptBuilder extends BaseBuilder {
    public SqlSource parseScriptNode() {
        //如果有子节点,则解析节点将递归调用解析
        List contents = this.parseDynamicTags(this.context);
        MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
        SqlSource sqlSource = null;
        if(this.isDynamic) {
            sqlSource = new DynamicSqlSource(this.configuration, rootSqlNode);
        } else {
            sqlSource = new RawSqlSource(this.configuration, rootSqlNode, this.parameterType);
        }

        return (SqlSource)sqlSource;
    }
}

XMLScriptBuilder 所有节点的递归解析:

public class XMLScriptBuilder extends BaseBuilder {
    private XNode context;
    private boolean isDynamic;
    private Class parameterType;
    private Map nodeHandlers;

    public XMLScriptBuilder(Configuration configuration, XNode context) {
        this(configuration, context, (Class)null);
    }

    public XMLScriptBuilder(Configuration configuration, XNode context, Class parameterType) {
        super(configuration);
        this.nodeHandlers = new HashMap() {
            private static final long serialVersionUID = 7123056019193266281L;

            {
                //不同的标签有不同的解析类。
                this.put("trim", XMLScriptBuilder.this.new TrimHandler(null));
                this.put("where", XMLScriptBuilder.this.new WhereHandler(null));
                this.put("set", XMLScriptBuilder.this.new SetHandler(null));
                this.put("foreach", XMLScriptBuilder.this.new ForEachHandler(null));
                this.put("if", XMLScriptBuilder.this.new IfHandler(null));
                this.put("choose", XMLScriptBuilder.this.new ChooseHandler(null));
                this.put("when", XMLScriptBuilder.this.new IfHandler(null));
                this.put("otherwise", XMLScriptBuilder.this.new OtherwiseHandler(null));
                this.put("bind", XMLScriptBuilder.this.new BindHandler(null));
            }
        };
        this.context = context;
        this.parameterType = parameterType;
    }

    /**
     * 所有节点的递归解析
     */
    private List parseDynamicTags(XNode node) {
        List contents = new ArrayList();
        NodeList children = node.getNode().getChildNodes();

        for(int i = 0; i < children.getLength(); ++i) {
            XNode child = node.newXNode(children.item(i));
            String nodeName;
            if(child.getNode().getNodeType() != 4 && child.getNode().getNodeType() != 3) {
                if(child.getNode().getNodeType() == 1) {
                    nodeName = child.getNode().getNodeName();
                    //根据不同的标签使用不同的解析类。
                    XMLScriptBuilder.NodeHandler handler = (XMLScriptBuilder.NodeHandler)this.nodeHandlers.get(nodeName);
                    if(handler == null) {
                        throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
                    }
                    //解析
                    handler.handleNode(child, contents);
                    this.isDynamic = true;
                }
            } else {
                nodeName = child.getStringBody("");
                TextSqlNode textSqlNode = new TextSqlNode(nodeName);
                if(textSqlNode.isDynamic()) {
                    contents.add(textSqlNode);
                    this.isDynamic = true;
                } else {
                    contents.add(new StaticTextSqlNode(nodeName));
                }
            }
        }

        return contents;
    }

    /**
     * 内部类IfHandler的实现
     */
    private class IfHandler implements XMLScriptBuilder.NodeHandler {
        private IfHandler() {
        }

        public void handleNode(XNode nodeToHandle, List targetContents) {
            List contents = XMLScriptBuilder.this.parseDynamicTags(nodeToHandle);
            MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
            String test = nodeToHandle.getStringAttribute("test");
            IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
            targetContents.add(ifSqlNode);
        }
    }
  //其他标签类型。handler不是一个接一个的例子,感兴趣的可以看到 XMLScriptBuilder 源代码实现
}
  • XMLConfigBuilder :解析mybatis中configLocation属性中的全局xml文件,该文件将在内部使用。 XMLMapperBuilder 解析各个xml文件。
  • XMLMapperBuilder :遍历mybatis中mapperLocations属性中的xml文件中每个节点的Builder,比如user.xml,内部将使用 XMLStatementBuilder 处理xml中的每个节点。
  • XMLStatementBuilder :解析xml文件中的每个节点,如 select,insert,update,delete 节点,内部将使用 XMLScriptBuilder 处理节点的sql部分,则将遍历生成的数据拖放到Configuration的mappedStatements中。
  • XMLScriptBuilder :解析xml中的每个节点sql部分的Builder。

至此,mapper.xml该文件已被解析、加载和获取 SqlSourceSqlSource 将会放到 Configuration 中,有了 SqlSource ,在实施的时间上会有所依据。 SqlSource 获取 BoundSql 从而获得所需的 SQLConfiguration 可以被视为一个巨大的资源池,Mybatis框架执行所需的数据可以是 Configuration 中获取, Configuration 的源码为:

public class Configuration {
    protected Environment environment;
    protected boolean safeRowBoundsEnabled;
    protected boolean safeResultHandlerEnabled;
    protected boolean mapUnderscoreToCamelCase;
    protected boolean aggressiveLazyLoading;
    protected boolean multipleResultSetsEnabled;
    protected boolean useGeneratedKeys;
    protected boolean useColumnLabel;
    protected boolean cacheEnabled;
    protected boolean callSettersOnNulls;
    protected String logPrefix;
    protected Class logImpl;
    protected LocalCacheScope localCacheScope;
    protected JdbcType jdbcTypeForNull;
    protected Set lazyLoadTriggerMethods;
    protected Integer defaultStatementTimeout;
    protected ExecutorType defaultExecutorType;
    protected AutoMappingBehavior autoMappingBehavior;
    protected Properties variables;
    protected ObjectFactory objectFactory;
    protected ObjectWrapperFactory objectWrapperFactory;
    protected MapperRegistry mapperRegistry;
    protected boolean lazyLoadingEnabled;
    protected ProxyFactory proxyFactory;
    protected String databaseId;
    protected Class configurationFactory;
    protected final InterceptorChain interceptorChain;
    protected final TypeHandlerRegistry typeHandlerRegistry;
    protected final TypeAliasRegistry typeAliasRegistry;
    protected final LanguageDriverRegistry languageRegistry;
    protected final Map mappedStatements;
    protected final Map caches;
    protected final Map resultMaps;
    protected final Map parameterMaps;
    protected final Map keyGenerators;
    protected final Set loadedResources;
    protected final Map sqlFragments;
    protected final Collection incompleteStatements;
    protected final Collection incompleteCacheRefs;
    protected final Collection incompleteResultMaps;
    protected final Collection incompleteMethods;
    protected final Map cacheRefMap;

  //SomeMethod...
}

以下示例说明:
在我们使用的示例中:

//mapper接口的方法
schoolCustomerDao.selectBySome(1l,  "2017-09-17","120706049");

此SQL使用 if 标签

在执行 schoolCustomerDao.selectBySome(1l, "2017-09-17","120706049"); 时,
mapper代理类将首先确定此方法是否存在于缓存中,如果不存在则需要加载,如果已存在则直接调用,然后根据 select|insert|update|delete 呼叫类型不同。 SqlSession 方法,该方法基于调用前的条目。 (1l, "2017-09-17","120706049") 套餐参数,套餐参数源码如下:

//MapperMethod该类执行此方法来组合参数。
param = this.method.convertArgsToSqlCommandParam(args);
/**
 * 拼装入参
 */
public Object convertArgsToSqlCommandParam(Object[] args) {
            int paramCount = this.params.size();
            if(args != null && paramCount != 0) {
                if(!this.hasNamedParameters && paramCount == 1) {
                    return args[((Integer)this.params.keySet().iterator().next()).intValue()];
                } else {
                    Map param = new MapperMethod.ParamMap();
                    int i = 0;
                    //召集参赛者key value 格式,并使用param+数字作为key对value按条目顺序排序
                    for(Iterator i$ = this.params.entrySet().iterator(); i$.hasNext(); ++i) {
                        Entry entry = (Entry)i$.next();
                        param.put(entry.getValue(), args[((Integer)entry.getKey()).intValue()]);
                        String genericParamName = "param" + String.valueOf(i + 1);
                        if(!param.containsKey(genericParamName)) {
                            param.put(genericParamName, args[((Integer)entry.getKey()).intValue()]);
                        }
                    }
                    return param;
                }
            } else {
                return null;
            }
        }

在封装参数时,不仅会封装条目参数,还会按照条目顺序进行封装,带有 param + 数字 作为key,输入value 放入 Map 中,如下所示:

封装参数后的结果

在封装参数之后, 该方法的全名也是 StatementId (在本例中: com.school.dao.SchoolCustomerDao.selectBySome )和传入的封装参数:

//调用查询 this.command.getName() 为 com.school.dao.SchoolCustomerDao.selectBySome
result = sqlSession.selectOne(this.command.getName(), param);

此步骤已准备好开始解析和生成SQL了,
先取出 Configuration 中的 MappedStatement ,根据大会的参与度 SQL 再执行

//从configuration中获取MappedStatement
//statement 为 com.school.dao.SchoolCustomerDao.selectBySome
MappedStatement ms = this.configuration.getMappedStatement(statement);
//调用查询
List result = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

/**
 * 调用查询
 */
public  List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        //将在此处生成部件。BoundSql
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
        //执行查询
        return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

getBoundSql 拼装的SQL代码为:

/**
 * BoundSql boundSql = ms.getBoundSql(parameter);
 * 调用以下方法类中的方法以获取。 BoundSql 
 */
public BoundSql getBoundSql(Object parameterObject) {
        //会有所不同,具体取决于sqlSource类型执行不同的解析,
       //被叫到这里DynamicSqlSource并返回解析后的方法。BoundSql,并已排序,需要替换的参数如下,下面将详细说明。
        BoundSql boundSql = this.sqlSource.getBoundSql(parameterObject);
        List parameterMappings = boundSql.getParameterMappings();
        if(parameterMappings == null || parameterMappings.size() <= 0) {
            boundSql = new BoundSql(this.configuration, boundSql.getSql(), this.parameterMap.getParameterMappings(), parameterObject);
        }
        Iterator i$ = boundSql.getParameterMappings().iterator();

        while(i$.hasNext()) {
            ParameterMapping pm = (ParameterMapping)i$.next();
            String rmId = pm.getResultMapId();
            if(rmId != null) {
                ResultMap rm = this.configuration.getResultMap(rmId);
                if(rm != null) {
                    this.hasNestedResultMaps |= rm.hasNestedResultMaps();
                }
            }
        }

        return boundSql;
    }

替换SQL中的相应参数

//DynamicSqlSource类
public class DynamicSqlSource implements SqlSource {
    private Configuration configuration;
    private SqlNode rootSqlNode;

    public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
        this.configuration = configuration;
        this.rootSqlNode = rootSqlNode;
    }
    /**
     * 解析SQL
     */
    public BoundSql getBoundSql(Object parameterObject) {
        DynamicContext context = new DynamicContext(this.configuration, parameterObject);
        //会根据 rootSqlNode 中每个节点的内容 MixedSqlNode 的 apply 方法,分析合并SQL
        this.rootSqlNode.apply(context);
        //用 SqlSourceBuilder 将SQL中的 #{} 换成 ?
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(this.configuration);
        Class parameterType = parameterObject == null?Object.class:parameterObject.getClass();
        SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        Iterator i$ = context.getBindings().entrySet().iterator();

        while(i$.hasNext()) {
            Entry entry = (Entry)i$.next();
            boundSql.setAdditionalParameter((String)entry.getKey(), entry.getValue());
        }
        return boundSql;
    }
}

debug有关某些参数的信息

/**
*  this.rootSqlNode.apply(context);
*  会调用MixedSqlNode类方法解析程序集。SQL
*/
public class MixedSqlNode implements SqlNode {
   private List contents;

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

   public boolean apply(DynamicContext context) {
       Iterator i$ = this.contents.iterator();

       while(i$.hasNext()) {
           SqlNode sqlNode = (SqlNode)i$.next();
           //解析和汇编,调用下面的此处。 IfSqlNode 的 apply 方法
           sqlNode.apply(context);
       }

       return true;
   }
}

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();
   }
   //被调用的 IfSqlNode 的 apply 方法 
   public boolean apply(DynamicContext context) {
       //调用 ExpressionEvaluator 的 evaluateBoolean 方法
       //确定执行结果是否为true
       if(this.evaluator.evaluateBoolean(this.test, context.getBindings())) {
           this.contents.apply(context);
           return true;
       } else {
           return false;
       }
   }
}

public class ExpressionEvaluator {
   public ExpressionEvaluator() {
   }
    //被调用的 ExpressionEvaluator 的 evaluateBoolean 方法
   public boolean evaluateBoolean(String expression, Object parameterObject) {
       //调用 Ognl 分析、实施细节不再精细梳理,有兴趣的读者可DeBug查看
       Object value = OgnlCache.getValue(expression, parameterObject);
       return value instanceof Boolean?((Boolean)value).booleanValue():(value instanceof Number?!(new BigDecimal(String.valueOf(value))).equals(BigDecimal.ZERO):value != null);
   }
}

sqlNode.apply(context);这里使用了对该方法的调用。Ognl进行解析

解析完成后,将替换一个参数 ? 的SQL,只需在执行时调用preparedStatement.setXXX()将List parameterMappings 如果中的参数SQL。到目前为止,动态解析Mybatis标签生成SQL,已经完成。

总结:

  • Mybatis中 mapper.xml 重新加载文件时,将解析该文件 rootSqlNode 节点
  • 调用mapper的DAO接口的方法被代理,参数被封装,并被传入。StatementId (方法全名限制)
  • 根据StatementId获取到 rootSqlNode 节点、循环调用 MixedSqlNode 方法,使用Ognl 分析的结果被组装并返回。
  • 使用 SqlSourceBuilder 将#{} 内容解析,生成排序参数 List<ParameterMapping> parameterMappings 并将 #{} 替换成 ?
  • 最终调用JDBC中的 preparedStatement.setXXX() 方法中,已排序的参数。 List<ParameterMapping> parameterMappings 中的参数将按顺序排列。?替换,您将获得一个完整的SQL

以上是“Mybatis原理--动态生成SQL如整篇内容有误,敬请更正,取长补短,共同进步。谢谢。

作者:ChinaXieShuai
链接:https://www.jianshu.com/p/c4ce04fc5627
资料来源:简讯
版权归作者所有。请联系作者以获得商业转载的授权,并注明非商业转载的来源。

版权声明

所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除