Mybatis实现动态拼接SQL的实践指南

 更新时间:2025年11月23日 08:50:53   作者:颜如玉  
这篇文章主要为大家详细介绍了Mybatis实现动态拼接SQL的相关知识,文中的示例代码讲解详细, 感兴趣的小伙伴可以跟随小编一起学习一下

注:

  • 本文无源码分析
  • 单纯记录@SelectProvider实践,年纪大了(无奈的笑),备忘一下、
  • 演示的都是最简单的场景最简单的代码,无法代表业务的复杂性
  • AI润色,模仿加谬文风

背景介绍

最近工作接到一个荒诞的任务,实际情况比较复杂,总结一下有以下特点

  • 返回的字段如幽灵般游移不定
  • 目标表仿佛在迷雾中随每次请求变化
  • 过滤条件无规则无限制任意搭配

Mybatis的XML模板沉默以对,Mybatis-Plus的Wrapper亦束手无策。这让我想起 Navicat 的“筛选”功能,感觉两者有些相似,只不过Navicat场景更加简单一些。

于是,在荒诞的尽头,笔者做出了一个近乎宿命的选择:亲手拼接SQL——不是出于信念,而是因为别无选择。在这片逻辑崩塌的废墟上,唯有将字符串缝合成一句句临时的祷词,交付给数据库那沉默而全能的神谕。一旦接受了这种徒劳的合理性,道路便在虚无中浮现。

JdbcTemplate

那是来自Spring最原始的呼唤,一种近乎本能的回归。在框架层层叠叠的抽象迷宫中迷失之后,我听见了它低沉而直接的声音:没有装饰,没有隐喻,只有queryForMapqueryForList横亘于代码之中。我们熟悉它,正如熟悉自己的双手。

public class JdbcTemplate extends JdbcAccessor
    implements JdbcOperations {
    
    /* 获取单条记录 */
    @Override
    public Map<String, Object> queryForMap(String sql)
        throws DataAccessException {
        return result(queryForObject(sql, getColumnMapRowMapper()));
    }
    
    /* 获取多条记录 */
    @Override
    public List<Map<String, Object>> queryForList(String sql) 
        throws DataAccessException {
        return query(sql, getColumnMapRowMapper());
    }

}

然而这朴素之中藏着代价。它不够优雅,更谈不上智能。而当多数据源的阴影悄然降临,它便显露出更深的无力:每一次切换,都像在荒原上重新挖一口井,徒增重复、耦合与混乱。于是,这份最初的慰藉,终究在现实的重压下显出裂痕——它能承载希望,却无法驯服复杂。

@SelectProvider

后来,我遇见了 SelectProvider——它披着优雅的外衣,携带着一种近乎诗意的承诺。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(SelectProvider.List.class)
/* 该注解允许定制多种选项,平常使用这两个即可 */
public @interface SelectProvider {

    /* 填写生产SQL语句的类 */
    Class<?> type() default void.class;
    
    /* 填写生产SQL语句的方法名 */
    String method() default "";

}

只需在方法上轻轻标注,指明谁将为SQL赋形、何处孕育语句,MyBatis 便会如虔诚的信使,自动召唤那段动态生成的咒语,并将其送入数据库的圣殿。代码简洁,结构清晰,仿佛终于在这查询迷局中,找到了一丝理性的秩序。

/**
 * @author Raphael
 * @since 2025-11-19 15:21
 */
@DS("report")
@Mapper
public interface TestMapper {

    @SelectProvider(type = SqlProvider.class, method = "findById")
    Map<String, Object> findById(String tbl, String id);
  
    /* SQL语句提供类 */
    static class SqlProvider {

        /* 提供语句的方法 */
        public static String findById(String tbl, String id) {
            return "select * from " + tbl + " where id = " + id;
        }
        
    }
    
}

然而,这优雅之下潜伏着古老的危险:若对输入不加审视,任其流入拼接的缝隙,SQL 注入便如幽灵般悄然附体——一句恶意的字符串,足以撕裂整个系统的防线。因此,我不得不低声警告:凡使用此道者,必先以转义为盾,以校验为矛,方可在自由与安全之间行走。

/**
 * @author Raphael
 * @since 2025-11-19 15:21
 */
@DS("report")
@Mapper
public interface TestMapper {

    @SelectProvider(type = SqlProvider.class, method = "getById")
    Map<String, Object> getById(String tbl, String id);
  
    /* SQL语句提供类 */
    static class SqlProvider {

