首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Activiti 流程引擎自定義 SQL 查詢實戰:Native Query 繞過鏈式 API 限制

     編輯:Java 綜合教學

Activiti 自定義 SQL 查詢最佳實踐:ManagementService 獲取表名、Native Query 參數綁定、已辦任務過濾 END_TIME_ 非空

Activiti 提供的鏈式查詢 API (createHistoricTaskInstanceQuery()) 功能有限,無法直接實現「已辦任務必須有結束時間」等複雜條件。本文演示如何通過 ManagementService.getTableName() 動態獲取表名、createNativeHistoricTaskInstanceQuery() 編寫原生 SQL、parameter() 防注入綁定參數、like 模糊查詢正確姿勢,解決實戰中 90% 的複雜查詢需求。

痛點場景:已辦任務列表必須過濾進行中任務

Activiti 會將運行時任務同步到歷史表 (ACT_HI_TASKINST):

  • 任務**完成** → 運行時表刪除,歷史表 END_TIME_ 有值
  • 任務**進行中** → 運行時表存在,歷史表 END_TIME_ IS NULL

直接用鏈式查詢 .list() 會包含 END_TIME_ IS NULL 的「偽已辦」數據,用戶體驗極差。

方案一:Native Query + 動態表名 (推薦)


@Autowired
private HistoryService historyService;
@Autowired
private ManagementService managementService;

public List findDoneTasks(String assignee, Pagination pagination) {
    // 1. 動態獲取歷史任務表名 (避免硬編碼 ACT_HI_TASKINST)
    String tableName = managementService.getTableName(HistoricTaskInstance.class);
    
    // 2. 構建原生 SQL
    String sql = "SELECT * FROM " + tableName + " H " +
                 "WHERE H.END_TIME_ IS NOT NULL " +           // 核心:過濾已完成
                 "AND H.ASSIGNEE_ LIKE #{assignee} " +        // 參數佔位符 #{paramName}
                 "ORDER BY H.END_TIME_ DESC";
    
    // 3. 執行 Native Query
    return historyService.createNativeHistoricTaskInstanceQuery()
        .sql(sql)
        .parameter("assignee", "%" + assignee + "%")  // 關鍵:% 拼在參數值中,而非 SQL 中
        .listPage(pagination.getStartResult(), pagination.getPageSize());
}

關鍵技術點解析

技術點作用注意事項
ManagementService.getTableName(Class)根據實體類動態解析對應數據庫表名 (含前綴)兼容不同表前綴配置,避免硬編碼
createNativeHistoricTaskInstanceQuery()進入原生 SQL 查詢模式,返回 NativeHistoricTaskInstanceQuery同理有 NativeTaskQuery, NativeProcessInstanceQuery 等
參數佔位符 #{paramName}防 SQL 注入,由引擎底層預編譯綁定**不要**在 SQL 中寫 LIKE '%#{param}%',會導致綁定失敗
parameter(name, value)綁定參數值模糊查詢的 % **必須拼在 value 中** ("%" + val + "%")
listPage(first, max)分頁查詢,對應 LIMIT offset, size需自行計算 offset = (page-1)*size

方案二:鏈式查詢 + Java 內存過濾 (小數據量可用)


List all = historyService.createHistoricTaskInstanceQuery()
    .taskAssignee(assignee)
    .orderByHistoricTaskInstanceEndTime().desc()
    .list(); // 查出所有再過濾

return all.stream()
    .filter(t -> t.getEndTime() != null) // 過濾 END_TIME_ IS NOT NULL
    .skip(offset)
    .limit(pageSize)
    .collect(Collectors.toList());

缺點:全量拉取內存壓力大、分頁不準確、不適用大數據量。

進階問題:歷史任務中如何獲取流程定義 Key (ProcessDefinitionKey)?

現狀:HistoricTaskInstance **無** getProcessDefinitionKey() 方法;HistoricProcessInstance 也無此字段 (僅 ProcessInstance 有)。

變通方案:利用 BUSINESS_KEY 隱藏流程 Key


// 啟動流程時設置
runtimeService.startProcessInstanceByKey(
    "leaveProcess",           // 流程定義 Key = 業務類類名
    "LeaveService.1001"       // BUSINESS_KEY = 業務類名.業務ID
);

// 查詢時解析
HistoricProcessInstance hpi = historyService.createHistoricProcessInstanceQuery()
    .processInstanceId(task.getProcessInstanceId())
    .singleResult();
String businessKey = hpi.getBusinessKey(); // "LeaveService.1001"
String processDefKey = businessKey.split("\\.")[0]; // "LeaveService"

前提:啟動流程時約定 BUSINESS_KEY = 流程Key.業務ID,且流程 Key 即業務類名。

避坑指南

  1. 表名前綴:生產環境常配置 databaseTablePrefix=ACT_,硬編碼表名會導致遷移失敗,必須用 ManagementService.getTableName()
  2. 模糊查詢 % 位置:LIKE #{param} + parameter("param", "%" + val + "%"),切勿寫成 LIKE '%#{param}%'
  3. 實體映射:Native Query 返回自動映射為 HistoricTaskInstance 實體,**僅映射表中存在的列**,自定義字段需手動 resultTransformer
  4. 性能:END_TIME_、ASSIGNEE_ 建議加索引,大量歷史數據查詢必須走索引

總結

Activiti 原生查詢 API 覆蓋 80% 簡單場景,剩餘 20% 複雜條件 (多表連接、特定字段過濾、自定義排序) **果斷使用 Native Query**。核心三步:**動態表名 → 原生 SQL + #{參數} → parameter 綁定值(含 %)**,安全、靈活、可維護。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved