首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >MyBatis-Plus条件构造器的一个注意事项

MyBatis-Plus条件构造器的一个注意事项

作者头像
烟雨平生
发布2023-03-07 10:56:57
发布2023-03-07 10:56:57
6720
举报
文章被收录于专栏:数字化之路数字化之路
城里八月中 . 大片的白云好治愈啊
发现异常

上线完成后,巡检日志。发现druid报了一个slow sql的错

ERROR c.a.druid.filter.stat.StatFilter - slow sql 1909 millis.

看了下,发现这个sql有些不一样:

代码语言:javascript
复制
select id, biz_filed_1from table1WHERE status IN (?, ?)  AND biz_date IS NOT NULL
  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id > ?  AND id

因为id是主键,经过mysql优化器的处理,这种sql的执行结果也是对的。

但是,MySQL默认sql语句最大为1M。如果不解决,当超过这个限制时,就报错了

初步分析

问题的范围应该在MyBatis-Plus的条件构造器。拼sql使用的是MyBatis-Plus的查询条件构造器QueryWrapper。涉及到代码如下:

代码语言:javascript
复制
public class BizDataService {  

    public void doTask() {
        log.info("任务开始");
        QueryWrapper<BizData> queryWrapper = new QueryWrapper<>();
        queryWrapper.in("status", 1, 2, 3);
        queryWrapper.isNotNull("biz_filed_1");
        queryWrapper.select("id", "biz_filed_1");
        queryWrapper.orderByAsc("id");
        queryWrapper.last("limit 10 ");
        List<BizData> bizDataList = bizDataService.list(queryWrapper);        if (CollectionUtils.isEmpty(bizDataList)) {
            log.info("没有满足条件的数据");            return;
        }        while (true) {
            updateData(traceId, bizDataList);            if (CollectionUtils.isEmpty(bizDataList)) {
                log.info("任务 完成 ");                return;
            }
            Long lastId = bizDataList.get(bizDataList.size() - 1).getId();
            log.info(" lastId {} ", lastId);
            queryWrapper.gt("id", lastId);
            bizDataList = bizDataService.list(queryWrapper);
        }

    }


}

有问题的sql,应该出现在构建 id>? 环节 :

代码语言:javascript
复制
queryWrapper.gt("id",lastId);

因为要取已经处理过的最大的id,所以放在while循环中了。为了复用,直接使用了方法最开始的条件构造器queryWrapper。结合上面的慢sql,很可能是MyBatis-Plus拼sql的条件构造器没有做去重处理。 不过单从上面这个场景来看,MyBatis-Plus作为基础框架也不知道应该保留那一次,最多能做的是遇到重复的做个去重。

复现

case :

  1. 使用MyBatis-Plus的条件构造器来构建查询语句
  2. 条件构造器的Wrapper.gt对同一个字段要执行多次
代码语言:javascript
复制
@Slf4j@SpringBootTest(classes = {MpIntroductionApplication.class})    class TaskServiceImplTest {    

    @Autowired
    private TaskService taskService;   

    @Test
    public void testWrapperWhen2Gt() {
        QueryWrapper<Task> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("id", "name");
        queryWrapper.orderByAsc("id");
        queryWrapper.last("limit 10");        for (int i = 0; i < 2; i++) {
            queryWrapper.gt("id", 10);
        }
        List<Task> taskList = taskService.list(queryWrapper);
        log.info("{} ", taskList);
    }

}

执行结果:

代码语言:javascript
复制
2022-08-16 10:04:29.535 DEBUG 57006 --- [main] c.d.m.a.r.t.m.TaskMapper.selectList      : ==>  Preparing: SELECT id,name FROM task WHERE deleted=false AND (id > ? AND id > ?) ORDER BY id ASC limit 10
2022-08-16 10:04:29.560 DEBUG 57006 --- [main] c.d.m.a.r.t.m.TaskMapper.selectList      : ==> Parameters: 10(Integer), 10(Integer)
2022-08-16 10:04:29.584 DEBUG 57006 --- [main] c.d.m.a.r.t.m.TaskMapper.selectList      : <==      Total: 10

复现了。MyBatis-Plus的Wrapper生成sql时,是一种append操作。

解决办法

使用条件构造器Wrapper时,单独构建每次用到的SQL。

