Solr自定义SearchComponent子类中如何访问字段分析器并预处理查询
核心实现思路
你当前直接用原始查询参数构造TermQuery的写法,会因为q的大小写、分词格式和索引存储的term不匹配,导致查询结果不符合预期。正确的实现完全不需要手动实例化分析器,也不需要硬编码预处理规则,直接从Solr当前加载的索引配置中获取对应字段的分析器即可,步骤如下:
- 从当前请求关联的SolrCore实例中拿到最新的索引Schema配置
- 定位到
name字段对应的字段类型定义 - 取出该字段类型下查询阶段专用的分析器实例,和你在schema中配置的分词、过滤规则完全一致
- 用分析器对原始查询字符串做标准化处理,拿到和索引阶段规则统一的分词结果
- 基于处理后的分词结果构造查询对象,替代原来直接用原始参数构造的TermQuery
代码修改示例
把原来prepare方法中直接构造查询的部分替换成对应逻辑即可,完整可参考的实现如下:
public class TempComponent extends SearchComponent { private static final Logger LOG = LoggerFactory.getLogger(TempComponent.class); @Override public void prepare(ResponseBuilder rb) throws IOException { LOG.info("Temp Component ---"); SolrIndexSearcher indexSearcher = rb.req.getSearcher(); SolrParams params = rb.req.getParams(); String q = params.get(CommonParams.Q); // 跳过空查询的场景 if (StringUtils.isBlank(q)) { return; } // 获取当前节点加载的最新索引Schema IndexSchema schema = rb.req.getCore().getLatestSchema(); // 获取name字段的配置及对应FieldType SchemaField nameField = schema.getField("name"); FieldType nameType = nameField.getType(); // 取出查询阶段对应的分析器,和schema中配置的<analyzer type="query">完全对应 Analyzer queryAnalyzer = nameType.getQueryAnalyzer(); // 用分析器处理原始q值,收集标准化后的分词结果 List<String> normalizedTerms = new ArrayList<>(); try (TokenStream tokenStream = queryAnalyzer.tokenStream("name", q); CharTermAttribute termAttr = tokenStream.addAttribute(CharTermAttribute.class)) { tokenStream.reset(); while (tokenStream.incrementToken()) { normalizedTerms.add(termAttr.toString()); } tokenStream.end(); } // 基于处理后的结果构造合法的查询 Query targetQuery; if (normalizedTerms.isEmpty()) { // 处理后无有效分词的场景,直接匹配空结果,避免异常 targetQuery = new MatchNoDocsQuery(); } else if (normalizedTerms.size() == 1) { // 单分词场景,直接构造TermQuery和原逻辑对齐 targetQuery = new TermQuery(new Term("name", normalizedTerms.get(0))); } else { // 多分词场景,按业务规则拼接BooleanQuery,示例为必须命中所有分词 BooleanQuery.Builder queryBuilder = new BooleanQuery.Builder(); for (String term : normalizedTerms) { queryBuilder.add(new TermQuery(new Term("name", term)), BooleanClause.Occur.MUST); } targetQuery = queryBuilder.build(); } // 后续执行查询的逻辑和原代码保持一致 DocList matchDocs = indexSearcher.getDocList(targetQuery, null, null, 0, 100000, 10000); } @Override public void process(ResponseBuilder rb) throws IOException { } @Override public String getDescription() { return "Custom temp search component for name field match"; } }
关键注意事项
- 不要手动实例化分析器,也不要硬编码小写转换、空格切分这类预处理逻辑。所有处理逻辑直接从Schema加载的分析器获取,能保证和索引阶段、Solr内置查询的处理规则完全一致,后续调整分词配置、加过滤规则时不需要改自定义组件的代码
- 区分两个分析器的获取方法:*
getQueryAnalyzer()对应查询入参的处理规则,getIndexAnalyzer()*对应索引写入时的处理规则,处理入参q时必须用前者,哪怕当前两者配置完全相同,也不要混用 - 处理TokenStream的时候一定要用try-with-resources语法自动释放资源,避免Lucene层的内存泄漏
- 原代码中的日志初始化存在小问题:原来写的
LoggerFactory.getLogger(SearchComponent.class)会导致日志输出的类名是父类SearchComponent,排查问题时定位不到具体组件,改成获取当前类的日志实例即可 - 原代码的
getDescription()方法返回null是不规范的,按接口要求返回组件的简单描述即可,避免系统日志打印空指针相关的告警
内容的提问来源于stack exchange,提问作者Anton Klichkov
相关产品推荐
相关产品推荐

