JQL查询实战技巧与性能优化全解析

开发者欣亿 工具 阅读 2,626
赞 6 收藏
二维码
手机扫码查看
反馈

这玩意儿真该早点统一

我最近在搞一个前端数据过滤器,用户要能自定义查询条件,后端用 JQL(Java Query Language)处理。本来以为就是传个字符串过去完事,结果发现前端构造 JQL 字符串的方式五花八门,团队里三个人用了三种不同方案。折腾了一周,踩了无数坑,最后我才意识到:这事儿得有个标准做法。

JQL查询实战技巧与性能优化全解析

今天我就把见过的几种主流 JQL 前端构造方式拉出来遛一遛。不是那种教科书式的对比,而是从实战角度说说哪个顺手、哪个坑多、哪个改起来要命。结论先放这儿:我目前最推荐的是 DSL 对象 + 序列化函数,后面慢慢讲为啥。

谁更灵活?谁更省事?

先说说我见过的三种常见玩法:

  • 直接拼字符串(别笑,真有人这么干)
  • 用 JSON 结构描述查询,后端再转成 JQL
  • 写一个轻量 DSL 对象,配一个 serialize 函数输出 JQL

下面一个个来看,重点是踩坑经验。

方案一:字符串模板硬拼 —— 快速但致命

这是我接手项目时看到的第一种写法。简单到令人发指:

function buildJql(filters) {
  let jql = 'issueType = Bug';
  if (filters.priority) {
    jql +=  AND priority = "${filters.priority}";
  }
  if (filters.assignee) {
    jql +=  AND assignee = "${filters.assignee}";
  }
  if (filters.labels && filters.labels.length) {
    jql +=  AND labels IN (${filters.labels.map(l => "${l}").join(', ')});
  }
  return jql;
}

看起来没问题,对吧?但问题是,这种写法根本没法维护。我加了个“创建时间范围”条件,写了半天发现括号嵌套乱了,还漏了个转义。后来发现用户如果名字带引号,直接炸掉。

这里注意我踩过好几次坑:字符串拼接最容易忽略转义和优先级。比如 AND/OR 混用时没加括号,逻辑就错了。而且一旦复杂点,比如有 nested query,代码立马变成意大利面条。

优点只有一个:上手快。适合 demo 或一次性脚本。正式项目?赶紧换掉。

方案二:JSON 结构化描述 —— 看着规范,实则绕路

第二种是团队里另一个同事搞的,想“规范化”,于是定义了一套 JSON 查询结构:

{
  "and": [
    { "field": "issueType", "operator": "=", "value": "Bug" },
    { "field": "priority", "operator": "=", "value": "High" },
    {
      "or": [
        { "field": "assignee", "operator": "=", "value": "zhangsan" },
        { "field": "assignee", "operator": "=", "value": "lisi" }
      ]
    }
  ]
}

然后前端传这个结构给后端,后端再写一套解析逻辑转成 JQL。听着挺合理?问题来了:前后端都要维护这套 schema。有一次我改了个嵌套逻辑,前端改了结构,后端没同步,接口直接 500。调试时两边互相甩锅。

而且这套结构太“通用”了,反而失去了 JQL 本身的表达力。比如 JQL 支持 Sprint in openSprints() 这种函数式语法,你 JSON 怎么表示?最后只能塞个 raw 字段,又回到字符串拼接的老路。

亲测有效但不推荐:适合中后台系统统一查询引擎的场景,但如果只是对接 Jira 或类似系统,纯属给自己加戏。

方案三:DSL 对象 + 序列化 —— 我现在的标准答案

第三种是我现在主推的。不追求完全抽象,而是贴近 JQL 语法设计一个轻量 DSL,再写个 serialize 把它转成合法字符串。

