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

发现异常

上线完成后,巡检日志。
发现druid报了一个slow sql的错
ERROR c.a.druid.filter.stat.StatFilter - slow sql 1909 millis.
看了下,发现这个sql有些不一样:筛选条件重复了

select id, biz_filed_1
from table1
WHERE 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优化器的处理【计算PRIMARY需要成本】,上面sql的执行结果也是对的。
但是,MySQL默认sql语句最大为1M。如果不解决,当超过这个限制时,就报错了

初步分析

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

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>? 环节 :

queryWrapper.gt("id",lastId);

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

复现

case :

  1. 使用MyBatis-Plus的条件构造器来构建查询语句
  2. 条件构造器的Wrapper.gt对同一个字段要执行多次
@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);
    }

}

执行结果:

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。

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);
        }

    }
  
}
小结

这次的问题,是由于对MyBatis-Plus的条件构造器不熟悉,在使用时想当然认为会自动进行去重造成的。
后面,在使用新的API或组件时,要有重点地进行测试。若是核心的场景要适当的为这些新API增加UT。

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

思路比结论重要

拓展:源码分析

展开聊一下。
拼SQL的逻辑是由类com.baomidou.mybatisplus.core.conditions.AbstractWrapper承担的。
AbstractWrapper 的实际上实现了五大接口:嵌套接口Nested、比较接口Compare、拼接接口Join、函数接口Func、SQL片断函数接口ISqlSegment。 Wrapper的gt
由比较接口Compare和SQL片断函数接口ISqlSegment来承接。

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);
}
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) {
        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

last(String lastSql)
last(boolean condition, String lastSql)

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Mybatis-Plus条件构造器是一种方便的查询条件构造工具,可以通过链式调用的方式构造出复杂的查询条件。它支持多种查询条件,包括等于、不等于、大于、小于、模糊查询等,还可以通过Lambda表达式来构造查询条件,使得代码更加简洁易懂。同时,Mybatis-Plus条件构造器还支持分页查询、排序等功能,可以满足大部分的查询需求。 ### 回答2: Mybatis-Plus 是一款增强 Mybatis 功能的开发包,它提供了很多便捷的操作方式以简化 Mybatis 的开发,其中条件构造器就是其中之一。 Mybatis-Plus条件构造器能够帮助我们构建查询条件,使我们省去了手写 SQL 语句的烦恼。在使用条件构造器之前,我们需要先导入依赖包。 下面是条件构造器的创建方式: Wrapper wrapper = new WrapperBuilder().and("name={0}", "Lucy").and("age>{0}", 18).build(); 其中,WrapperBuilder 是构造器的构建工具类,and() 方法用于添加多个条件。构建条件之后,我们可以将条件传递给 Mybatis-Plus查询方法进行查询操作。 除了上述的构建方式,条件构造器还支持 QueryWrapper、UpdateWrapper 和 LambdaQueryWrapper 等多种构建方式,不同的构建方式可以满足不同的查询需求。 使用 Mybatis-Plus条件构造器,我们可以通过灵活的组合多个查询条件,快速地构建满足我们需求的 SQL 语句。同时,在实际使用过程中,我们还需要注意一些常见的使用技巧: 1. 使用占位符。在构建 SQL 语句时,我们需要使用占位符来避免 SQL 注入的风险。 2. 处理字段映射。在使用条件构造器时,需要注意数据库表名和实体类属性名之间的映射关系。如果两者不一致,需要进行处理。 3. 处理关联表查询。当我们需要查询多张表时,需要通过关联查询的方式来完成,并且需要处理好关系的映射。 总之,Mybatis-Plus条件构造器能够为我们节约很多时间和精力。当然,在使用时需要注意一些使用技巧,以便更好地发挥它的能力。 ### 回答3: MyBatis-Plus一个基于MyBatis的增强工具,为MyBatis提供了更加便捷、高效的持久层操作方式。它的条件构造器是其中一个功能强大的组件,用于链式构建SQL查询条件,无需手动拼接SQL语句,提高了代码可读性和可维护性。 条件构造器提供了多种查询方式,包括等于、大于、小于、in、between、like等,可以满足大部分的查询需求。我们可以通过Lambda表达式来指定查询条件,这种方式比传统的字符串拼接更加安全、可读,可以少写一些防注入的代码。 MyBatis-Plus条件构造器还支持分页查询,可以通过Page对象指定当前页码、每页显示数量等,方便地实现分页查询。同时,条件构造器也支持排序、自定义SQL片段等功能,使得查询功能更加多样化和灵活。 除了条件构造器MyBatis-Plus还提供了其他很多方便的功能,例如注解式CRUD、自动代码生成等。相信在项目开发中使用MyBatis-Plus可以极大地提高开发效率和代码质量,使得持久层操作更加简洁、优雅。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

也和光同尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值