Use reflection to dynamically construct wrapper conditions (use reflection to obtain annotation values and entity class values)

Use reflection to dynamically construct wrapper conditions

Entity class

/**
 * api list query entity class
 *
 *@authorwys
 */
@Data
public class SysApiQuery {

    private Long id;

    /**
     * api name
     */
    @Query(type = QueryTypeEnum.EQUAL)
    private String apiName;

    /**
     * api path
     */
    @Query(type = QueryTypeEnum.EQUAL)
    private String apiUrl;



    /** Starting time */
    @JsonIgnore
    private String beginTime;

    /** End Time */
    @JsonIgnore
    private String endTime;

}

QueryTypeEnum enumeration class

@Getter
@RequiredArgsConstructor
public enum QueryTypeEnum {

    /**
     * Equivalent query, for example: WHERE `age` = 18
     */
    EQUAL(1, "="),
    /**
     * Non-equivalent query, for example: WHERE `age` != 18
     */
    NOT_EQUAL(2, "!="),
    /**
     * Greater than query, for example: WHERE `age` > 18
     */
    GREATER_THAN(3, ">"),
    /**
     * Less than query, for example: WHERE `age` < 18
     */
    LESS_THAN(4, "<"),
    /**
     * Greater than or equal to query, for example: WHERE `age` >= 18
     */
    GREATER_THAN_OR_EQUAL(5, ">="),
    /**
     * Less than or equal to query, for example: WHERE `age` <= 18
     */
    LESS_THAN_OR_EQUAL(6, "<="),
    /**
     * Range query, for example: WHERE `age` BETWEEN 10 AND 18
     */
    BETWEEN(7, "BETWEEN"),
    /**
     * Left fuzzy query, for example: WHERE `nickname` LIKE '%s'
     */
    LEFT_LIKE(8, "LIKE '%s'"),
    /**
     * Fuzzy query, for example: WHERE `nickname` LIKE '%s%'
     */
    INNER_LIKE(9, "LIKE '%s%'"),
    /**
     * Right fuzzy query, for example: WHERE `nickname` LIKE 's%'
     */
    RIGHT_LIKE(10, "LIKE 's%'"),
    /**
     * Contains queries, for example: WHERE `age` IN (10, 20, 30)
     */
    IN(11, "IN"),
    /**
     * Does not contain queries, for example: WHERE `age` NOT IN (20, 30)
     */
    NOT_IN(12, "NOT IN"),
    /**
     * Empty query, for example: WHERE `email` IS NULL
     */
    IS_NULL(13, "IS NULL"),
    /**
     * Non-null query, for example: WHERE `email` IS NOT NULL
     */
    IS_NOT_NULL(14, "IS NOT NULL"),;

    private final Integer value;
    private final String description;
}

Query custom annotations

import com.chinaunicom.common.enums.QueryTypeEnum;

import java.lang.annotation.*;

/**
 * Query annotations
 *
 *@author Zheng Jie(ELADMIN)
 * @author Charles7c
 * @since 2023/1/15 18:01
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Query {

    /**
     * Attribute name (default is consistent with the name of the attribute using this annotation)
     */
    String property() default "";

    /**
     * Query type (equivalence query, fuzzy query, range query, etc.)
     */
    QueryTypeEnum type() default QueryTypeEnum.EQUAL;

    /**
     * Multi-attribute fuzzy query, only supports String type attributes, separate multiple attributes with commas
     * <p>
     * For example: @Query(blurry = "username,email") means fuzzy query based on username and email
     *</p>
     */
    String blurry() default "";
}

