注:
- 本文无源码分析
- 单纯记录@selectprovider实践,年纪大了(无奈的笑),备忘一下、
- 演示的都是最简单的场景最简单的代码,无法代表业务的复杂性
- ai润色,模仿加谬文风
背景介绍
最近工作接到一个荒诞的任务,实际情况比较复杂,总结一下有以下特点
- 返回的字段如幽灵般游移不定
- 目标表仿佛在迷雾中随每次请求变化
- 过滤条件无规则无限制任意搭配
mybatis的xml模板沉默以对,mybatis-plus的wrapper亦束手无策。这让我想起 navicat 的“筛选”功能,感觉两者有些相似,只不过navicat场景更加简单一些。

于是,在荒诞的尽头,笔者做出了一个近乎宿命的选择:亲手拼接sql——不是出于信念,而是因为别无选择。在这片逻辑崩塌的废墟上,唯有将字符串缝合成一句句临时的祷词,交付给数据库那沉默而全能的神谕。一旦接受了这种徒劳的合理性,道路便在虚无中浮现。
jdbctemplate
那是来自spring最原始的呼唤,一种近乎本能的回归。在框架层层叠叠的抽象迷宫中迷失之后,我听见了它低沉而直接的声音:没有装饰,没有隐喻,只有queryformap与queryforlist横亘于代码之中。我们熟悉它,正如熟悉自己的双手。
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内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论