MySQL 存儲過程根據輸入參數 `n` 循環生成 `n` 個結果集,每個結果集最多 20 條記錄:
CREATE PROCEDURE queryTop20(IN n INT)
BEGIN
DECLARE i INT DEFAULT 1;
WHILE i <= n DO
SELECT * FROM t_tab t WHERE t.a = i LIMIT 0, 20;
SET i = i + 1;
END WHILE;
END
期望 Java 端獲得 `List>` 結構,但 MyBatis 標準 `
CREATE PROCEDURE queryTop20(IN n INT)
BEGIN
-- 創建臨時表(會話級,自動清理)
CREATE TEMPORARY TABLE IF NOT EXISTS temp_tab (
id INT PRIMARY KEY,
col1 VARCHAR(100),
col2 INT,
-- 其他欄位對應 Tab 實體
batch_num INT -- 標識屬於第幾次查詢
);
TRUNCATE TABLE temp_tab; -- 清空上次殘留數據
DECLARE i INT DEFAULT 1;
WHILE i <= n DO
INSERT INTO temp_tab
SELECT *, i AS batch_num
FROM t_tab t WHERE t.a = i LIMIT 0, 20;
SET i = i + 1;
END WHILE;
-- 統一返回,按批次排序
SELECT * FROM temp_tab ORDER BY batch_num, id;
END
<select id="queryTop20" statementType="CALLABLE" resultType="Tab">
{call queryTop20(#{n, mode=IN, jdbcType=INTEGER})}
</select>
public List> queryTop20Grouped(Integer n) {
List flatList = mapper.queryTop20(n);
// 內存中按 batch_num 分組
return flatList.stream()
.collect(Collectors.groupingBy(Tab::getBatchNum))
.values()
.stream()
.map(ArrayList::new)
.collect(Collectors.toList());
}
若單次查詢結果集極大,可使用 MyBatis `Cursor` 流式處理:
<select id="queryTop20Cursor" statementType="CALLABLE" resultType="Tab" cursor="true">
{call queryTop20(#{n, mode=IN, jdbcType=INTEGER})}
</select>
// Mapper 接口
@Options(cursor = FetchType.SCROLL_SENSITIVE)
Cursor queryTop20Cursor(@Param("n") Integer n);
// Service 使用
try (Cursor cursor = mapper.queryTop20Cursor(n)) {
for (Tab tab : cursor) {
// 流式處理,避免全量載入內存
}
}
若業務允許,建議將存儲過程邏輯下沉到應用層:
// Mapper
List selectByA(@Param("a") Integer a, @Param("limit") Integer limit);
// Service
public List> queryTop20Refactored(Integer n) {
List> result = new ArrayList<>();
for (int i = 1; i <= n; i++) {
result.add(mapper.selectByA(i, 20));
}
return result;
}
優點:類型安全、易測試、易維護、支持動態 SQL。缺點:多次數據庫交互,網絡開銷增加。