MyBatis 的 @SelectProvider 是一個強大的注解,用于動態生成 SQL 語句
MyBatis 的 @SelectProvider 是一個強大的注解,用于動態生成 SQL 語句。讓我詳細介紹一下它的用途和使用方法。
一、@SelectProvider 的作用
主要用途:
- 動態 SQL 構建 - 根據條件動態生成復雜的 SQL
- 代碼邏輯控制 - 使用 Java 代碼控制 SQL 生成邏輯
- 復雜查詢處理 - 處理在注解中難以表達的復雜 SQL
- SQL 復用 - 多個方法可以共用同一個 Provider 類
與其他注解對比:
@Select:適合固定不變的簡單 SQL@SelectProvider:適合動態的、復雜的 SQL
二、基本使用方法
1. 定義 Provider 類
public class CheckSqlProvider {
// 簡單示例
public String selectAll() {
return "SELECT * FROM C_CHECKS_T";
}
// 帶參數的動態 SQL
public String selectByDateRange(Map<String, Object> params) {
String startDate = (String) params.get("startDate");
String endDate = (String) params.get("endDate");
return "SELECT * FROM C_CHECKS_T WHERE CHECK_DATE >= TO_DATE('" +
startDate + "', 'YYYY-MM-DD') AND CHECK_DATE < TO_DATE('" +
endDate + "', 'YYYY-MM-DD')";
}
}
2. 在 Mapper 中使用
@Mapper
public interface CChecksTMapper extends BaseMapper<CChecksT> {
@SelectProvider(type = CheckSqlProvider.class, method = "selectAll")
List<CChecksT> selectAll();
@SelectProvider(type = CheckSqlProvider.class, method = "selectByDateRange")
List<CChecksT> selectByDateRange(@Param("startDate") String startDate,
@Param("endDate") String endDate);
}
三、針對你的復雜查詢的實現
1. 創建專門的 Provider 類
public class CheckSummaryProvider {
public String selectCheckSummary(Map<String, Object> params) {
String startDate = (String) params.get("startDate");
String endDate = (String) params.get("endDate");
StringBuilder sql = new StringBuilder();
sql.append("SELECT a.*, b.TRACK_RECTIFICATION, b.TRACK_ESTIMATED_FINISH_TIME ");
sql.append("FROM (");
sql.append(" SELECT t.BRANCH, count(*) jcxm, ");
sql.append(" COUNT(CASE WHEN check_status IN (4,6) THEN 1 END) AS wczs, ");
sql.append(" COUNT(CASE WHEN check_status IN (2,3,5) THEN 1 END) AS wwc ");
sql.append(" FROM C_CHECKS_T t ");
sql.append(" WHERE t.CHECK_DATE >= TO_DATE('").append(startDate).append("', 'YYYY-MM-DD') ");
sql.append(" AND t.CHECK_DATE < TO_DATE('").append(endDate).append("', 'YYYY-MM-DD') ");
sql.append(" GROUP BY t.BRANCH ");
sql.append(") a ");
sql.append("LEFT JOIN (");
sql.append(" SELECT t.BRANCH, t.TRACK_RECTIFICATION, t.TRACK_ESTIMATED_FINISH_TIME ");
sql.append(" FROM C_CHECKS_T t ");
sql.append(" WHERE t.check_status IN (2,3,5) ");
sql.append(" AND t.CHECK_DATE >= TO_DATE('").append(startDate).append("', 'YYYY-MM-DD') ");
sql.append(" AND t.CHECK_DATE < TO_DATE('").append(endDate).append("', 'YYYY-MM-DD') ");
sql.append(") b ON a.BRANCH = b.BRANCH ");
sql.append("ORDER BY a.BRANCH");
return sql.toString();
}
// 更安全的參數綁定方式
public String selectCheckSummarySafe(Map<String, Object> params) {
return new SQL() {{
SELECT("a.*, b.TRACK_RECTIFICATION, b.TRACK_ESTIMATED_FINISH_TIME");
FROM("(" +
" SELECT t.BRANCH, count(*) jcxm, " +
" COUNT(CASE WHEN check_status IN (4,6) THEN 1 END) AS wczs, " +
" COUNT(CASE WHEN check_status IN (2,3,5) THEN 1 END) AS wwc " +
" FROM C_CHECKS_T t " +
" WHERE t.CHECK_DATE >= TO_DATE(#{startDate}, 'YYYY-MM-DD') " +
" AND t.CHECK_DATE < TO_DATE(#{endDate}, 'YYYY-MM-DD') " +
" GROUP BY t.BRANCH " +
") a");
LEFT_OUTER_JOIN("(" +
" SELECT t.BRANCH, t.TRACK_RECTIFICATION, t.TRACK_ESTIMATED_FINISH_TIME " +
" FROM C_CHECKS_T t " +
" WHERE t.check_status IN (2,3,5) " +
" AND t.CHECK_DATE >= TO_DATE(#{startDate}, 'YYYY-MM-DD') " +
" AND t.CHECK_DATE < TO_DATE(#{endDate}, 'YYYY-MM-DD') " +
") b ON a.BRANCH = b.BRANCH");
ORDER_BY("a.BRANCH");
}}.toString();
}
}
2. 在 Mapper 中調用
@Mapper
public interface CChecksTMapper extends BaseMapper<CChecksT> {
@SelectProvider(type = CheckSummaryProvider.class, method = "selectCheckSummary")
List<Map<String, Object>> selectCheckSummary(@Param("startDate") String startDate,
@Param("endDate") String endDate);
@SelectProvider(type = CheckSummaryProvider.class, method = "selectCheckSummarySafe")
List<Map<String, Object>> selectCheckSummarySafe(@Param("startDate") String startDate,
@Param("endDate") String endDate);
}
四、高級用法
1. 動態條件查詢
public class DynamicCheckProvider {
public String selectByConditions(Map<String, Object> params) {
String branch = (String) params.get("branch");
List<Integer> statusList = (List<Integer>) params.get("statusList");
String startDate = (String) params.get("startDate");
String endDate = (String) params.get("endDate");
StringBuilder sql = new StringBuilder("SELECT * FROM C_CHECKS_T WHERE 1=1 ");
if (branch != null && !branch.trim().isEmpty()) {
sql.append(" AND BRANCH = '").append(branch).append("'");
}
if (statusList != null && !statusList.isEmpty()) {
sql.append(" AND check_status IN (");
for (int i = 0; i < statusList.size(); i++) {
if (i > 0) sql.append(",");
sql.append(statusList.get(i));
}
sql.append(")");
}
if (startDate != null) {
sql.append(" AND CHECK_DATE >= TO_DATE('").append(startDate).append("', 'YYYY-MM-DD')");
}
if (endDate != null) {
sql.append(" AND CHECK_DATE < TO_DATE('").append(endDate).append("', 'YYYY-MM-DD')");
}
sql.append(" ORDER BY BRANCH, CHECK_DATE");
return sql.toString();
}
}
2. 使用 MyBatis SQL 構建器
public String buildComplexQuery(Map<String, Object> params) {
return new SQL() {{
SELECT("t.BRANCH", "COUNT(*) as total");
SELECT("SUM(CASE WHEN check_status IN (4,6) THEN 1 ELSE 0 END) as completed");
FROM("C_CHECKS_T t");
WHERE("t.CHECK_DATE >= TO_DATE(#{startDate}, 'YYYY-MM-DD')");
WHERE("t.CHECK_DATE < TO_DATE(#{endDate}, 'YYYY-MM-DD')");
GROUP_BY("t.BRANCH");
ORDER_BY("t.BRANCH");
}}.toString();
}
五、最佳實踐建議
1. 參數安全
// 不推薦 - SQL注入風險
"WHERE name = '" + name + "'"
// 推薦 - 使用 #{} 參數綁定
"WHERE name = #{name}"
2. 代碼組織
// 按功能模塊組織 Provider 類
- provider/
- UserSqlProvider.java
- OrderSqlProvider.java
- CheckSqlProvider.java
3. 異常處理
public String safeQuery(Map<String, Object> params) {
try {
// SQL 構建邏輯
return sql.toString();
} catch (Exception e) {
throw new RuntimeException("SQL構建失敗", e);
}
}
六、總結
@SelectProvider 的優勢:
- ? 靈活性 - 可以處理極其復雜的 SQL 邏輯
- ? 可維護性 - SQL 在 Java 代碼中,便于調試和維護
- ? 復用性 - 多個 Mapper 方法可以共用 Provider
- ? 類型安全 - 編譯時檢查 SQL 構建邏輯
對于你的復雜查詢場景,使用 @SelectProvider 是非常合適的選擇!

浙公網安備 33010602011771號