QueryHelper Query Assistant

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.chinaunicom.common.annotation.Query;
import com.chinaunicom.common.enums.QueryTypeEnum;
import com.chinaunicom.common.utils.reflect.ReflectUtils;
import com.chinaunicom.common.validate.ValidationUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
 * Query assistant
 *
 *@author Zheng Jie(ELADMIN)
 * @author Charles7c
 * @since 2023/1/15 18:17
 */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class QueryHelper {

    /**
     * Construct MyBatis Plus query condition encapsulation object based on query conditions
     *
     * @param query
     *            Query conditions
     * @param <Q>
     * Query condition data type
     * @param <R>
     * Query data type
     * @return MyBatis Plus query condition encapsulation object
     */
    public static <Q, R> QueryWrapper<R> build(Q query) {
        QueryWrapper<R> queryWrapper = new QueryWrapper<>();
        // No query conditions, return directly
        if (null == query) {
            return queryWrapper;
        }

        // Get all fields in the query conditions
        List<Field> fieldList = ReflectUtils.getNonStaticFields(query.getClass());
        fieldList.forEach(field -> buildQuery(query, field, queryWrapper));
        return queryWrapper;
    }

    /**
     * Construct MyBatis Plus query condition encapsulation object
     *
     * @param query
     *            Query conditions
     * @param field
     * field
     * @param queryWrapper
     * MyBatis Plus query condition encapsulation object
     * @param <Q>
     * Query condition data type
     * @param <R>
     * Query data type
     */
    private static <Q, R> void buildQuery(Q query, Field field, QueryWrapper<R> queryWrapper) {
        boolean accessible = field.isAccessible();
        try {
            field.setAccessible(true);
            // Without @Query, return directly
            Query queryAnnotation = field.getAnnotation(Query.class);
            if (null == queryAnnotation) {
                return;
            }

            // If the field value is empty, return directly
            Object fieldValue = field.get(query);
            if (ObjectUtil.isEmpty(fieldValue)) {
                return;
            }

            // Parse query conditions
            parse(queryAnnotation, field.getName(), fieldValue, queryWrapper);
        } catch (Exception e) {
            log.error("Build query occurred an error: {}. Query: {}, Field: {}.", e.getMessage(), query, field, e);
        } finally {
            field.setAccessible(accessible);
        }
    }

    /**
     * Parse query conditions
     *
     * @param queryAnnotation
     * Query annotations
     * @param fieldName
     *Field name
     * @param fieldValue
     *Field value
     * @param queryWrapper
     * MyBatis Plus query condition encapsulation object
     * @param <R>
     * Query data type
     */
    private static <R> void parse(Query queryAnnotation, String fieldName, Object fieldValue,
                                  QueryWrapper<R> queryWrapper) {
        // Parse multi-attribute fuzzy query
        // If a multi-attribute fuzzy query is set, split the attributes for conditional splicing.
        String blurry = queryAnnotation.blurry();
        if (StrUtil.isNotBlank(blurry)) {
            String[] propertyArr = blurry.split(",");
            queryWrapper.and(wrapper -> {
                for (String property : propertyArr) {
                    wrapper.or().like(StrUtil.toUnderlineCase(property), fieldValue);
                }
            });
            return;
        }

        // Parse a single attribute query
        // If the attribute name is not specified separately, it will be consistent with the name of the attribute using this annotation.
        // Note: The columns in the database specification are named using the underscore connection method, and the variables in the program specification are named using the camel case method.
        String property = queryAnnotation.property();
        String columnName = StrUtil.toUnderlineCase(StrUtil.blankToDefault(property, fieldName));
        QueryTypeEnum queryType = queryAnnotation.type();
        switch (queryType) {
            case EQUAL:
                queryWrapper.eq(columnName, fieldValue);
                break;
            case NOT_EQUAL:
                queryWrapper.ne(columnName, fieldValue);
                break;
            case GREATER_THAN:
                queryWrapper.gt(columnName, fieldValue);
                break;
            case LESS_THAN:
                queryWrapper.lt(columnName, fieldValue);
                break;
            case GREATER_THAN_OR_EQUAL:
                queryWrapper.ge(columnName, fieldValue);
                break;
            case LESS_THAN_OR_EQUAL:
                queryWrapper.le(columnName, fieldValue);
                break;
            case BETWEEN:
                List<Object> between = new ArrayList<>((List<Object>)fieldValue);
                ValidationUtils.throwIf(between.size() != 2, "[{}] must be a range", fieldName);
                queryWrapper.between(columnName, between.get(0), between.get(1));
                break;
            case LEFT_LIKE:
                queryWrapper.likeLeft(columnName, fieldValue);
                break;
            case INNER_LIKE:
                queryWrapper.like(columnName, fieldValue);
                break;
            case RIGHT_LIKE:
                queryWrapper.likeRight(columnName, fieldValue);
                break;
            case IN:
                ValidationUtils.throwIfEmpty(fieldValue, "[{}] cannot be empty", fieldName);
                queryWrapper.in(columnName, (List<Object>)fieldValue);
                break;
            case NOT_IN:
                ValidationUtils.throwIfEmpty(fieldValue, "[{}] cannot be empty", fieldName);
                queryWrapper.notIn(columnName, (List<Object>)fieldValue);
                break;
            case IS_NULL:
                queryWrapper.isNull(columnName);
                break;
            case IS_NOT_NULL:
                queryWrapper.isNotNull(columnName);
                break;
            default:
                throw new IllegalArgumentException(String.format("[%s] query type is not supported yet", queryType));
        }
    }
}

