動態

詳情 返回 返回

利用Mybatis自定義排序規則實現複雜排序 - 動態 詳情

場景分析

本次需要實現規則是根據用户點擊目錄左側——彈出排序選項,文件名A-Z:英文A-Z → 中文A-Z → 數字0-9 ,文件名Z-A:與A-Z相反,最近更新時間:文件夾按文件夾修改時間,文檔按文檔修改時間。圖片直達鏈接

如何實現?

1.改造接口增加參數

    /**
     * 排序類型: NAME_ASC(文件名A-Z), NAME_DESC(文件名Z-A), TIME_DESC(最近更新時間)
     */
    @Schema(description = "排序類型: NAME_ASC(文件名A-Z), NAME_DESC(文件名Z-A), TIME_DESC(最近更新時間)")
    private String sortType;

2.修改默認查詢子集排序規則

// 排序 - 默認按sort字段排序,如果需要其他排序規則會在數據庫層處理
this.children.sort(Comparator.comparing(KbPageTreeResp::getSort, Comparator.nullsLast(Comparator.naturalOrder())));

這個表達式是一個複合比較器,用於處理包含 null 值的排序場景。

1. Comparator.naturalOrder()

  • 這是一個基礎比較器,用於對實現了 Comparable 接口的對象進行自然排序
  • 對於 Integer 類型,自然排序就是數值升序(1, 2, 3, 4...)
  • 相當於調用對象的 compareTo() 方法

2. Comparator.nullsLast(...)

  • 這是一個裝飾器比較器,用於處理 null
  • nullsLast 表示:null 值排在最後
  • 它接受一個內部比較器作為參數,用於比較非 null

3. 組合效果

Comparator.nullsLast(Comparator.naturalOrder()) 的排序規則是:

  1. null:按照自然排序(升序)排列
  2. null:統一排在最後面
  3. 混合情況:非 null 值在前面按升序排列,null 值在最後

4. 實際例子

假設 sort 字段的值有:[3, null, 1, null, 2]

排序後的結果將是:[1, 2, 3, null, null]

// 示例代碼
List<Integer> sorts = Arrays.asList(3, null, 1, null, 2);
sorts.sort(Comparator.nullsLast(Comparator.naturalOrder()));
System.out.println(sorts); // 輸出: [1, 2, 3, null, null]

5. 在您代碼中的應用

KbPageTreeResp 類中,這個比較器用於:

this.children.sort(Comparator.comparing(KbPageTreeResp::getSort, 
    Comparator.nullsLast(Comparator.naturalOrder())));
  • Comparator.comparing(KbPageTreeResp::getSort, ...) 提取每個對象的 sort 字段進行比較
  • 如果某些頁面的 sort 字段為 null,這些頁面會被排在最後
  • 其他有 sort 值的頁面按照數值升序排列

6. 為什麼需要處理 null

