SQL 模板写到这里,为什么 Mongo 也可以用同一种方式接进来

前面几篇一直在讲 Java / Spring 项目里的动态 SQL。

从最早的字符串拼接,到 FSScript 风格的 SQL 模板,再到固定 helper、权限条件注入,这一小段其实可以先收住了。它解决的是一个很具体的问题:在已经使用 Spring JPA / Hibernate 的企业项目里,复杂查询不要继续散落在 Repository、Service、JdbcTemplate 的拼接分支里。

但企业系统里不一定只有关系型数据库。

很多项目会在后面接入 MongoDB。原因也很常见:有些数据天生更像文档,比如操作日志、事件流、配置快照、表单扩展字段、外部系统回调、非固定结构的业务记录。它们放进 MySQL / PostgreSQL 不是不行,但字段扩展、嵌套结构和历史版本会越来越别扭。

问题是,一旦加了 Mongo,我们并不希望查询代码又回到另一套混乱状态。

如果 SQL 这边已经开始用模板来管理动态条件、分页、排序、当前用户和权限上下文,Mongo 这边最好也能保持相似的工程形态。

否则后端代码很快会变成两种风格:

java 复制代码
// 关系型查询:一套模板和 helper
orderDataset.query(form);

另一边是:

java 复制代码
// Mongo 查询:在 Service 里手写一堆 Criteria
Query query = new Query();
if (status != null) {
    query.addCriteria(Criteria.where("status").is(status));
}
if (keyword != null) {
    query.addCriteria(Criteria.where("orderNo").regex(keyword));
}
query.addCriteria(Criteria.where("tenantId").is(currentUser.getTenantId()));
query.with(Sort.by(Sort.Direction.DESC, "createTime"));

这不是 MongoTemplate 不好用。它在 Java 代码里很直接,也足够强。

但如果一个项目里已经有很多可选条件、分页查询、导出查询和权限条件,Criteria 同样会遇到动态 SQL 那类问题:条件分支散落、参数来源混杂、权限边界不稳定、AI 辅助修改时不容易看出哪些字段不能动。

所以早期做 Mongo 接入时,一个自然的想法是:不要重新发明一套完全不同的东西,而是让 Mongo 查询也走 FSScript 风格。

大致形态可以是这样:

javascript 复制代码
import '@mcpMongoTemplate';
import {getCurrentUser, getDataScope} from '@authService';

export const setName = 'order_event';
export const mongoTemplate = mcpMongoTemplate;

const user = getCurrentUser();
const scope = getDataScope('order_event');

export function buildMongo() {
    const query = {
        tenantId: user.tenantId,
        teamId: {$in: scope.teamIds}
    };

    if (form.param.status) {
        query.status = form.param.status;
    }

    if (form.param.keyword) {
        query.orderNo = {$regex: form.param.keyword};
    }

    return query;
}

export const sort = {
    createTime: -1
};

这个示例重点不在 Mongo 语法,而在几个边界。

第一,集合名是脚本显式导出的。

javascript 复制代码
export const setName = 'order_event';

它不是让调用方临时传一个 collection 名字,也不是让 AI 自己猜该查哪个集合。查询脚本本身知道自己面向哪个数据集。

第二,MongoTemplate 可以来自 Spring。

javascript 复制代码
import '@mcpMongoTemplate';
export const mongoTemplate = mcpMongoTemplate;

这和前面 SQL 模板里通过 import '@springBean' 接入 Spring 服务是一致的。连接、事务边界、环境配置、Bean 生命周期仍然交给 Spring,不需要在脚本里重新管理。

第三,权限上下文还是从系统里来。

javascript 复制代码
const user = getCurrentUser();
const scope = getDataScope('order_event');

Mongo 查询并不因为换了数据库,就可以绕过当前用户、租户、团队范围这些条件。关系型数据库里需要注入的数据范围,Mongo 里同样需要。