PageDataVO structure encapsulation

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.Data;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * Pagination information
 *
 * @param <V>
 * List data type
 * @author Charles7c
 * @since 2023/1/14 23:40
 */
@Data
public class PageDataVO<V> implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * List data
     */
    private List<V> list;

    /**
     * total
     */
    private long total;

    /**
     * Build paging information based on MyBatis Plus paging data and convert source data into specified type data
     *
     * @param page
     * MyBatis Plus paging data
     * @param targetClass
     * Target type Class object
     * @param <T>
     * Source list data type
     * @param <V>
     * Target list data type
     * @return paging information
     */
    public static <T, V> PageDataVO<V> build(IPage<T> page, Class<V> targetClass) {
        if (null == page) {
            return empty();
        }
        PageDataVO<V> pageDataVO = new PageDataVO<>();
        pageDataVO.setList(BeanUtil.copyToList(page.getRecords(), targetClass));
        pageDataVO.setTotal(page.getTotal());
        return pageDataVO;
    }



    /**
     * Based on MyBatis Plus and convert source data into specified type data
     *
     * @param
     *
     * @param targetClass
     * Target type Class object
     * @param <T>
     * Source list data type
     * @param <V>
     * Target list data type
     * @return paging information
     */
    public static <T, V> List<V> build(List<T> list, Class<V> targetClass) {
        if (null == list) {
            return new ArrayList<>();
        }

        List<V> toList = BeanUtil.copyToList(list, targetClass);
        return toList;
    }

    /**
     * Build paging information based on MyBatis Plus paging data
     *
     * @param page
     * MyBatis Plus paging data
     * @param <V>
     * List data type
     * @return paging information
     */
    public static <V> PageDataVO<V> build(IPage<V> page) {
        if (null == page) {
            return empty();
        }
        PageDataVO<V> pageDataVO = new PageDataVO<>();
        pageDataVO.setList(page.getRecords());
        pageDataVO.setTotal(page.getTotal());
        return pageDataVO;
    }

    /**
     * Build paging information based on list data
     *
     * @param page
     *Page number
     * @param size
     *Number of items per page
     * @param list
     * List data
     * @param <V>
     * List data type
     * @return paging information
     */
    public static <V> PageDataVO<V> build(int page, int size, List<V> list) {
        if (CollUtil.isEmpty(list)) {
            return empty();
        }
        PageDataVO<V> pageDataVO = new PageDataVO<>();
        pageDataVO.setTotal(list.size());
        //Page the list data
        int fromIndex = (page - 1) * size;
        int toIndex = page * size + size;
        if (fromIndex > list.size()) {
            pageDataVO.setList(new ArrayList<>(0));
        } else if (toIndex >= list.size()) {
            pageDataVO.setList(list.subList(fromIndex, list.size()));
        } else {
            pageDataVO.setList(list.subList(fromIndex, toIndex));
        }
        return pageDataVO;
    }

    /**
     * Empty paging information
     *
     * @param <V>
     * List data type
     * @return paging information
     */
    private static <V> PageDataVO<V> empty() {
        PageDataVO<V> pageDataVO = new PageDataVO<>();
        pageDataVO.setList(new ArrayList<>(0));
        return pageDataVO;
    }
}

service implementation

 @Override
    public List<SysApiVo> pageList(SysApiQuery apiQuery) {

        //Construct MyBatis Plus query condition encapsulation object based on query conditions
        QueryWrapper<SysApi> queryWrapper = QueryHelper.build(apiQuery);
        List<SysApi> apiList = baseMapper.selectList(queryWrapper);
        // Based on MyBatis Plus and convert source data to specified type data
        List<SysApiVo> list = PageDataVO.build(apiList, SysApiVo.class);
        return list;
    }