MyBatis 的 @SelectProvider 是一个强大的注解,用于动态生成 SQL 语句

MyBatis 的 @SelectProvider 是一个强大的注解,用于动态生成 SQL 语句。让我详细介绍一下它的用途和使用方法。

一、@SelectProvider 的作用

主要用途:

  1. 动态 SQL 构建 - 根据条件动态生成复杂的 SQL
  2. 代码逻辑控制 - 使用 Java 代码控制 SQL 生成逻辑
  3. 复杂查询处理 - 处理在注解中难以表达的复杂 SQL
  4. 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 是非常合适的选择!

posted @ 2025-10-21 17:04  dirgo  阅读(6)  评论(0)    收藏  举报