Mybatis-plus通过反射实现动态排序不同字段功能

本文介绍了一种利用Mybatis-Plus实现动态排序的方法。通过自定义BaseQueryDto类及其子类OfiQueryDto,实现了从前端传入任意字段进行排序的功能,并确保了字段的安全性和有效性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在做项目,使用到了Mybatis-plus,页面上需要对某些字段进行排序,原来是固定好哪些字段需要排序,然后在Controller里接收参数,如下代码所示。

	@LogOperation(name = "query urpOfi", objType = "Config")
    @GetMapping
    public ResponseEntity<DtoResponse<OfiDto>> queryOfi(
            @RequestParam(name = "pageSize", defaultValue = "-1", required = false) Integer pageSize,
            @RequestParam(name = "page", defaultValue = "-1", required = false) Integer page,
            @RequestParam(value = "sort", defaultValue = "", required = false) String sort,
            @RequestParam(value = "msFlag", defaultValue = "", required = false) String msFlag)
            throws UnsupportedEncodingException {
        sort = URLDecoder.decode(sort, StandardCharsets.UTF_8.toString());
        msFlag = URLDecoder.decode(msFlag, StandardCharsets.UTF_8.toString());
        余下省略......

其中@LogOperation使我们自定义的AOP日志注解,可以不用关注。这里存在的问题是参数没有校验,直接接收了,可能会被恶意攻击,同时sort为排序字段,前台会传一个固定好的字段值过来,无法实现动态排序(比如:前台随意传一个字段,只要在我的表数据Entity中存在该字段,就可以实现排序),通过以下方式实现动态排序。

@Data
public class BaseQueryDto<T> {
    // 排序方式标识符号,”-“代表倒序
    private static final String ORDER_BY_IDENTIFIER = "-";

    private static final int MAX_PAGE_SIZE = 100;

    private static final int MAX_PAGE = 10000;

    private static final int MAX_SORT_LENGTH = 100;

    /**
     * 每页显示记录数
     */
    @Range(min = 0, max = MAX_PAGE_SIZE)
    protected Integer pageSize = 10;

    /**
     * 当前页
     */
    @Range(min = 0, max = MAX_PAGE)
    protected Integer page = 0;

    /**
     * 排序,包含2部分内容,排序方式及排序字段
     */
    @Length(max = MAX_SORT_LENGTH)
    protected String sort;

    /**
     * 获取排序方式,默认升序(asc)
     *
     * @return 排序方式
     */
    public SqlOrderType getOrderType() {
        if (StringUtils.isEmpty(sort)) {
            return SqlOrderType.ASC;
        }

        if (sort.startsWith(ORDER_BY_IDENTIFIER)) {
            return SqlOrderType.DESC;
        }

        return SqlOrderType.ASC;
    }

    private Optional<Class<?>> getEntityClass() {
        Type type = getClass().getGenericSuperclass();
        if (type instanceof ParameterizedType) {
            Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();
            try {
                return Optional.of(Class.forName(actualTypeArguments[0].getTypeName()));
            } catch (ClassNotFoundException e) {
                return Optional.empty();
            }
        }

        return Optional.empty();
    }

    private Optional<Field> getEntityField(String fieldName) {
        Optional<Class<?>> optionalClass = getEntityClass();

        return optionalClass.flatMap(it -> {
            try {
                return Optional.of(it.getDeclaredField(fieldName));
            } catch (NoSuchFieldException e) {
                return Optional.empty();
            }
        });
    }

    /**
     * 获取数据排序的列
     *
     * @return 排序列
     */
    public Optional<String> getOrderColumn() {
        if (StringUtils.isEmpty(sort)) {
            return Optional.empty();
        }
        return getEntityField(sort.replaceFirst(ORDER_BY_IDENTIFIER, "")).map(Field::getName);
    }

    /**
     * 设置排序的QueryWrapper
     *
     * @param queryWrapper query wrapper
     */
    public void setOrderQueryWrapper(QueryWrapper<T> queryWrapper) {
        this.getOrderColumn().ifPresent(column -> {
            queryWrapper.orderBy(true, this.getOrderType().equals(SqlOrderType.ASC), column);
        });
    }
}

BaseQueryDto是公共的查询Dto父类,泛型T为具体的表数据Entity。通过java beanvalidation控制入参大小及长度,前端传递的参数sort值一般为Entity中的字段名,例如“id”或者“-id”,带有“-”的代表是降序。会根据传入的sort值通过反射去判断在Entity中是否存在该字段,存在就正常排序,不存在就什么也不做。
如果只需要排序,那么Controller中用BaseQueryDto去接收参数即可,然后在Service中调用baseQueryDto.setOrderQueryWrapper(queryWrapper);
如果不仅仅排序,还需要对某些字段做模糊查询或者下拉框过滤查询,需要新建Dto继承BaseQueryDto。

@Data
public class OfiQueryDto extends BaseQueryDto<OfiEntity> {
    /**
     * 主从标识(过滤查询)
     */
    @Length(max = 5)
    private String msFlag = "";

    /**
     * 国内,国内备用,国际,国际备用网编码(模糊查询)
     */
    @Length(max = 6)
    private String networkCode1 = "";

    @Length(max = 6)
    private String networkCode2 = "";

    @Length(max = 6)
    private String networkCode3 = "";

    @Length(max = 6)
    private String networkCode4 = "";
}

Service中设置模糊查询,下拉框过滤和排序。

public ListPage<OfiEntity> queryOfiListPage(OfiQueryDto dto, Integer clusterId)
            throws UnsupportedEncodingException {
        QueryWrapper<OfiEntity> queryWrapper = Wrappers.query();
        queryWrapper.lambda().eq(OfiEntity::getClusterId, clusterId);

        setSelectQueryWrapper(dto, queryWrapper.lambda());

        setLikeQueryWrapper(dto, queryWrapper.lambda());

        dto.setOrderQueryWrapper(queryWrapper);

        Page<OfiEntity> ofiEntityPage = new Page<>(dto.getPage(), dto.getPageSize());
        ofiEntityPage = getBaseMapper().selectPage(ofiEntityPage, queryWrapper);

        return ListPage.of(ofiEntityPage);
    }
### 解决 MyBatisPlus 分页查询时设置参数发生错误的方法 在使用 MyBatis-Plus 进行分页查询的过程中,如果遇到设置参数时发生的错误,通常可以通过以下几个方面来排查和解决问题。 #### 1. 版本兼容性问题 确保所使用的 `mybatis-plus` 和其他依赖库之间的版本相互兼容。例如,在某些特定版本中可能存在已知的 bug 或者不支持的功能[^3]。建议升级到最新稳定版以获得更好的支持和修复。 ```xml <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <!-- 使用最新的稳定版本 --> <version>3.5.0</version> </dependency> ``` #### 2. Lambda 表达式的缓存机制 当定义复杂的查询条件(如多表联结)并尝试通过 lambda 方式构建 SQL 语句片段时,可能会因为缺少相应的实体类映射而导致异常抛出。对于此类情况,可以考虑手动指定要操作的数据表名称以及字段名,而不是完全依赖于反射获取这些信息[^2]。 ```java // 手动编写SQL片段而非仅靠Lambda表达式 queryWrapper.apply("game.id = game_query.game_id"); ``` #### 3. 排序条件的重要性 为了防止由于数据库内部优化器等原因造成的结果集不稳定现象,在执行分页查询之前应当始终显式地指明至少一个排序依据列。这不仅有助于提升查询效率,还能有效避免因数据变动而引起的显示顺序混乱等问题[^1]。 ```java Page<Game> page = new Page<>(currentPage, pageSize); page.addOrder(OrderItem.asc("create_time")); // 添加创建时间升序排列作为默认排序方式 iGameService.page(page, queryWrapper); ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值