ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

JeecgBoot 全文检索实战:从数据库 LIKE 到 Elasticsearch 集成的完整迁移

2026/8/14 17:40:29 拓冰建站 浏览量
JeecgBoot 全文检索实战:从数据库 LIKE 到 Elasticsearch 集成的完整迁移

JeecgBoot 全文检索实战:从数据库 LIKE 到 Elasticsearch 集成的完整迁移

【免费下载链接】jeecg-boot【低代码迈入v2.0时代,一句话即可生成整个系统】企业级AI低代码平台,一键生成前后端代码甚至整个系统。 AI Skills 一句话画流程、设计表单、生成报表、大屏。内置 AI应用平台涵盖:AI聊天、知识库、流程编排、MCP插件等,兼容主流大模型。引领AI低代码「Skills 生成 → 在线配置 → 代码生成 → 手工合并->AI修改」开发模式,解决 Java 项目 90% 重复工作,提高效率又不失灵活。项目地址: https://gitcode.com/GitHub_Trending/je/jeecg-boot

如果你的系统里有一张两百万行的用户表,前端搜索框每敲一个字就触发一次LIKE '%关键字%'查询,你会发现接口耗时从几十毫秒一路涨到几秒,数据库 CPU 也随之报警。这是很多管理后台的真实处境,也是我们决定把 JeecgBoot 的搜索能力迁移到 Elasticsearch 的直接原因。这篇文章就围绕 JeecgBoot Elasticsearch 集成的完整过程展开,记录我们从零配置、写代码到踩坑排错的全流程,读完你可以照着在自己的 JeecgBoot 项目里复现一遍。

为什么非换不可:先看 LIKE 查询的三个硬伤

在动手之前,我们先说清楚"为什么要迁移",否则你会觉得这是没事找事。

第一个硬伤是扫描成本。LIKE '%关键词%'无法利用 B+ 树索引,本质是全表扫描,数据量过百万后性能断崖式下跌。

第二个硬伤是命中率。用户搜"张伟",数据库只认字面完全一致的匹配;想支持"错别字容忍""同义词扩展""拼音前缀"这类体验,SQL 基本无能为力。

第三个硬伤是排序。相关性打分(比如标题命中比正文命中权重更高)在数据库里要靠手写复杂 CASE WHEN,维护成本极高。

Elasticsearch 解决的正是这三件事:倒排索引、分词与打分。它不替代数据库做主存储,而是作为"检索副本"存在——数据先落库,再同步到 ES。

前置条件:三条清单,一项都不能少

JeecgBoot 对 ES 的封装基于 REST API 实现,不依赖重量级客户端依赖,这一点对新手非常友好。动手前确认三件事:

  1. Elasticsearch 服务:7.x 及以上版本均可,8.x 也兼容(代码里针对 7.x 做了显式兼容处理)。
  2. JeecgBoot 工程:任意 3.x 版本即可,ES 相关类位于jeecg-boot-base-core模块,无需额外引入 starter。
  3. Java + Maven 环境:能正常编译运行 JeecgBoot 即可。

这里有个值得注意的设计:模板类的注册是有条件的。查看源码会发现JeecgElasticsearchTemplate上标了@ConditionalOnProperty(prefix = "jeecg.elasticsearch", name = "cluster-nodes"),意思是只有当你配置了 ES 地址,这个 Bean 才会被创建。没配 ES 的项目不会因为类加载而报错,这也是它能作为可选功能安静存在的原因。

核心配置类源码:jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/vo/Elasticsearch.java

三步完成 ES 连接配置

配置集中在application.yml,总共就两个键,加上注释也不到十行:

jeecg: elasticsearch: cluster-nodes: 127.0.0.1:9200 # ES 服务地址,多个节点用逗号分隔 check-enabled: true # 启动时是否校验连接,建议开发期打开

这段配置的作用很直白:cluster-nodes是模板类拼接请求 URL 的基础地址,check-enabled控制项目启动时是否主动探测一次 ES 版本号。

check-enabled: true时,启动日志里会出现两行关键信息:ElasticSearch 服务连接成功ElasticSearch version: 7.x.x。如果 ES 没起来,日志会给出明确的失败警告,并且后续所有操作都会被拒——这是官方故意设计的"熔断",避免你在服务不可用时做无意义的写入。

核心实操:索引与增删改查

ES 相关代码集中在模板类里,先注入它:

配置文件中的映射关系由JeecgBaseConfig统一管理,相关源码见:jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/JeecgBaseConfig.java

@Resource private JeecgElasticsearchTemplate esTemplate;

1. 创建与检查索引

// 返回 true 表示索引创建成功;若索引已存在会打印警告并返回 false boolean created = esTemplate.createIndex("sys_user_index"); // 判断索引是否存在,常用于启动时的幂等初始化 boolean exists = esTemplate.indexExists("sys_user_index");

这段代码在做什么:createIndex底层发的是PUT /{indexName},靠返回体的acknowledged字段判断成败;已存在的索引不会抛异常,只会打一条警告,方便你重复执行初始化逻辑。

2. 写入与更新数据

JSONObject user = new JSONObject(); user.put("id", "u_1001"); user.put("username", "张三"); user.put("deptName", "研发部"); // typeName 只是分类标识,不是数据库表名,随便起但要统一 esTemplate.saveOrUpdate("sys_user_index", "docs", "u_1001", user);

这段代码在做什么:saveOrUpdate发的是PUT /{indexName}/{typeName}/{dataId}?refresh=wait_forwait_for表示写入后等待刷新完成再返回,保证你立刻能查到刚写入的数据,代价是写入吞吐略降,适合低频同步场景。