代码语言:javascript
复制
public class BizDataService {    public void doTask() {
        log.info("任务开始");
        QueryWrapper<BizData> queryWrapper = new QueryWrapper<>();
        queryWrapper.in("status", 1, 2, 3);
        queryWrapper.isNotNull("biz_filed_1");
        queryWrapper.select("id", "biz_filed_1");
        queryWrapper.orderByAsc("id");
        queryWrapper.last("limit 10 ");
        List<BizData> bizDataList = bizDataService.list(queryWrapper);        if (CollectionUtils.isEmpty(bizDataList)) {
            log.info("没有满足条件的数据");            return;
        }        while (true) {
            updateData(traceId, bizDataList);            if (CollectionUtils.isEmpty(bizDataList)) {
                log.info("任务 完成 ");                return;
            }
            Long lastId = bizDataList.get(bizDataList.size() - 1).getId();
            log.info(" lastId {} ", lastId);            /**
             * 这部分有重复,可以额外抽一个buildQueryWrapper的方法来封装这个重复
             */
            queryWrapper = new QueryWrapper<>();
            queryWrapper.in("status", 1, 2, 3);
            queryWrapper.isNotNull("biz_filed_1");
            queryWrapper.select("id", "biz_filed_1");
            queryWrapper.orderByAsc("id");
            queryWrapper.last("limit 10 ");
            queryWrapper.gt("id", lastId);
            bizDataList = bizDataService.list(queryWrapper);
        }

    }

}
小结

使用新的API或组件时,要有重点地进行测试。

语法糖虽好,用不好会粘牙哦

另,思路比结论重要

拓展:源码分析

展开聊一下。

构建SQL的功能是由类com.baomidou.mybatisplus.core.conditions.AbstractWrapper承担的。来重点看这个类:

AbstractWrapper实际上实现了五大接口:

嵌套接口Nested、

比较接口Compare、

拼接接口Join、

函数接口Func、

SQL片断函数接口ISqlSegment。

Wrapper的gt由比较接口Compare和SQL片断函数接口ISqlSegment来承接。

代码语言:javascript
复制
public interface Compare<Children, R> extends Serializable {      
  
    default Children gt(R column, Object val) {    
        return this.gt(true, column, val);    
    }   

    Children gt(boolean condition, R column, Object val);  
}
代码语言:javascript
复制
public abstract class AbstractWrapper<T, R, Children extends AbstractWrapper<T, R, Children>> extends Wrapper<T> implements Compare<Children, R>, Nested<Children, Children>, Join<Children>, Func<Children, R> {     
    public Children gt(boolean condition, R column, Object val) {    
        /**
         * 条件构造器的处理策略是append   
         */
        return this.addCondition(condition, column, SqlKeyword.GT, val);   
    }   
    
    protected Children addCondition(boolean condition, R column, SqlKeyword sqlKeyword, Object val) {     
        return this.maybeDo(condition, () -> {    
            /**   
             * 所有的拼sql指令,都是由appendSql来承接   
             */   
            this.appendSqlSegments(this.columnToSqlSegment(column), sqlKeyword, () -> {   
                return this.formatParam((String) null, val);   
            });   
        });   
    }   

    /**
     * 所有append就是把所有的条件add到一个List中   
     * @param sqlSegments  
     */   
    protected void appendSqlSegments(ISqlSegment... sqlSegments) {   
        this.expression.add(sqlSegments);   
    }   
  
}

gt gt(R column, Object val) gt(boolean condition, R column, Object val) 大于 > 例: gt("age", 18)--->age > 18 https://baomidou.com/pages/10c804/#ne

MyBatis-Plus唯一进行过去重的是last方法: last

代码语言:javascript
复制
last(String lastSql)   
last(boolean condition,String lastSql)

无视优化规则直接拼接到 sql 的最后

注意事项: 只能调用一次,多次调用以最后一次为准 有sql注入的风险,请谨慎使用 例: last("limit 1")

last方法的实现:覆盖上一次

代码语言:javascript
复制
public interface Join<Children> extends Serializable {   
    default Children last(String lastSql) {  
        return this.last(true, lastSql);  
    }  
    
    Children last(boolean condition, String lastSql);    
    
}
代码语言:javascript
复制
public abstract class AbstractWrapper<T, R, Children extends AbstractWrapper<T, R, Children>> extends Wrapper<T> implements Compare<Children, R>, Nested<Children, Children>, Join<Children>, Func<Children, R> {    protected final Children typedThis = this;  
    
    protected SharedString lastSql;   
    
    public Children last(boolean condition, String lastSql) {   
        if (condition) {   
            /**   
             * 条件构造器的处理策略是覆盖上次的值   
             */   
            this.lastSql.setStringValue(" " + lastSql);   
        }   
        return this.typedThis;  
    }   
}

只要一个还有追求,他就没有老。直到后悔取代了梦想,一个人才算老。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-08-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 的数字化之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 发现异常
  • 初步分析
  • 复现
  • 解决办法
  • 小结
  • 拓展:源码分析
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档