mybatis打印sql语句到控制台_MyBatis如何获取Sql语句

随着上一篇文章中 configuration.setEnvironment(environmentBuilder.build()) 语句被执行

数据源已经锁定,环境已经确定

接下来就是如何获取 Sql 语句

还记得上一篇文章遗留下的一个问题

mapperElement(root.evalNode("mappers"))

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
                String mapperPackage = child.getStringAttribute("name");
                configuration.addMappers(mapperPackage);
            } else {
                String resource = child.getStringAttribute("resource");
                String url = child.getStringAttribute("url");
                String mapperClass = child.getStringAttribute("class");
                if (resource != null && url == null && mapperClass == null) {
                    ErrorContext.instance().resource(resource);
                    InputStream inputStream = Resources.getResourceAsStream(resource);
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                    mapperParser.parse();
                } else if (resource == null && url != null && mapperClass == null) {
                    ErrorContext.instance().resource(url);
                    InputStream inputStream = Resources.getUrlAsStream(url);
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                    mapperParser.parse();
                } else if (resource == null && url == null && mapperClass != null) {
                    Class> mapperInterface = Resources.classForName(mapperClass);
                    configuration.addMapper(mapperInterface);
                } else {
                    throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                }
            }
        }
    }
}

从代码中得知,mappers总共有四种方法进行配置,在 MyBatis 官方文档中也有描述这四种映射器的使用

  • resource:配置所有xml文件在resource包下的相对路径

  • url:配置所有xml文件在系统盘中的绝对路径

  • class:所有mapper 接口的包名 + 类名

  • package:接口的包名,一般只需要配置一个

09ff7fa7f796e3fcf74ae0311f1038f5.png

回到代码中,我们可以看到方法的主体是遍历 mappers 中的 mapper 节点,然后逐一解析

解析 mapper

我们可以看出,解析 package 和 class 节点的流程是一样的,都是通过类名去创建 class 类,再通过类名绑定 xml 文件

而 resource 和 url 的解析方式是一样的,都是通过输入流创建 XMLMapperBuilder 类并执行 parse 方法

他们的区别就是 package 和 class 加载的是 mapper 接口,而 resource 和 url 加载的是 resource 包下的 xml 文件

package(class) 是如何解析的

String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);

如上所示,先调用 getStringAttribute 方法获得包名

再使用 addMappers 方法解析包名

addMappers(mapperPackage)

public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
}

public void addMappers(String packageName, Class> superType) {
    ResolverUtil> resolverUtil = new ResolverUtil<>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);// 通过 resolverUtil 解析包名,并将包下的所有类存放进 Set 中
    Set>> mapperSet = resolverUtil.getClasses();// 遍历 Set,对每个mapper的class类进行解析for (Class> mapperClass : mapperSet) {
        addMapper(mapperClass);
    }
}public  void addMapper(Class type) {if (type.isInterface()) {if (hasMapper(type)) {// 判断是否已经有了该 mapper 的类型throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }boolean loadCompleted = false;try {// 将该mapper的类型移入已知的范围
            knownMappers.put(type, new MapperProxyFactory<>(type));// It's important that the type is added before the parser is run// otherwise the binding may automatically be attempted by the// mapper parser. If the type is already known, it won't try.
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);// 最终调用的是parse()方法
            parser.parse();
            loadCompleted = true;
        } finally {// 如果try中的代码块执行出错,则将该mapper的类型移除已知的范围if (!loadCompleted) {
                knownMappers.remove(type);
            }
        }
    }
}

在几个 addMapper 方法的调用中,核心是将 package 下的所有 mapper 接口解析成 class 类,并交由 MapperAnnotationBuilder 的 parse 方法去解析

parse()

