ToB企服应用市场:ToB评测及商务社交产业平台

标题: Lucene轻量级搜索引擎,真的太强了!!!Solr 和 ES 都是基于它 [打印本页]

作者: 渣渣兔    时间: 2024-5-13 18:58
标题: Lucene轻量级搜索引擎,真的太强了!!!Solr 和 ES 都是基于它
一、底子知识

1、Lucene 是什么

Lucene 是一个本地全文搜索引擎,Solr 和 ElasticSearch 都是基于 Lucene 的封装
Lucene 适合那种轻量级的全文搜索,我就是服务器资源不够,假如上 ES 的话会很占用服务器资源,所有就选择了 Lucene 搜索引擎
2、倒排索引原理

全文搜索的原理是使用了倒排索引,那么什么是倒排索引呢?
3、与传统数据库对比

LuceneDB数据库表(table)索引(index)行(row)文档(document)列(column)字段(field)4、数据类型

常见的字段类型
Lucene 分词器是将文本内容分解成单独的词汇(term)的工具。Lucene 提供了多种分词器,此中一些常见的包括
但是对于中文分词器,我们一般常用第三方分词器IKAnalyzer,需要引入它的POM文件
二、最佳实践

1、依赖导入
  1. <lucene.version>8.1.1</lucene.version>
  2. <IKAnalyzer-lucene.version>8.0.0</IKAnalyzer-lucene.version>



  3. <dependency>
  4.     <groupId>org.apache.lucene</groupId>
  5.     <artifactId>lucene-core</artifactId>
  6.     <version>${lucene.version}</version>
  7. </dependency>


  8. <dependency>
  9.     <groupId>org.apache.lucene</groupId>
  10.     <artifactId>lucene-queryparser</artifactId>
  11.     <version>${lucene.version}</version>
  12. </dependency>


  13. <dependency>
  14.     <groupId>org.apache.lucene</groupId>
  15.     <artifactId>lucene-analyzers-common</artifactId>
  16.     <version>${lucene.version}</version>
  17. </dependency>


  18. <dependency>
  19.     <groupId>org.apache.lucene</groupId>
  20.     <artifactId>lucene-highlighter</artifactId>
  21.     <version>${lucene.version}</version>
  22. </dependency>


  23. <dependency>
  24.     <groupId>com.jianggujin</groupId>
  25.     <artifactId>IKAnalyzer-lucene</artifactId>
  26.     <version>${IKAnalyzer-lucene.version}</version>
  27. </dependency>
复制代码
2、创建索引

  1. /**
  2.  * @author: sunhhw
  3.  * @date: 2023/12/25 17:39
  4.  * @description: 定义文章文档字段和索引名称
  5.  */
  6. public interface IArticleIndex {

  7.     /**
  8.      * 索引名称
  9.      */
  10.     String INDEX_NAME = "article";

  11.     // --------------------- 文档字段 ---------------------
  12.     String COLUMN_ID = "id";
  13.     String COLUMN_ARTICLE_NAME = "articleName";
  14.     String COLUMN_COVER = "cover";
  15.     String COLUMN_SUMMARY = "summary";
  16.     String COLUMN_CONTENT = "content";
  17.     String COLUMN_CREATE_TIME = "createTime";
  18. }
复制代码
  1. /**
  2.  * 创建索引并设置数据
  3.  *
  4.  * @param indexName 索引地址
  5.  */
  6. public void addDocument(String indexName, List<Document> documentList) {
  7.     // 配置索引的位置 例如:indexDir = /app/blog/index/article
  8.     String indexDir = luceneProperties.getIndexDir() + File.separator + indexName;
  9.     try {
  10.         File file = new File(indexDir);
  11.         // 若不存在,则创建目录
  12.         if (!file.exists()) {
  13.             FileUtils.forceMkdir(file);
  14.         }
  15.         // 读取索引目录
  16.         Directory directory = FSDirectory.open(Paths.get(indexDir));
  17.         // 中文分析器
  18.         Analyzer analyzer = new IKAnalyzer();
  19.         // 索引写出工具的配置对象
  20.         IndexWriterConfig conf = new IndexWriterConfig(analyzer);
  21.         // 创建索引
  22.         IndexWriter indexWriter = new IndexWriter(directory, conf);
  23.         long count = indexWriter.addDocuments(documentList);
  24.         log.info("[批量添加索引库]总数量:{}", documentList.size());
  25.         // 提交记录
  26.         indexWriter.commit();
  27.         // 关闭close
  28.         indexWriter.close();
  29.     } catch (Exception e) {
  30.         log.error("[创建索引失败]indexDir:{}", indexDir, e);
  31.         throw new UtilsException("创建索引失败", e);
  32.     }
  33. }
