解决Fortify漏洞${tableName} SQL注入问题


@Repository
public interface AbstractPDMWorkflowEntityMapper {

    @SelectProvider(type = SqlProvider.class,method = "getEntityByProvider")
    AbstractPDMWorkflowEntity getEntityByProcessId(@Param("tableName") String tableName, @Param("processId") Long processId);

    @ResultMap("AbstractPDMWorkflowEntityResult")
    @Select("select * from ${tableName} where id=#{entityId}")
    AbstractPDMWorkflowEntity getEntityByIdAndFormDefinition(@Param("tableName") String tableName, @Param("entityId") Long entityId);

    @Update("update ${tableName} set process_type='N',form_code=#{formCode},applier_No=#{applierNo},applier_Name=#{applierName},process_instance_id=#{processInstanceId} where id=#{id}")
    void updateAbstractPDMWorkflowProcess(AbstractPDMWorkflowEntity abstractPDMWorkflowEntity);

    @Update("update ${tableName} set business_state=#{businessState},pass_date=#{passDate} where id=#{id}")
    void updateAbstractPDMWorkflowStatus(AbstractPDMWorkflowEntity abstractPDMWorkflowEntity);

    class SqlProvider {

        public String getEntityByProvider(@NonNull Map<String, Object> params){
            // 获取动态传入的表名(注意:${} 属于字符串直接替换,需防范 SQL 注入风险)
            String tableName = (String) params.get("tableName");

            return new SQL() {{
                // 1. 拼接 SELECT 字段
                SELECT("d.*");
                SELECT("fd.table_name");
                SELECT("fd.mapper_name");
                SELECT("bs.business_State_en");

                // 2. 拼接 FROM 主表(动态表名)
                FROM(tableName + " d");

                // 3. 拼接第一个 LEFT JOIN(包含多个 ON 条件)
                JOIN("form_definition fd ON d.form_code = fd.code AND fd.preassignparticipant = true AND fd.entitystate = 0");

                // 4. 拼接第二个 LEFT JOIN
                JOIN("business_state_convert bs ON d.business_state = bs.business_state");

                // 5. 拼接 WHERE 条件(#{processId} 会自动被 MyBatis 解析为预编译参数,防止注入)
                WHERE("d.process_instance_id = #{processId}");
            }}.toString();
        }
    }
}

@SelectProvider 和xml 和 @Select 区别, 执行速度, 更建议哪种?

在 MyBatis 中,@Select、@SelectProvider 和 XML 映射文件是三种常见的 SQL 编写方式。它们在灵活性、维护性和执行性能上有着明显的区别。

⚖️ 三种方式的核心区别

维度 @Select (静态注解) @SelectProvider (动态注解) XML 映射文件
适用场景 极其简单的单表、固定 SQL 查询 中等复杂度的动态 SQL(如多条件筛选、动态排序) 复杂 SQL(多表联查、复杂子查询)、需要高度可维护性的业务
动态 SQL 能力 弱(需配合 <script> 标签,字符串拼接极难维护) 强(通过 Java 代码和 SQL Builder 灵活构建) 极强(原生支持 <if>, <foreach>, <choose> 等标签,结构清晰)
代码维护性 简单直观,但 SQL 复杂时接口会变得混乱 逻辑在 Java 类中,可读性较好,但复杂 SQL 仍需脑补还原 SQL 与 Java 代码完全解耦,便于 DBA 审核、优化和统一管理
功能支持 仅支持简单结果映射,不支持 SQL 片段复用 @Select 支持复杂的 ResultMap 映射、SQL 片段(<sql>)复用、多数据库适配

🚀 执行速度对比

在性能方面,XML 映射文件通常略优于注解方式(包括 @Select 和 @SelectProvider)。

  • XML 方式:在高并发、高频调用的场景下,XML 方式具有明显的性能优势(实测吞吐量更高、CPU 占用更低)。这主要得益于 MyBatis 框架对 XML 中定义的 MappedStatement 进行了深度的解析优化和高效的执行计划缓存机制。
  • @SelectProvider 方式:由于它需要在运行时通过反射调用 Java 方法来动态生成 SQL 字符串,在高并发查询时,这个动态生成和解析的过程会带来一定的性能开销,导致 QPS(每秒查询率)相对较低。
  • @Select 方式:虽然 SQL 在编译期就已经确定,加载速度较快,但在复杂和高频场景下的整体执行效率依然不如经过深度优化的 XML 方式。

💡 最终建议:到底选哪种?

在实际的企业级项目开发中,强烈建议采用“混合开发,以 XML 为主”的策略。具体的选型建议如下:

  1. 极度简单的单表 CRUD:
    可以直接使用 @Select、@Insert 等静态注解,或者引入 MyBatis-Plus 这类增强工具,通过 Wrapper 甚至无需编写 SQL,极大提升开发效率。
  2. 中等复杂的动态查询:
    如果业务逻辑主要是多条件的动态拼接,且你希望保持代码的强类型安全,使用 @SelectProvider 配合 MyBatis 自带的 SQL 构建器是一个非常优雅的选择。
  3. 复杂业务与核心 SQL(强烈推荐 XML):
    对于涉及多表 Join、复杂报表、多层子查询,或者对性能有极致要求的场景,请直接使用 XML 映射文件。将 SQL 集中放在 XML 中不仅结构最清晰、最容易维护,还能享受到 MyBatis 框架最好的性能优化。
  • 避坑指南:在混合使用时请注意,MyBatis 默认会优先加载 XML 中的 SQL 定义。如果同一个 Mapper 接口方法同时在 XML 和注解中定义了 SQL,XML 中的定义会覆盖注解,为了避免混淆,建议尽量保持单一项目中 SQL 编写风格的统一。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容