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号