复制代码
  1. @Test
  2. public void create_index_test() {
  3.     ArticlePO articlePO = new ArticlePO();
  4.     articlePO.setArticleName("git的基本使用" + i);
  5.     articlePO.setContent("这里是git的基本是用的内容" + i);
  6.     articlePO.setSummary("测试摘要" + i);
  7.     articlePO.setId(String.valueOf(i));
  8.     articlePO.setCreateTime(LocalDateTime.now());
  9.     Document document = buildDocument(articlePO);
  10.     LuceneUtils.X.addDocument(IArticleIndex.INDEX_NAME, document);
  11. }

  12. private Document buildDocument(ArticlePO articlePO) {
  13.     Document document = new Document();
  14.     LocalDateTime createTime = articlePO.getCreateTime();
  15.     String format = LocalDateTimeUtil.format(createTime, DateTimeFormatter.ISO_LOCAL_DATE);

  16.     // 因为ID不需要分词,使用StringField字段
  17.     document.add(new StringField(IArticleIndex.COLUMN_ID, articlePO.getId() == null ? "" : articlePO.getId(), Field.Store.YES));
  18.     // 文章标题articleName需要搜索,所以要分词保存
  19.     document.add(new TextField(IArticleIndex.COLUMN_ARTICLE_NAME, articlePO.getArticleName() == null ? "" : articlePO.getArticleName(), Field.Store.YES));
  20.     // 文章摘要summary需要搜索,所以要分词保存
  21.     document.add(new TextField(IArticleIndex.COLUMN_SUMMARY, articlePO.getSummary() == null ? "" : articlePO.getSummary(), Field.Store.YES));
  22.      // 文章内容content需要搜索,所以要分词保存
  23.     document.add(new TextField(IArticleIndex.COLUMN_CONTENT, articlePO.getContent() == null ? "" : articlePO.getContent(), Field.Store.YES));
  24.     // 文章封面不需要分词,但是需要被搜索出来展示
  25.     document.add(new StoredField(IArticleIndex.COLUMN_COVER, articlePO.getCover() == null ? "" : articlePO.getCover()));
  26.     // 创建时间不需要分词,仅需要展示
  27.     document.add(new StringField(IArticleIndex.COLUMN_CREATE_TIME, format, Field.Store.YES));
  28.     return document;
  29. }
复制代码
3、更新文档

  1. /**
  2.  * 更新文档
  3.  *
  4.  * @param indexName 索引地址
  5.  * @param document  文档
  6.  * @param condition 更新条件
  7.  */
  8. public void updateDocument(String indexName, Document document, Term condition) {
  9.     String indexDir = luceneProperties.getIndexDir() + File.separator + indexName;
  10.     try {
  11.         // 读取索引目录
  12.         Directory directory = FSDirectory.open(Paths.get(indexDir));
  13.         // 中文分析器
  14.         Analyzer analyzer = new IKAnalyzer();
  15.         // 索引写出工具的配置对象
  16.         IndexWriterConfig conf = new IndexWriterConfig(analyzer);
  17.         // 创建索引
  18.         IndexWriter indexWriter = new IndexWriter(directory, conf);
  19.         indexWriter.updateDocument(condition, document);
  20.         indexWriter.commit();
  21.         indexWriter.close();
  22.     } catch (Exception e) {
  23.         log.error("[更新文档失败]indexDir:{},document:{},condition:{}", indexDir, document, condition, e);
  24.         throw new ServiceException();
  25.     }
  26. }
复制代码
  1. @Test
  2. public void update_document_test() {
  3.     ArticlePO articlePO = new ArticlePO();
  4.     articlePO.setArticleName("git的基本使用=编辑");
  5.     articlePO.setContent("这里是git的基本是用的内容=编辑");
  6.     articlePO.setSummary("测试摘要=编辑");
  7.     articlePO.setId("2");
  8.     articlePO.setCreateTime(LocalDateTime.now());
  9.     Document document = buildDocument(articlePO);
  10.     LuceneUtils.X.updateDocument(IArticleIndex.INDEX_NAME, document, new Term("id", "2"));
  11. }
复制代码
4、删除文档

  1. /**
  2. * 删除文档
  3. *
  4. * @param indexName 索引名称
  5. * @param condition 更新条件
  6. */
  7. public void deleteDocument(String indexName, Term condition) {
  8.   String indexDir = luceneProperties.getIndexDir() + File.separator + indexName;
  9.   try {
  10.       // 读取索引目录
  11.       Directory directory = FSDirectory.open(Paths.get(indexDir));
  12.       // 索引写出工具的配置对象
  13.       IndexWriterConfig conf = new IndexWriterConfig();
  14.       // 创建索引
  15.       IndexWriter indexWriter = new IndexWriter(directory, conf);

  16.       indexWriter.deleteDocuments(condition);
  17.       indexWriter.commit();
  18.       indexWriter.close();
  19.   } catch (Exception e) {
  20.       log.error("[删除文档失败]indexDir:{},condition:{}", indexDir, condition, e);
  21.       throw new ServiceException();
  22.   }
  23. }
