# 表格列排序

# 概述

在表格分页查询中,如果需要按指定列进行排序,则需要后端进行代码支持,实现真分页排序。

# 实现

前端发起表格查询请求时,传递需要进行正序或倒序的字段。后端进行接收处理,排序功能主要分为:注解排序自定义排序.

# 注解排序

注解排序使用Mybatis-Plus自带的分页Page对象中的orders属性来实现,所以service排序方法中参数必须有Page对象和有实现了PageEnity或者BaseEntity的实体入参。

# Order 注解

Order注解作用于需要排序的方法上,注解详情如下:

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public  @interface Order {

# TableOrderProperty 注解

TableOrderProperty注解作用于查询dto实体类上,用于添加字段前缀,添加实体映射,注解详情如下:

/**
 * @Description: TODO
 * @author: whh
 * @Date: 2023-05-17 09:56
 */
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public  @interface TableOrderProperty {
    /**
    *@Description:查询sql时,所查询字段的前缀。

    * @return: java.lang.String
    *@auth:whh
    *@date 2023/5/17
     **/
    String prefix()default "";

    /**
    *@Description:映射的实体,配置以后可以根据实体去匹配,有些dto中不一定存在排序字段,可以通过实体映射去匹配

    * @return: java.lang.Class
    *@auth:whh
    *@date 2023/5/19
     **/
    Class entity() default  String.class;
}

# OrderProperty 注解

OrderProperty 最小粒度的注解,作用于需要排序的字段上,用于添加字段前缀,匹配数据库表名称等,注解详情如下:

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public  @interface  OrderProperty {

    /**
    *@Description:对应数据库表中的字段名称例如:person_code

    * @return: java.lang.String
    *@auth:whh 
    *@date 2023/5/17
     **/
    String value() default "";

    /**
    *@Description:查询sql时,自定义数据表的别名,如果在字段中设置了前缀,TableOrderProperty中设置的前缀对于当前字段无效

    * @return: java.lang.String
    *@auth:whh
    *@date 2023/5/17
     **/
    String prefix()default "";

    /**
    *@Description:是否中文排序
    * @return: null
    *@auth:whh
    *@date 2023/5/22
     **/
    boolean isChineseSort() default false;
}

实现示例:实现部门表格排序。

实现了PageEntity的dto中添加注解如下:

@Data
@TableOrderProperty(prefix = "dept",entity = HosDepartment.class)
public class HosDepartmentDTO  extends PageEntity {
    @ApiModelProperty(value = "部门代码")
    @OrderProperty("code")
    private String code;

    @ApiModelProperty(value = "部门名称")
    @OrderProperty(value = "name",isChineseSort = true)
    private String name;

    @ApiModelProperty(hidden = true)
    @OrderProperty(value = "name",prefix = "orgac",isChineseSort = true)
    private  String  orgacName;
    ......
}

service方法添加Order注解

   @Override
    @Order
    public IPage<HosDepartmentVO> selectAllPage(Page<HosDepartment> page, HosDepartmentDTO hosDepartmentDTO) {
}

字段匹配逻辑规则:
1.通过Order注解在执行目标方法之前,找到实现了 PageEntity或者 BaseEntity的入参;
2.在当前实体中查找标记了OrderProperty注解的字段,如果存在标记了OrderProperty注解的字段,取注解value,判断是否存在前缀如果存在前缀将前缀拼上,判断是否是中文排序,如果是中文排序将中文排序拼上
3.如果未设前缀且实体添加tableOrderProperty注解,判断TableOrderProperty中是否存在前缀,如果存在前缀将前缀拼上.
4.如果当前实体中没有标记了OrderProperty注解的字段,查找是否存在标记了TableField注解的字段,如果存在取 注解value;
5.重复3步骤
6.如果当前实体字段中没有OrderProperty和TableField注解,且实体存在TableOrderProperty注解,TableOrderProperty映射了实体去映射实体中查找, 重复执行类似步骤2,3,4,5的方法,需注意的是映射实体中使用前缀时,会去目标实体中查找TableOrderProperty注解上的前缀
7.如果不存在映射实体或者映射实体中未找到标注OrderProperty和TableField注解的字段,则在目标类中查找是否存在匹配的数据字段,如果存在取实体字段名称重复步骤3
8.如果步骤7也未找到排序字段,则去映射实体中去查找,映射实体字段中去查找,如果存在取映射实体字段名称重复步骤3
9.将匹配处理好的字段放入到page对象中。

# 自定义排序

自定义排序也可以通过工具类或者完全自定义来实现

# 完全自定义

实现示例:查询系统参数,支持配置编码排序。

分页mapper接口及实现sql如下:

IPage<Config> selectPage(Page<Config> page, @Param("config") ConfigDTO config);
select * from hos_sys_config hsc where 1=1
<if test="config.name!=null and config.name!=''">
    and hsc.name like CONCAT('%',CONCAT(#{config.name},'%'))
</if>
<if test="config.code!=null and config.code!=''">
    and hsc.code like CONCAT('%',CONCAT(#{config.code},'%'))
</if>
order by hsc.create_time asc
  1. 将查询入参ConfigDTO继承PageEntity分页参数实体。
public class ConfigDTO extends PageEntity {

    @ApiModelProperty(value="配置编码")
    private String code;

    @ApiModelProperty(value="配置名称")
    private String name;
    
}

@Data
public class PageEntity {

    @ApiModelProperty(value="页数")
    private int current;

    @ApiModelProperty(value="每页条数")
    private int size;

    @ApiModelProperty(value="hisui页数")
    private int page;

    @ApiModelProperty(value="hisui每页条数")
    private int rows;

    @ApiModelProperty(value="排序字段")
    private String sort;

    @ApiModelProperty(value="排序类型")
    private String order;

    /**
     * 兼容HisUI入参
     * @return int
     */
    public int getCurrent() {
        if (this.current == 0 && this.page > 0) {
            return this.page;
        }
        return this.current;
    }

    /**
     * 兼容HisUI入参
     * @return int
     */
    public int getSize() {
        if (this.size == 0 && this.rows > 0) {
            return this.rows;
        }
        return this.size;
    }
}

  1. 处理排序字段,放入分页page对象中。
public class ConfigServiceImpl extends BaseServiceImpl<ConfigMapper, Config> implements ConfigService {

    @Override
    public IPage<Config> selectPage(Page<Config> page, ConfigDTO dto) {
        // 排序字段
        String sort = dto.getSort();
        // 排序类型
        String order = dto.getOrder();
        List<OrderItem> orderItemList = new ArrayList<>();
        if (StringUtil.isNotBlank(sort) && StringUtil.isNotBlank(order)) {
            // 入参与sql字段不一致需手动处理
            sort = "hsc."+sort;
            if (CommonUtils.equals("asc", order)) {
                orderItemList.add(new OrderItem(sort, true));
            }
            if (CommonUtils.equals("desc", order)) {
                orderItemList.add(new OrderItem(sort, false));
            }
            if (orderItemList.size() > 0) {
                page.addOrder(orderItemList);
            }
        }
        return this.baseMapper.selectPage(page, dto);
    }
    
}

原sql中如果已有order by,加入的排序字段会插入到最前方。示例如下(按名称name倒序):

select * from hos_sys_config hsc where 1=1 order by hsc.name desc,hsc.create_time asc

注意:当存在传入字段与sql表字段不一致时,需要在代码中对字段进行转换。

1、传入字段存在驼峰。例如:userName ---> user_name
2、查询sql中使用了表别名。例如:code ---> hsc.code

# 通过工具类处理

实现示例:菜单管理排序。

实体类

@Data
@TableOrderProperty(prefix = "tr",entity = Resource.class)
public class ResourceDTO extends PageEntity {
    /**
     * 父资源id
     */
    private String parentId;
    /**
     * 资源编码
     */
    @OrderProperty("code")
    private String code;
    /**
     * 资源名称
     */
    @OrderProperty(value = "name",isChineseSort = true)
    private String name;

    @ApiModelProperty(hidden = true)
    private String orderSql;
}

service 方法

@Override
  public List<ResourceVO> listResources(ResourceDTO resourceDTO) {
    // 全部资源树。
       String sql = SqlOrderUtil.orderSql(resourceDTO, driverClassName, "tr");
       resourceDTO.setOrderSql(sql);
}

工具类

  /**
     *@Description:设置排序
     * @param data:查询实体
     * @param driverClassName:yml配置的driverClass
     * @param prefix:前缀
     * @return: 处理好的sql
     *@auth:whh
     *@date 2023/5/17
     **/
    public static  <E> String  orderSql(E data,String driverClassName,String prefix){
        ....
    }

字段匹配逻辑规则与注解处理直接基本一致,需要注意的是,使用工具类时,TableOrderProperty上的前缀无效,需手动传入前缀。