public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
        // 如果对应的xml文件还未被加载,则加载对应xml文件
        loadXmlResource();
        configuration.addLoadedResource(resource);
        assistant.setCurrentNamespace(type.getName());
        parseCache();
        parseCacheRef();
        // 获取mapper下的每个方法,并进行遍历
        Method[] methods = type.getMethods();
        for (Method method : methods) {
            try {
                // 这里是对mapper上的注解进行解析,因为我们没有注解,因此跳过
                if (!method.isBridge()) {
                    parseStatement(method);
                }
            } catch (IncompleteElementException e) {
                // 最终配置文件加载了一个默认的方法解析器
                configuration.addIncompleteMethod(new MethodResolver(this, method));
            }
        }
    }
    parsePendingMethods();
}

这个方法的作用是加载 xml 文件,并解析接口上的注解。

因为我们没有注解,因此 loadXmlResource() 方法是这个类中最重要的方法,它的底层通过输入流执行了 xmlParser.parse() 方法,对xml 文件进行详细解析,这个方法在 resource 中也会被调用,我们接下来就来看 resource 方式是如何解析 xml 文件的。

resource(url)的解析方式

ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();

通过将 resource 的值,即xml文件转化为输入流,调用 mapperParser.parse() 进行解析。

这个 mapperParser.parse() 与上文提及的 xmlParser.parse() 其实是是同一个方法

mapperParser.parse()

public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
        configurationElement(parser.evalNode("/mapper"));
        configuration.addLoadedResource(resource);
        bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
}

这个方法的主体是 configurationElement(parser.evalNode("/mapper")),在 evalNode 方法加载了 mapper 节点后,进行解析。后面的几个方法是对处理的结果进行再次解析。

configurationElement(parser.evalNode("/mapper"))

private void configurationElement(XNode context) {
    try {
        String namespace = context.getStringAttribute("namespace");
        if (namespace == null || namespace.equals("")) {
            throw new BuilderException("Mapper's namespace cannot be empty");
        }
        builderAssistant.setCurrentNamespace(namespace);
        cacheRefElement(context.evalNode("cache-ref"));
        cacheElement(context.evalNode("cache"));
        parameterMapElement(context.evalNodes("/mapper/parameterMap"));
        resultMapElements(context.evalNodes("/mapper/resultMap"));
        sqlElement(context.evalNodes("/mapper/sql"));
        buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
}

这个方法执行了缓存的解析、parameterMap 的解析、resultMap 的解析等,当然,我们要把重点放到这个方法的第13行:buildStatementFromContext(context.evalNodes("select|insert|update|delete")) 方法,这个方法是解析 Sql 语句的核心

buildStatementFromContext

这个方法过长,但这个方法的根本目的就是将 xml 文件解析成数据库能够读取的 Sql 文件

public void parseStatementNode() {
    // 获取 id 和 databaseId 属性
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
        return;
    }

    // 获取节点名称,比如节点就会被解析为"select"
    String nodeName = context.getNode().getNodeName();
    // 根据节点名称解析 SqlCommandType
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // 解析节点
    XMLIncludeTransformer Parser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    String parameterType = context.getStringAttribute("parameterType");
    Class> parameterTypeClass = resolveClass(parameterType);

    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    // 解析节点
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // 解
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
        keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
        keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
                                                   configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
            ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    // 解析Sql语句的方法
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));

    // 以下是解析各种节点中对应的属性
    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String resultType = context.getStringAttribute("resultType");
    Class> resultTypeClass = resolveClass(resultType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
        resultSetTypeEnum = configuration.getDefaultResultSetType();
    }
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    String resultSets = context.getStringAttribute("resultSets");

    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
                                        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
                                        resultSetTypeEnum, flushCache, useCache, resultOrdered,
                                        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

这段代码很长,长得我一开始不想贴上这段代码。但如果耐心仔细看一下这段代码,发现里面其实很大篇幅是在获取节点的属性并解析。如果忽视这部分代码,其实这段代码只是干了下面这些事:

  • 解析节点

  • 解析节点

  • 解析 Sql 语句,获取 SqlSource

  • 构建 MappedStatement 实例

经历这四个步骤之后,Sql 语句就被解析并存储在 MappedStatement 当中,因为篇幅原因,这四个步骤我们在下一篇文章再进行学习。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值