mybatis的LambdaQueryWrapper不配置数据库如何获取即将执行的sql?

比如

public static void main(String[] args) {
    LambdaQueryWrapper<User> q = new LambdaQueryWrapper<>();
    q.eq(User::getName, "tom");
    q.last("limit 1");
    q.得到SQL();
}

我只是想练习或者测试下语法写得对不对,并不会在spring boot里面使用,也不会配置数据库的。

阅读 3.5k
2 个回答

我翻了大部分源码,结论是很难。

LambdaQueryWrapper 是属于 mybatis plus 的类。

而 mybatis plus 的执行实际上并没有拼接 sql,它只是准备了 mybatis 执行所需要的类,再通过拦截等的方式传递给 mybatis,因此 sql 实际上是 mybatis 产生的。

也就是说,研究 mybatis plus 是没用的,必须要知道 mybatis 的内部运作原理,并且准备很多前提条件,才能够生成不执行直接 sql,我想就算达成了这一目的,也已经背离了你这个问题的初衷了。

参考 Can I use MyBatis to generate Dynamic SQL without executing it?

新手上路,请多包涵

想试试对不对是需要通过执行sql来验证的。最简单的是弄个h2数据库。直接写好脚本,启动的时候就把库建好了。

表名和字段的映射

拿你的例子来说,首先在mybatis plus中User可以不做配置,但是配置是要做的,具体来说就是数据库中的名称和实体的名字是通过什么规则来对应上的,比如有没有前缀、是不是把驼峰转成下划线就可以、或者干脆名字一样的等等。当然也可以通过mybatis plus的 @TableName 注解。同理字段映射也是这样的逻辑。映射完之后就是sql如何生成的逻辑了。

SQL如何生成

. 最原始的方式是拿到 SqlSession 对象直接传递在xml中配置的sql的id。这种方式操作比较麻烦,同时和mybatis耦合的比较深。
. 既然SqlSession只需要一个id就可以,那有没有更简单的方式呢?是不是把方法的名称和xml的id对应上就可以了, 于是就有了动态代理的方式,具体mybatis plus的实现可以参考 com.baomidou.mybatisplus.core.MybatisMapperRegistry.getMapper(),同时对于mybatis plus来说他有自定义的注解什么的,如果每次都用到时再扫描注解那效率就低了,所以要提前扫描注解。如何去根据类扫描具体逻辑在 com.baomidou.mybatisplus.core.MybatisMapperRegistry.addMapper

回到题中的如何来生成sql。首先来看下具体的过程
在上面的 addMapper 方法中调用了 MybatisMapperAnnotationBuilder.parse() 方法。

@Override
    public void parse() {
        String resource = type.toString();
        if (!configuration.isResourceLoaded(resource)) {
            loadXmlResource();
            configuration.addLoadedResource(resource);
            String mapperName = type.getName();
            assistant.setCurrentNamespace(mapperName);
            parseCache();
            parseCacheRef();
            InterceptorIgnoreHelper.InterceptorIgnoreCache cache = InterceptorIgnoreHelper.initSqlParserInfoCache(type);
            for (Method method : type.getMethods()) {
                if (!canHaveStatement(method)) {
                    continue;
                }
                if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
                    && method.getAnnotation(ResultMap.class) == null) {
                    parseResultMap(method);
                }
                try {
                    // TODO 加入 注解过滤缓存
                    InterceptorIgnoreHelper.initSqlParserInfoCache(cache, mapperName, method);
                    parseStatement(method);
                } catch (IncompleteElementException e) {
                    // TODO 使用 MybatisMethodResolver 而不是 MethodResolver
                    configuration.addIncompleteMethod(new MybatisMethodResolver(this, method));
                }
            }
            // TODO 注入 CURD 动态 SQL , 放在在最后, because 可能会有人会用注解重写sql
            try {
                // https://github.com/baomidou/mybatis-plus/issues/3038
                if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {
                    parserInjector();
                }
            } catch (IncompleteElementException e) {
                configuration.addIncompleteMethod(new InjectorResolver(this));
            }
        }
        parsePendingMethods();
    }

    void parserInjector() {
        GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
    }

parse方法是上面这样的,具体核心在 parserInjector 方法。可以看到在这个方法中调用了 ISqlInjector.inspectInject() 方法。 ISqlInjector 的抽象类是 AbstractSqlInjector,它的方法 inspectInject 实现是这样的

 @Override
    public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
        Class<?> modelClass = ReflectionKit.getSuperClassGenericType(mapperClass, Mapper.class, 0);
        if (modelClass != null) {
            String className = mapperClass.toString();
            Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());
            if (!mapperRegistryCache.contains(className)) {
                TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
                List<AbstractMethod> methodList = this.getMethodList(mapperClass, tableInfo);
                if (CollectionUtils.isNotEmpty(methodList)) {
                    // 循环注入自定义方法
                    methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));
                } else {
                    logger.debug(mapperClass.toString() + ", No effective injection method was found.");
                }
                mapperRegistryCache.add(className);
            }
        }
    }

这个方法的作用就是来生成sql的,上面说了动态代理是和方法名字相关的,那是不是只要能生成方法对应的sql就可以了

就他来说就是 methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo)); 这行。他的作用就是生成 com.baomidou.mybatisplus.core.mapper.BaseMapper 中的sql。可以看到 AbstractMethod 中有很多实现类,每个实现类对应1个方法,要是 方法没有对应的 AbstractMethod 实现就不行了。

那接下来就看看 AbstractMethod 是如何工作的

SelectList 来说

@Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        SqlMethod sqlMethod = SqlMethod.SELECT_LIST;
        String sql = String.format(sqlMethod.getSql(), sqlFirst(), sqlSelectColumns(tableInfo, true), tableInfo.getTableName(),
            sqlWhereEntityWrapper(true, tableInfo), sqlOrderBy(tableInfo), sqlComment());
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return this.addSelectMappedStatementForTable(mapperClass, getMethod(sqlMethod), sqlSource, tableInfo);
    }

可以看到里面有个 sql 变量。就是这么拼出来的。也就是mybatis plus的sql是动态拼出来的。

LambdaQueryWrapper 又是如何去处理的,每个生成sql的条件可能都不一样,结果肯定不是一样的sql,这就不符合一个方法对应一个sql了。但是别忘了mybatis有个经常用到的特性就是动态sql。有个动态sql就有了一对一的保障。

具体可以看看 sqlMethod.getSql() 和 sqlWhereEntityWrapper 的实现,这里就不再深入了。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
1 篇内容引用
推荐问题
宣传栏