        /* 提供语句的方法 */
        public static String getById(@Param("tbl")String tbl, @Param("id") String id) {
            return new SQL()
                .SELECT("*")
                .FROM("tbl = #{tbl}")
                .WHERE("id = #{id}")
                .toString();
        }
        
    }
    
}

更令人踟蹰的是性能之问。有人提议缓存生成的 SQL,以避重复构造之耗。可这念头本身便陷入悖论:若 SQL 真能被缓存,那意味着其形式已然趋于稳定;

private static final String SQL =
    new SQL()
        .SELECT("*")
        .FROM("#{tbl}")
        .WHERE("id = #{id}")
        .toString();

public static String getById(@Param("tbl")String tbl, @Param("id") String id) {
    return SQL;
}

而若形式稳定,又何须动用SelectProvider这般为混沌而生的机制?这就像试图为风塑形,再宣称它的轮廓可以复用。

总结

回望整个探索过程,从 JdbcTemplate 的直白拼接到 SelectProvider 的动态封装,每一种方案都像是在不确定性的迷宫中点亮一盏临时的灯——足够照亮脚下的路,却照不透整座建筑的结构。

工具没有对错,只有适配与否;而所谓“优雅”或“原始”,往往只是我们面对复杂时情绪的投射。真正关键的,是在开放性与安全性、灵活性与性能之间,做出清醒的权衡,并为自己的选择负责。

最终,代码不只是逻辑的堆砌,更是判断的痕迹。我选择接受它的不完美,并用克制的拼接、谨慎的校验和明确的边界,为系统保留最后一道理性的防线。

到此这篇关于Mybatis实现动态拼接SQL的实践指南的文章就介绍到这了,更多相关Mybatis动态拼接SQL内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 利用hadoop查询两两之间有共同好友及他俩的共同好友都是谁

    利用hadoop查询两两之间有共同好友及他俩的共同好友都是谁

    一想到要实现求共同好友的功能,很多人都会想到redis来实现。但是redis存储和数据和计算时需要耗费较多的内存资源。所以文本将介绍另一种方法,即利用Hadoop中的MapReduce来实现,感兴趣的可以了解一下
    2022-01-01
  • IDEA启动服务提示端口被占用,Web server failed to start.Port was already in use.

    IDEA启动服务提示端口被占用,Web server failed to start.Port was al

    这篇文章主要介绍了IDEA启动服务提示端口被占用,Web server failed to start.Port was already in use.,本文给大家分享解决方案,分为linux系统和windows系统解决方案,需要的朋友可以参考下
    2023-07-07
  • Java中parallelStream().forEach()的踩坑日记

    Java中parallelStream().forEach()的踩坑日记

    本文主要介绍了Java中parallelStream().forEach()的踩坑日记,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • JDK、J2EE、J2SE、J2ME四个易混淆概念区分

    JDK、J2EE、J2SE、J2ME四个易混淆概念区分

    这篇文章将向你详细介绍JDK、J2EE、J2SE、J2ME的概念以及他们的关系区别。
    2015-09-09
  • java实现爬取知乎用户基本信息

    java实现爬取知乎用户基本信息

    这篇文章主要为大家介绍了一个基于JAVA的知乎爬虫,抓取知乎用户基本信息,感兴趣的小伙伴们可以参考一下
    2016-05-05
  • 浅析关于java的序列化和反序列化

    浅析关于java的序列化和反序列化

    这篇文章主要介绍了浅析关于java的序列化和反序列化,所谓序列化,就是把要传输的对象以及相关信息转换成字节数组进行存储的过程,而反序列化就是将字节数组再转回对象的过程,需要的朋友可以参考下
    2023-07-07
  • Java8特性使用Function代替分支语句

    Java8特性使用Function代替分支语句

    这篇文章主要介绍了Java8特性使用Function代替分支语句,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-09-09
  • Java中的Static class详解及实例代码

    Java中的Static class详解及实例代码

    这篇文章主要介绍了 Java中的Static class详解及实例代码的相关资料,在Java中我们可以有静态实例变量、静态方法、静态块。类也可以是静态的,需要的朋友可以参考下
    2017-03-03
  • SSM框架把日志信息保存到数据库过程详解

    SSM框架把日志信息保存到数据库过程详解

    这篇文章主要介绍了SSM框架把日志信息保存到数据库过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-07-07
  • Ribbon从入门到精通实战案例演示

    Ribbon从入门到精通实战案例演示

    Ribbon是Netflix开源的客户端负载均衡工具,用于微服务通信,动态获取服务实例并应用轮询、随机、权重响应时间等策略,下面通过实战案例给大家解析Ribbon的核心组件与工作原理,感兴趣的朋友一起看看吧
    2025-08-08

最新评论