3. 按 ID 查询与删除

// 查不到时返回 null,而不是空对象 JSONObject doc = esTemplate.getDataById("sys_user_index", "docs", "u_1001"); // 删除成功返回 true,文档不存在返回 false boolean deleted = esTemplate.delete("sys_user_index", "docs", "u_1001");

这段代码在做什么:这两个方法分别对应GETDELETE请求,且都对 404 做了兜底处理,不会因为文档不存在而抛出异常打断你的业务逻辑。

4. 批量写入

JSONArray list = new JSONArray(); // 注意:数组里每个元素必须带 id 字段,模板会用它作为文档 _id list.add(userJson1); list.add(userJson2); esTemplate.saveBatch("sys_user_index", "docs", list);

这段代码在做什么:saveBatch走的是 ES 的_bulk批量接口,把所有数据拼成一条请求发送,网络往返从 N 次降到 1 次,是全量初始化索引时最该用的方法。

查询实操:从关键词到组合条件

查询是 ES 集成的重头戏,模板类给了一套"搭积木"式的构造方法。

全文检索,一段代码搞定

// 在 username、deptName 两个字段里搜"研发" JSONObject query = esTemplate.buildQueryString("username OR deptName", "研发"); // 组装完整请求:只返回指定字段,从第 0 条开始取 10 条 JSONObject body = esTemplate.buildQuery(null, query, 0, 10); JSONObject result = esTemplate.search("sys_user_index", "docs", body);

这段代码在做什么:buildQueryString生成query_string查询 DSL,支持ANDORNOT*通配符;buildQuery负责把查询条件、分页参数和字段过滤拼成完整请求体。

组合查询:过滤 + 范围 + 关键字

JSONArray must = new JSONArray(); must.add(esTemplate.buildQueryString("status", "1")); // 状态必须为 1 must.add(esTemplate.buildRangeQuery("createTime", "2024-01-01", "2024-12-31", true, true)); JSONObject query = esTemplate.buildBoolQuery(must, null, null); // bool 查询,must 条件全中

这段代码在做什么:buildBoolQuery对应 DSL 里的bool查询,三个参数分别是 must(必须满足)、mustNot(必须排除)、should(满足加分);buildRangeQuery生成范围条件,gte/lte是否包含边界由最后两个布尔参数控制。

如果查询条件更复杂,模板还提供了QueryStringBuilder这个链式工具,支持.and(...).or(...).not(...)连续拼接,适合在代码里动态组装复杂的检索表达式。

避坑指南:四个高频问题

问题一:启动日志提示连接失败,但 ES 明明在跑。

先确认cluster-nodes里没写http://前缀——模板类会自己拼http://,写了反而拼出http://http://。再看 ES 是否绑定了127.0.0.1,跨机器访问记得放开监听地址。

问题二:写入报failed to parse field ... of type [text]

这是模板类里最典型的一个坑。看saveOrUpdate源码会发现,它写入前会主动剔除两类字段:空值字段,以及包含[{前缀的上传控件字段(比如富文本、图片路径)。原因很简单:这类值在 ES 里无法解析成合法的 text。你自己封装写入方法时,也要记得过滤这两种字段,否则报错会非常隐蔽。

问题三:一次查询只能拿到 10000 条?

这是 ES 的分页硬上限,模板类里定义了ES_MAX_SIZE = 10000。业务上如果需要深分页,应该改用 search_after 或 scroll 方案,而不是盲目加大 size。

问题四:getIndexMapping返回 null。

模板对 ES 7.x 做了include_type_name=true的兼容处理,但如果你用的是 8.x 且未显式指定 type 名称,映射结构可能对不上。遇到这种情况,先手动调一次GET /{index}/{type}/_mapping对比返回结构,再决定是否需要自定义解析。

性能与维护:三句可落地的话

  • 全量同步用saveBatch,增量同步用saveOrUpdate,别反过来——逐条写 ES 的性能和批量写入差一个数量级。
  • 业务数据变更处加同步钩子,比如在 Service 层保存方法成功后调用 ES 更新,同时用定时任务兜底对账,保证 ES 与数据库最终一致。
  • 上线前用getIndices列一次索引清单,配合getIndexMappingFormat检查字段类型是否符合预期,避免"索引建了但字段全是 text 没法做精确过滤"这类隐形问题。

收尾:先跑通最小闭环

回到开头的用户表场景——两百万行数据、几秒的 LIKE 查询,在我们接入 ES 后变成了几十毫秒的关键词检索,命中率也因为分词和通配支持明显提升。但请记住,ES 不是银弹:它解决的是"检索"问题,事务、强一致、复杂关联查询仍然要交给数据库。

下一步行动很明确:先在测试环境搭一个最小 ES 实例,按上文完成配置、跑通"建索引 → 写入 → 查询"三步闭环,再逐步把真实业务表同步进来。整个过程不需要引入额外依赖,改的是配置和几段 Service 代码,风险完全可控。

【免费下载链接】jeecg-boot【低代码迈入v2.0时代,一句话即可生成整个系统】企业级AI低代码平台,一键生成前后端代码甚至整个系统。 AI Skills 一句话画流程、设计表单、生成报表、大屏。内置 AI应用平台涵盖:AI聊天、知识库、流程编排、MCP插件等,兼容主流大模型。引领AI低代码「Skills 生成 → 在线配置 → 代码生成 → 手工合并->AI修改」开发模式,解决 Java 项目 90% 重复工作,提高效率又不失灵活。项目地址: https://gitcode.com/GitHub_Trending/je/jeecg-boot

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考