第四,脚本导出的是 Mongo 查询对象或构建函数。

javascript 复制代码
export function buildMongo() {
    const query = {
        tenantId: user.tenantId,
        teamId: {$in: scope.teamIds}
    };

    if (form.param.status) {
        query.status = form.param.status;
    }

    return query;
}

底层执行时,引擎可以拿到 mongobuildMongo() 的结果,再交给 MongoCollection 执行 find;如果脚本返回的是 aggregation,也可以走 aggregate。分页、limit、skip、sort 仍然由统一的查询执行过程处理。

这一步的意义不是把 Mongo 查询包装得更炫。

它只是把 SQL 模板阶段已经得到的一些经验,迁移到文档数据库上:

  • 查询条件继续放在脚本里,而不是散在 Java 分支里;
  • Spring 上下文继续可以注入;
  • 当前用户和数据范围继续由系统提供;
  • 分页和排序继续走统一执行路径;
  • 返回结果继续可以进入统一的 PagingResult;
  • AI 生成或修改查询时,仍然面对一段人工可读的模板。

对 LLM 来说,这个成本也很低。

如果让它直接写一大段 Java Criteria 代码,它需要理解 Spring Data MongoDB API、对象链式调用、分页排序、参数来源,还要保证不会漏掉权限条件。

但如果告诉它:这是一个 FSScript Mongo 模板,你只需要在 buildMongo() 里补页面筛选条件,tenantIdteamId 这些系统条件必须保留,它的任务就变小了。

这和 SQL 模板阶段的思路一样:不是让 AI 接管数据库查询,而是让它在一个更短、更稳定、更容易审查的模板里工作。

当然,这也不是完整的 Mongo 语义层。

它没有解决所有文档结构治理问题,也不意味着可以把任意 Mongo 查询开放给外部调用。它只是一个扩展点:当项目里已经开始用模板管理 SQL 查询时,Mongo 查询也可以用类似方式接进来,避免因为换了数据源就丢掉已有的工程边界。

在 Foggy 早期实现里,这部分对应的是 foggy-dataset-mongo:通过 .ms 脚本加载 Mongo 查询,导出 setNamemongoTemplatemongobuildMongo,执行时支持 find / aggregate、分页、排序和 Spring Bean 注入。

再往后,Mongo 也可以进入模型化阶段,例如 type: 'mongo' 的 TM,以及基于它暴露字段的 QM。但那是下一层问题。

这一篇先停在更早的位置:SQL 模板不是终点,它证明的是一种写法。只要边界足够清楚,同样的写法也可以扩展到 Mongo。

相关代码:foggy-dataset-mongo

github.com/foggy-proje...

相关模型示例:Mongo TM / QM 测试模型

github.com/foggy-proje...

相关推荐
卷无止境1 小时前
零信任架构与传统边界安全:一场关于"信任"的根本分歧
后端
风止何安啊1 小时前
我一个前端仔,居然用 Python 搞起了 AI?从零到一,撸了个 AI 聊天框小 demo
前端·人工智能·后端
逍遥运德1 小时前
PostgreSQL ---【序列】用法详解
后端·sql·postgresql
回家路上绕了弯2 小时前
AgentScope Harness 深度实战:让Java智能体从“Demo可用”走向“生产可用”
后端
卷心菜投手ovo2 小时前
RAG 为什么引用总是对不上?
后端·github
foggyprojects2 小时前
动态 SQL 模板里,权限条件为什么要注入而不是散落在业务代码里
后端
无风听海2 小时前
ASP.NET Core .NET 10 错误响应体系全景:从 BadRequest 到编译器基础设施
后端·asp.net·.net
文心快码BaiduComate2 小时前
从个人效能到组织资产:文心快码企业版Agent Hub上线,提升团队AI编程效能
前端·后端·程序员
雪隐3 小时前
个人电脑玩AI00-前言
人工智能·后端