介绍
Elasticsearch 是一个基于 Lucene 的搜索服务器。
ES 中的核心组件
- 索引:Index(数据库的 Database)
- 类型:type(数据库的 Table)
- 域:Field(数据库的 column)
- 映射:mapping(数据库的建表语句)
- 文档:documents(数据库的数据 rows)
Lucene 是 apache 软件基金会 jakarta 项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,但它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文)。Lucene 的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。Lucene 在全文检索领域是一个经典的祖先,现在很多检索引擎都是在其基础上创建的。
Lucene 是根据关健字来搜索的文本搜索工具,只能在某个网站内部搜索文本内容,不能跨网站搜索
Lucene 分词器
在对 Docuemnt 中的内容进行索引之前,需要使用分词器进行分词 ,分词的目的是为了搜索。分词的主要过程就是先分词后过滤。
- 分词:采集到的数据会存储到 document 对象的 Field 域中,分词就是将 Document 中 Field 的 value 值切分成一个一个的词。
- 过滤:包括去除标点符号过滤、去除停用词过滤(的、是、a、an、the 等)、大写转小写、词的形还原(复数形式转成单数形参、过去式转成现在式。。。)等。
如下是org.apache.lucene.analysis.standard.standardAnalyzer
的部分源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| @Override protected TokenStreamComponents createComponents(final String fieldName) { final Tokenizer src; if (getVersion().onOrAfter(Version.LUCENE_4_7_0)) { StandardTokenizer t = new StandardTokenizer(); t.setMaxTokenLength(maxTokenLength); src = t; } else { StandardTokenizer40 t = new StandardTokenizer40(); t.setMaxTokenLength(maxTokenLength); src = t; } TokenStream tok = new StandardFilter(src); tok = new LowerCaseFilter(tok); tok = new StopFilter(tok, stopwords); return new TokenStreamComponents(src, tok) { @Override protected void setReader(final Reader reader) throws IOException { int m = StandardAnalyzer.this.maxTokenLength; if (src instanceof StandardTokenizer) { ((StandardTokenizer)src).setMaxTokenLength(m); } else { ((StandardTokenizer40)src).setMaxTokenLength(m); } super.setReader(reader); } }; }
|
分词器的工作流程
1、标准过滤
标准过滤完成的内容是将空格,标点符号,无意义的词语去掉
2、大小写过滤
将大写全部转换为小写
3、停用词过滤
不允许作为搜索条件的词语
4、进行分词
Lucene 全文检索:Field 域
Field 属性
Field 是文档中的域,包括 Field 名和 Field 值两部分,一个文档可以包括多个 Field,Document 只是 Field 的一个承载体,Field 值即为要索引的内容,也是要搜索的内容。
是:作分词处理,即将 Field 值进行分词,分词的目的是为了索引。比如:商品名称、商品描述等,这些内容用户要输入关键字搜索,由于搜索的内容格式大、内容多需要分词后将语汇单元建立索引
否:不作分词处理比如:商品 id、订单号、身份证号等
是:进行索引。将 Field 分词后的词或整个 Field 值进行索引,存储到索引域,索引的目的是为了搜索。比如:商品名称、商品描述分析后进行索引,订单号、身份证号不用分词但也要索引,这些将来都要作为查询条件。
否:不索引。比如:图片路径、文件路径等,不用作为查询条件的不用索引。
是:将 Field 值存储在文档域中,存储在文档域中的 Field 才可以从 Document 中获取。比如:商品名称、订单号,凡是将来要从 Document 中获取的 Field 都要存储。
否:不存储 Field 值。比如:商品描述,内容较大不用存储。如果要向用户展示商品描述可以从系统的关系数据库中获取。
Field 常用类型
下边列出了开发中常用的 Filed 类型,注意 Field 的属性,根据需求选择:
Field 类 | 数据类型 | Analyzed 是否分词 | Indexed 是否索引 | Stored 是否存储 | 说明 |
---|
StringField(FieldName, FieldValue,Store.YES)) | 字符串 | N | Y | Y 或 N | 这个 Field 用来构建一个字符串 Field,但是不会进行分词,会将整个串存储在索引中,比如(订单号,身份证号等)是否存储在文档中用 Store.YES 或 Store.NO 决定 |
LongField(FieldName, FieldValue,Store.YES) | Long 型 | Y | Y | Y 或 N | 这个 Field 用来构建一个 Long 数字型 Field,进行分词和索引,比如(价格)是否存储在文档中用 Store.YES 或 Store.NO 决定 |
StoredField(FieldName, FieldValue) | 重载方法,支持多种类型 | N | N | Y | 这个 Field 用来构建不同类型 Field 不分析,不索引,但要 Field 存储在文档中(图片的 url 地址) |
TextField(FieldName, FieldValue, Store.NO)或 TextField(FieldName, reader) | 字符串或流 | Y | Y | Y 或 N | 如果是一个 Reader, lucene 猜测内容比较多,会采用 Unstored 的策略。(商品名字) |
基于 ES 的查询实现
依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <dependencies> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>transport</artifactId> <version>7.2.0</version> </dependency> <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>7.2.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies>
|
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
| import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHits; import org.elasticsearch.transport.client.PreBuiltTransportClient; import org.junit.Test;
import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Iterator;
public class DemoTest {
@Test public void getDemo() throws UnknownHostException { TransportClient client = new PreBuiltTransportClient(Settings.EMPTY); client.addTransportAddress( new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); GetResponse getResponse = client.prepareGet("person", "_doc", "Osx4FXsB6WFf174V5EJg").get(); String sourceAsString = getResponse.getSourceAsString(); System.out.println(sourceAsString); client.close(); }
@Test public void searchDemo() throws UnknownHostException { PreBuiltTransportClient client = new PreBuiltTransportClient(Settings.EMPTY); client.addTransportAddress( new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); SearchResponse searchResponse = client.prepareSearch("person") .setTypes("_doc") .setQuery(QueryBuilders.matchAllQuery()) .setSize(100) .get();
SearchHits hits = searchResponse.getHits(); Iterator<SearchHit> iterator = hits.iterator(); while (iterator.hasNext()) { SearchHit next = iterator.next(); String sourceAsString = next.getSourceAsString(); System.out.println(sourceAsString); } client.close(); }
@Test public void searchDemo1() throws UnknownHostException { PreBuiltTransportClient client = new PreBuiltTransportClient(Settings.EMPTY); client.addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); SearchResponse java0223 = client.prepareSearch("java0223") .setTypes("_doc") .setQuery(QueryBuilders.queryStringQuery("Lucene").field("content")) .setSize(10) .get();
SearchHits hits = java0223.getHits(); Iterator<SearchHit> iterator = hits.iterator(); while (iterator.hasNext()) { SearchHit next = iterator.next(); String sourceAsString = next.getSourceAsString(); System.out.println(sourceAsString); } client.close(); }
@Test public void searchDemo2() throws UnknownHostException { PreBuiltTransportClient client = new PreBuiltTransportClient(Settings.EMPTY); client.addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); SearchResponse java0223 = client.prepareSearch("java0223") .setTypes("_doc") .setQuery(QueryBuilders.termQuery("content","Lucene")) .setSize(10) .get();
SearchHits hits = java0223.getHits(); Iterator<SearchHit> iterator = hits.iterator(); while (iterator.hasNext()) { SearchHit next = iterator.next(); String sourceAsString = next.getSourceAsString(); System.out.println(sourceAsString); } client.close(); } }
|
总结
判断查询是否会进行分词,通过设置需要查询值为大写
进行查询,因为如果进行了分词,会将大写转小写,这样才能和索引域匹配到
- 字符串查询 matchQuery:对于输入的条件,会进行分词
- 字符串查询 queryStringQuery:对于输入的条件,会进行分词
- 词条查询 termQuery:对于输入的条件,不会进行分词
- 模糊查询 wildcardQuery:对于输入的条件,不会进行分词
- 相似度查询 fuzzyQuery:对于输入的条件,不会进行分词
高亮查询的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| public static void main(String[] args) throws UnknownHostException { PreBuiltTransportClient client = new PreBuiltTransportClient(Settings.EMPTY); client.addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300));
HighlightBuilder highlightBuilder = new HighlightBuilder(); highlightBuilder.field("content"); highlightBuilder.preTags("<font style='color:red'>"); highlightBuilder.postTags("</font>");
SearchResponse java0223 = client.prepareSearch("java0223") .setTypes("_doc") .setQuery(QueryBuilders.matchQuery("content", "Lucene")) .setSize(10) .highlighter(highlightBuilder) .get();
SearchHits hits = java0223.getHits(); Iterator<SearchHit> iterator = hits.iterator(); while (iterator.hasNext()) { SearchHit next = iterator.next(); String sourceAsString = next.getSourceAsString(); Article article = JSONObject.parseObject(sourceAsString, Article.class); HighlightField highlightField = next.getHighlightFields().get("content"); if (highlightField != null) { Text[] fragments = highlightField.getFragments(); if (fragments != null && fragments.length > 0) { String content = ""; for (Text fragment : fragments) { content += fragment; } article.setContent(content); } } System.out.println(article); } client.close(); }
|
这里建立了一个实体类用来反序列化,实体类的字段和索引相同
1 2 3 4 5 6 7 8
| public class Article implements Serializable {
private Long id; private String title; private String content;
}
|