MyBatis Plus根据指定的属性名创建Lambda的SFunction

2024-09-22 47点热度 0人点赞 0条评论

问题描述

在使用LambdaQueryWrapper的时候,有时候会从前端(或者根据一些业务逻辑)传过来一个不确定的属性名,例如Person类中一个name属性,这个属性名需要加入到LambdaQueryWrapper的查询或者排序中去,LambdaQueryWrapper因为是Lambda的方式,只能传入SFunction,这就需要把name转成SFunction。

工具类如下(FunctionTools.java):

public class FunctionTools {
    /**
     * 可序列化
     */
    private static final int FLAG_SERIALIZABLE = 1;

    private static Map<String, SFunction> functionMap = new HashMap<>();

    /**
     * 获取与实体类字段对应的 SFunction 对象。
     * @param entityClass 实体类的 Class 对象。
     * @param fieldName 实体类中的字段名。
     * @return 返回找到的 SFunction 对象。
     */
    public static SFunction getSFunction(Class<?> entityClass, String fieldName) {
        // 检查缓存中是否已经有了对应的 SFunction 对象。
        if (functionMap.containsKey(entityClass.getName() + fieldName)) {
            return functionMap.get(entityClass.getName() + fieldName);
        }
        // 获取实体类中名为 fieldName 的字段。
        Field field = getDeclaredField(entityClass, fieldName);
        if (field == null) {
            //如果字段不存在,使用 ExceptionUtils 抛出一个异常,指出实体类中没有找到该字段。
            throw ExceptionUtils.mpe("This class %s is not have field %s ", entityClass.getName(), fieldName);
        }
        SFunction func = null;
        // 获取 MethodHandles.Lookup 实例,用于反射操作。
        final MethodHandles.Lookup lookup = MethodHandles.lookup();
        // 定义方法类型,表示实体类的实例方法,该方法返回字段的类型。
        MethodType methodType = MethodType.methodType(field.getType(), entityClass);
        // 用于存储 LambdaMetafactory 创建的 CallSite 对象。
        final CallSite site;
        // 构造标准的 Java getter 方法名。
        String getFunName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
        try {
            // 使用 LambdaMetafactory 创建一个动态的 SFunction 实例。
            site = LambdaMetafactory.altMetafactory(
                    lookup,
                    "invoke",
                    MethodType.methodType(SFunction.class),
                    methodType,
                    lookup.findVirtual(entityClass, getFunName, MethodType.methodType(field.getType())),
                    methodType,
                    FLAG_SERIALIZABLE
            );
            // 使用 CallSite 来获取 SFunction 实例。
            func = (SFunction) site.getTarget().invokeExact();
            // 将生成的 SFunction 实例存储到缓存中。
            functionMap.put(entityClass.getName() + field.getName(), func);
            return func;
        } catch (Throwable e) {
            // 如果在创建 SFunction 过程中发生异常,抛出异常,指出实体类中没有找到对应的 getter 方法。
            throw ExceptionUtils.mpe("This class %s is not have method %s ", entityClass.getName(), getFunName);
        }
    }

    /**
     * 递归获取类中声明的字段,包括私有字段。
     * @param clazz 要检查的类。
     * @param fieldName 要查找的字段名。
     * @return 返回找到的 Field 对象,如果没有找到则返回 null。
     */
    public static Field getDeclaredField(Class<?> clazz, String fieldName) {
        Field field = null;
        // 遍历类及其父类,直到到达 Object 类。
        for (; clazz != Object.class; clazz = clazz.getSuperclass()) {
            try {
                // 尝试获取声明的字段。
                field = clazz.getDeclaredField(fieldName);
                // 如果找到字段,返回该字段。
                return field;
            } catch (NoSuchFieldException e) {
                // 如果没有找到字段,继续查找父类。
                // 这里不处理异常,让其继续执行循环。
            }
        }
        // 如果没有找到字段,返回 null。
        return null;
    }

}

使用方式如下(FunctionTools.getSFunction的部分,其中排序的字段就是前端传过来的):

LambdaQueryWrapper<Course> queryWrapper = new LambdaQueryWrapper<>();
if (courseSearchDto.getName() != null) {
    queryWrapper.like(Course::getName, courseSearchDto.getName());
}
if (courseSearchDto.getCat() != null) {
    queryWrapper.eq(Course::getCat, courseSearchDto.getCat());
}
// 排序的字段就是前端传过来的
if(!StringUtils.isEmpty(courseSearchDto.getOrderByColumn())) {
    queryWrapper.orderBy(!StringUtils.isEmpty(courseSearchDto.getIsAsc()), courseSearchDto.isAsc(), FunctionTools.getSFunction(Course.class, courseSearchDto.getOrderByColumn()));
}
queryWrapper.orderByAsc(Course::getId);

参考连接:https://github.com/baomidou/mybatis-plus/issues/4777

admin

这个人很懒,什么都没留下

文章评论

您需要 登录 之后才可以评论