const jqlBuilder = {
  conditions: [],
  and(...conds) {
    this.conditions.push(...conds);
    return this;
  },
  or(...conds) {
    this.conditions.push({ or: conds });
    return this;
  },
  field(name, op, value) {
    return { field: name, operator: op, value };
  },
  toQuery() {
    return this.conditions.map(serializeCondition).join(' AND ');
  }
};

function serializeCondition(cond) {
  if (cond.or) {
    return (${cond.or.map(serializeCondition).join(' OR ')});
  }
  if (cond.field && cond.operator && cond.value !== undefined) {
    const val = Array.isArray(cond.value)
      ? (${cond.value.map(v => "${escapeQuote(v)}").join(', ')})
      : "${escapeQuote(cond.value)}";
    return ${cond.field} ${cond.operator} ${val};
  }
  return '';
}

function escapeQuote(str) {
  return String(str).replace(/"/g, '\"');
}

用起来长这样:

const query = jqlBuilder
  .and(
    jqlBuilder.field('issueType', '=', 'Bug'),
    jqlBuilder.field('priority', '=', 'High'),
    jqlBuilder.or(
      jqlBuilder.field('assignee', '=', 'zhangsan'),
      jqlBuilder.field('assignee', '=', 'lisi')
    )
  )
  .toQuery();

console.log(query);
// 输出: issueType = "Bug" AND priority = "High" AND (assignee = "zhangsan" OR assignee = "lisi")

这个方案最爽的地方是:既保留了代码可读性,又不会脱离 JQL 本质。加新语法也方便,比如我想支持 IN 操作符,加个 in 方法就行:

in(field, values) {
  return { field, operator: 'IN', value: values };
}

序列化时单独处理即可。也不用担心前后端耦合,因为最终只传字符串。

当然也不是完美。比如复杂的 nested query 写起来还是有点啰嗦,但至少结构清晰,改起来不怕。

性能对比:差距比我想象的小

我本来以为方案三会慢不少,毕竟多了对象构建和遍历。于是写了个简单 benchmark 测试一万次生成相同查询:

  • 字符串拼接:~80ms
  • JSON 结构:~120ms(含 deep clone 开销)
  • DSL 对象:~95ms

差距其实可以忽略。真正影响性能的是网络请求和后端解析,前端这点计算根本不值一提。所以别拿性能当借口继续拼字符串了。

我的选型逻辑

看场景,我一般选 DSL 方案,原因很实际:

  • 错误率低:自动转义、括号闭合,不容易出错
  • 可复用:同一个 builder 可以组合不同查询块
  • 易调试:打印中间对象比打印字符串容易分析多了
  • 扩展性强:加个 orderBylimit 很自然

只有两种情况我会妥协:

  • 临时脚本或 migration 工具:直接拼字符串,反正不用长期维护
  • 公司已有统一查询网关:那就按他们的 JSON schema 来,别自己造轮子

其他时候,我都愿意多花半小时搭个靠谱的 builder,省下后续三天 debug 的时间。

额外提醒:这几个坑我踩过

不管用哪种方案,这几个点一定要注意:

  • 字段名大小写敏感:JQL 里 Assigneeassignee 不是一回事,最好统一小写
  • 值必须双引号包裹:即使数字或布尔值,在 JQL 中也建议加引号,避免歧义
  • 空值处理:filter 为空数组时别生成 IN (),直接跳过条件
  • 特殊字符转义:除了引号,像反斜杠也要处理,不然后端解析失败

我还遇到过一次线上问题:用户筛选标签时用了中文冒号,没转义,导致整个查询无效。后来我在 escapeQuote 里加上了常见控制字符过滤才解决。

结尾:就这样吧,够用就好

以上是我对 JQL 前端构造方案的对比总结。没有银弹,但我比较喜欢用 DSL 那套,写得清楚,看得明白,改得安心。

这个技巧的拓展用法还有很多,比如缓存常用查询条件、支持链式调用、集成到 UI 组件联动。后续会继续分享这类实战经验。

以上是我踩坑后的总结,希望对你有帮助。有不同看法欢迎评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论