复制代码
  1. @Test
  2. public void delete_document_test() {
  3.     LuceneUtils.X.deleteDocument(IArticleIndex.INDEX_NAME, new Term(IArticleIndex.COLUMN_ID, "1"));
  4. }
复制代码
5、删除索引

把改索引下的数据全部清空
  1. /**
  2. * 删除索引
  3. *
  4. * @param indexName 索引地址
  5. */
  6. public void deleteIndex(String indexName) {
  7.   String indexDir = luceneProperties.getIndexDir() + File.separator + indexName;
  8.   try {
  9.       // 读取索引目录
  10.       Directory directory = FSDirectory.open(Paths.get(indexDir));
  11.       // 索引写出工具的配置对象
  12.       IndexWriterConfig conf = new IndexWriterConfig();
  13.       // 创建索引
  14.       IndexWriter indexWriter = new IndexWriter(directory, conf);
  15.       indexWriter.deleteAll();
  16.       indexWriter.commit();
  17.       indexWriter.close();
  18.   } catch (Exception e) {
  19.       log.error("[删除索引失败]indexDir:{}", indexDir, e);
  20.       throw new ServiceException();
  21.   }
  22. }
复制代码
6、普通查询

  1. Term term = new Term("title", "lucene");
  2. Query query = new TermQuery(term);
复制代码
上述代码表现通过精确匹配字段"title"中包含"lucene"的文档。
  1. PhraseQuery.Builder builder = new PhraseQuery.Builder();
  2. builder.add(new Term("content", "open"));
  3. builder.add(new Term("content", "source"));
  4. PhraseQuery query = builder.build();
复制代码
上述代码表现在字段"content"中查找包含"open source"短语的文档
  1. TermQuery query1 = new TermQuery(new Term("title", "lucene"));
  2. TermQuery query2 = new TermQuery(new Term("author", "john"));
  3. BooleanQuery.Builder builder = new BooleanQuery.Builder();
  4. builder.add(query1, BooleanClause.Occur.MUST);
  5. builder.add(query2, BooleanClause.Occur.MUST);
  6. BooleanQuery query = builder.build();
复制代码
上述代码表现使用布尔查询同时满足"title"字段包含"lucene"和"author"字段包含"john"的文档。
  1. WildcardQuery示例:
  2. java
  3. WildcardQuery query = new WildcardQuery(new Term("title", "lu*n?e"));
复制代码
上述代码表现使用通配符查询匹配"title"字段中以"lu"开头,且第三个字符为任意字母,最后一个字符为"e"的词项
  1. String[] fields = {"title", "content", "author"};
  2. Analyzer analyzer = new StandardAnalyzer();

  3. MultiFieldQueryParser parser = new MultiFieldQueryParser(fields, analyzer);
  4. Query query = parser.parse("lucene search");
复制代码
a. 在"title", "content", "author"三个字段中搜索关键字"lucene search"的文本数据b. MultiFieldQueryParser 默认使用 OR 运算符将多个字段的查询结果归并,即只要在任意一个字段中匹配乐成即
可以使用MultiFieldQueryParser查询来封装一个简单的搜索工具类,这个较为常用
[code]/**
* 关键词搜索
*
* @param indexName 索引目录
* @param keyword   查询关键词
* @param columns   被搜索的字段
* @param current   当前页
* @param size      每页数据量
* @return
*/
public List search(String indexName, String keyword, String[] columns, int current, int size) {
  String indexDir = luceneProperties.getIndexDir() + File.separator + indexName;
  try {
      // 打开索引目录
      Directory directory = FSDirectory.open(Paths.get(indexDir));
      IndexReader reader = DirectoryReader.open(directory);
      IndexSearcher searcher = new IndexSearcher(reader);
      // 中文分析器
      Analyzer analyzer = new IKAnalyzer();
      // 查询解析器
      QueryParser parser = new MultiFieldQueryParser(columns, analyzer);
      // 解析查询关键字
      Query query = parser.parse(keyword);
      // 执行搜索,获取匹配查询的前 limit 条结果。
      int limit = current * size;
      // 搜索前 limit 条结果
      TopDocs topDocs = searcher.search(query, limit); 
      // 匹配的文档数组
      ScoreDoc[] scoreDocs = topDocs.scoreDocs;
      // 计算分页的起始 - 竣事位置
      int start = (current - 1) * size;
      int end = Math.min(start + size, scoreDocs.length);
      // 返回指定页码的文档
      List documents = new ArrayList();
      for (int i = start; i 




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4