在實際業務中,頁面的 sort 字段可能:

  • 新創建的頁面還沒有設置排序值(null
  • 某些頁面被明確設置為不參與排序(null
  • 數據遷移或其他原因導致的空值

使用 nullsLast 可以確保程序不會因為 null 值而拋出 NullPointerException,同時提供合理的排序行為。

3.自定義排序規則

動態構建 SQL 排序語句(ORDER BY)*的 Java 方法。它的核心作用是根據*文件類型、標題、時間等多個維度,對一組文件/文件夾進行複合排序,並且支持多種排序策略(升序、降序、按時間、按名稱等)。
  
    /**
     * 構建ORDER BY語句
     * 文件夾和文件分別排序,文件夾在前,文件在後
     * xxxxx節點始終置頂
     * 
     * @return ORDER BY語句
     */
    private String buildOrderByClause() {
        StringBuilder orderBy = new StringBuilder();
        
        // 1. xxxxxx節點置頂
        orderBy.append("ORDER BY CASE WHEN title = 'xxxxxx' THEN 0 ELSE 1 END, ");
        
        // 2. 文件夾在前,文件在後
        orderBy.append("CASE WHEN type = 'FOLDER' THEN 0 ELSE 1 END, ");
        
        // 3. 根據排序類型進行排序
        if (StrUtil.isNotBlank(this.sortType)) {
            switch (this.sortType.toUpperCase()) {
                case "NAME_ASC":
                    // 文件名A-Z:英文A-Z → 中文A-Z → 數字0-9
                    // 使用ASCII碼和字符判斷來實現優先級排序
                    orderBy.append("CASE ")
                            .append("WHEN ASCII(SUBSTRING(title, 1, 1)) BETWEEN 65 AND 90 OR ASCII(SUBSTRING(title, 1, 1)) BETWEEN 97 AND 122 THEN CONCAT('1', title) ") // 英文字母
                            .append("WHEN ASCII(SUBSTRING(title, 1, 1)) > 127 THEN CONCAT('2', title) ") // 中文字符
                            .append("WHEN ASCII(SUBSTRING(title, 1, 1)) BETWEEN 48 AND 57 THEN CONCAT('3', title) ") // 數字
                            .append("ELSE CONCAT('4', title) ") // 其他字符
                            .append("END ASC");
                    break;
                case "NAME_DESC":
                    // 文件名Z-A:與A-Z相反
                    orderBy.append("CASE ")
                            .append("WHEN ASCII(SUBSTRING(title, 1, 1)) BETWEEN 65 AND 90 OR ASCII(SUBSTRING(title, 1, 1)) BETWEEN 97 AND 122 THEN CONCAT('1', title) ") // 英文字母
                            .append("WHEN ASCII(SUBSTRING(title, 1, 1)) > 127 THEN CONCAT('2', title) ") // 中文字符
                            .append("WHEN ASCII(SUBSTRING(title, 1, 1)) BETWEEN 48 AND 57 THEN CONCAT('3', title) ") // 數字
                            .append("ELSE CONCAT('4', title) ") // 其他字符
                            .append("END DESC");
                    break;
                case "TIME_DESC":
                    // 最近更新時間:文件夾按文件夾修改時間,文檔按文檔修改時間
                    orderBy.append("modifier_time DESC");
                    break;
                default:
                    // 默認排序:修改時間降序 + sort升序 + 標題升序
                    orderBy.append("modifier_time DESC, sort ASC, title ASC");
                    break;
            }
        } else {
            // 默認排序:修改時間降序 + sort升序 + 標題升序
            orderBy.append("modifier_time DESC, sort ASC, title ASC");
        }
        
        return orderBy.toString();
    }
}

正確的排序代碼

    /**
     * 構建ORDER BY語句
     * 文件夾和文件分別排序,文件夾在前,文件在後
     * xxxxxx節點始終置頂
     * 
     * @return ORDER BY語句
     */
    private String buildOrderByClause() {
        StringBuilder orderBy = new StringBuilder();
        
        // 1. xxxxx沉澱節點置頂
        orderBy.append("ORDER BY CASE WHEN title = 'xxxxxx' THEN 0 ELSE 1 END, ");
        
        // 2. 文件夾在前,文件在後
        orderBy.append("CASE WHEN type = 'FOLDER' THEN 0 ELSE 1 END, ");
        
        // 3. 根據排序類型進行排序
        if (StrUtil.isNotBlank(this.sortType)) {
            switch (this.sortType.toUpperCase()) {
                case "NAME_ASC":
                    // 文件名A-Z:英文A-Z → 中文A-Z → 數字0-9
                    // 統一使用gbk_chinese_ci排序規則避免collation衝突
                    orderBy.append("CASE ")
                            .append("WHEN title REGEXP '^[A-Za-z]' THEN 1 ") // 英文字母開頭
                            .append("WHEN title REGEXP '^[\\u4e00-\\u9fa5]' THEN 2 ") // 中文字符開頭
                            .append("WHEN title REGEXP '^[0-9]' THEN 3 ") // 數字開頭
                            .append("ELSE 4 ") // 其他字符
                            .append("END ASC, ")
                            .append("CONVERT(")
                            .append("CASE ")
                            .append("WHEN title REGEXP '^[A-Za-z]' THEN UPPER(title) ") // 英文按字母序,忽略大小寫
                            .append("WHEN title REGEXP '^[\\u4e00-\\u9fa5]' THEN title ") // 中文保持原樣
                            .append("WHEN title REGEXP '^[0-9]' THEN LPAD(title, 20, '0') ") // 數字按數值序,左補零確保正確排序
                            .append("ELSE title ") // 其他字符按默認序
                            .append("END USING gbk) COLLATE gbk_chinese_ci ASC");
                    break;
                case "NAME_DESC":
                    // 文件名Z-A:數字9-0 → 中文Z-A → 英文Z-A
                    // 統一使用gbk_chinese_ci排序規則避免collation衝突
                    orderBy.append("CASE ")
                            .append("WHEN title REGEXP '^[0-9]' THEN 1 ") // 數字開頭優先
                            .append("WHEN title REGEXP '^[\\u4e00-\\u9fa5]' THEN 2 ") // 中文字符其次
                            .append("WHEN title REGEXP '^[A-Za-z]' THEN 3 ") // 英文字母最後
                            .append("ELSE 4 ") // 其他字符
                            .append("END ASC, ")
                            .append("CONVERT(")
                            .append("CASE ")
                            .append("WHEN title REGEXP '^[A-Za-z]' THEN UPPER(title) ") // 英文按字母序,忽略大小寫
                            .append("WHEN title REGEXP '^[\\u4e00-\\u9fa5]' THEN title ") // 中文保持原樣
                            .append("WHEN title REGEXP '^[0-9]' THEN LPAD(title, 20, '0') ") // 數字按數值序,左補零確保正確排序
                            .append("ELSE title ") // 其他字符按默認序
                            .append("END USING gbk) COLLATE gbk_chinese_ci DESC");
                    break;
                case "TIME_DESC":
                    // 最近更新時間:文件夾按文件夾修改時間,文檔按文檔修改時間
                    orderBy.append("modifier_time DESC");
                    break;
                default:
                    // 默認排序:修改時間降序 + sort升序 + 標題升序
                    orderBy.append("modifier_time DESC, sort ASC, CONVERT(title USING gbk) COLLATE gbk_chinese_ci ASC");
                    break;
            }
        } else {
            // 默認排序:修改時間降序 + sort升序 + 標題升序(中文按拼音)
            orderBy.append("modifier_time DESC, sort ASC, CONVERT(title USING gbk) COLLATE gbk_chinese_ci ASC");
        }
        
        return orderBy.toString();
    }

user avatar u_16502039 頭像 debuginn 頭像 u_11365552 頭像 ligaai 頭像 kohler21 頭像 jkdataapi 頭像 dalideshoushudao 頭像 lvweifu 頭像 javalover 頭像 kubesphere 頭像 laoshideyangrouchuan 頭像 xiaoal 頭像
點贊 24 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.