接入 SolrJ 8.0.0 时,旧教程中的 SolrServer 与手上依赖不一致。我从 JAR 和 IDE 类层次找到 SolrClient,再使用 HttpSolrClient.Builder 创建客户端。查当前依赖里的 API,比反复猜旧类名更直接。

创建与关闭客户端

SolrClient 是抽象入口,HttpSolrClient 提供 HTTP 实现。单次演示可以用 try-with-resources;长期服务通常复用客户端,并在组件销毁时关闭,不要每次查询都创建连接池。Solr 8.0 SolrJ 指南

下面代码面向已有的隔离演示 core,不是服务端安装教程。执行前需准备 id 为 uniqueKey、book_name 可写可查且可返回并支持高亮的 schema。不要把演示 URL 指到生产 core。

恢复单文档读写结构

import java.io.IOException;
import java.util.List;
import java.util.Map;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrInputDocument;

public class Solr8Examples {
    private static final String DEMO_ID = "demo-solrj8-001";

    private static SolrInputDocument document(String title) {
        SolrInputDocument doc = new SolrInputDocument();
        doc.setField("id", DEMO_ID);
        doc.setField("book_name", title);
        return doc;
    }

    public static void add(SolrClient client)
            throws IOException, SolrServerException {
        client.add(document("Solr 入门"));
        client.commit();
    }

    public static void replace(SolrClient client)
            throws IOException, SolrServerException {
        client.add(document("SolrJ 8 入门"));
        client.commit();
    }

    public static void deleteDemo(SolrClient client)
            throws IOException, SolrServerException {
        client.deleteById(DEMO_ID);
        client.commit();
    }

    public static void query(SolrClient client)
            throws IOException, SolrServerException {
        SolrQuery query = new SolrQuery("book_name:Solr");
        query.addFilterQuery("id:" + DEMO_ID);
        query.addSort("id", SolrQuery.ORDER.asc);
        query.setStart(0);
        query.setRows(5);
        query.setFields("id", "book_name");
        query.setHighlight(true);
        query.addHighlightField("book_name");
        query.setHighlightSimplePre("[[");
        query.setHighlightSimplePost("]]");

        QueryResponse response = client.query(query);
        Map<String, Map<String, List<String>>> highlighting =
                response.getHighlighting();
        for (SolrDocument doc : response.getResults()) {
            System.out.println(doc.getFieldValue("id"));
            System.out.println(doc.getFieldValue("book_name"));
            Map<String, List<String>> perDocument = highlighting == null
                    ? null : highlighting.get(String.valueOf(doc.getFieldValue("id")));
            List<String> fragments = perDocument == null
                    ? null : perDocument.get("book_name");
            if (fragments != null && !fragments.isEmpty()) {
                System.out.println(fragments.get(0));
            }
        }
    }

    public static void main(String[] args) throws Exception {
        // 只读入口;示意域名需换成自己的隔离测试 core。
        String url = "http://solr.example.invalid:8983/solr/demo_core";
        try (SolrClient client = new HttpSolrClient.Builder(url).build()) {
            query(client);
        }
    }
}

main 只展示查询;add、replace 和 deleteDemo 是分别展示的变更方法,不会随 main 自动执行。方法包含 commit 便于解释可见性,生产吞吐场景应另设计提交策略,而不是每条记录硬提交。

同 ID 写入的边界

在正常唯一键与覆盖写入语义下,再 add 相同 ID 是整份文档替换,不是“只更新传入字段”。因此 replace 示例提供该演示文档的完整业务字段;若希望局部修改,应使用相应原子更新语法并满足 schema 条件。Solr 更新处理器

deleteDemo 只针对固定演示 ID,不使用全量查询删除。数据写入成功、提交完成和搜索可见,也应分别理解。

查询和高亮常见误区

不要先 set q=:,再 setQuery 另一个条件,却以为两个 q 会自动 AND;后设置会替换前者。多个 fq 使用追加入口,见 过滤条件覆盖问题。

fl 选出的字段要与随后读取的一致。高亮可能缺失,不能直接连续 map.get(…).get(0)。示例输出的是文本标记,不是让页面无条件渲染搜索返回的 HTML。

这里明确使用 SolrJ 8.0 API,不凭旧教程差异推定 SolrServer 究竟在哪个版本移除,也不把版本数字相同当作客户端与服务端已完成兼容性验证。