ElasticSearch
ElasticSearch安装
环境:> JDK1.8
Windows下安装
下载完成后直接解压使用
运行完成后,访问http://localhost:9200/测试
安装可视化插件
elasticsearch-head
,需要node环境
cnpm install
npm run start
端口为9100,会出现跨域问题,先解决跨域
在elasticsearch-7.10.1\config 下的elasticsearch.yml中加入
http.cors.enabled: true http.cors.allow-origin: "*"
然后重启 ElasticSearch服务,访问:http://localhost:9100/
成功!这个工具就当作数据展示工具!我们后面所有的查询使用Kibana
安装Kibana
下载地址:https://www.elastic.co/cn/downloads/past-releases/kibana-7-10-1
Kibana 版本要和 ElasticSearch 版本一致
IK分词器插件
如果要使用中文,建议使用IK分词器
什么是IK分词器?
分词:即把一段中文或者别的划分成一个个关键字,我们在搜索的时候会把自己的信息进行分词,会把数据库中或者索引库中的数据进行分词,然后进行一个匹配操作,默认的中文分词是将每个字看成一个词,所以我们需要安装中文分词器ik来解决这个问题。
IK提供了两个分词算法,ik_smart和ik_max_word,其中ik_smart为最少切分,ik_max_word为最细粒度划分!
安装
下载地址:https://github.com/medcl/elasticsearch-analysis-ik
下载完毕之后,放入到我们的ElasticSearch插件即可
然后重启ES
进行测试
ik_smart
ik_max_word
发现问题,我想要“在一直等待”,但是却被分开了,此时应该操作我们的字典
我们在ik的字典中,加入自己的字典,来使自己不愿意拆开的词保持完整。然后重启ES
可以看出我们的字典加载进来了。然后我们再次测试
我们要求不拆分的词,成功实现。以后,我们需要自己配置的分词,在ik分词的字典里加入即可。
ES Rest风格说明
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iPNlMjyn-1616998211748)(F:\Users\笔记\assets\1616829900471.png)]
Relational DB -> Databases -> Tables -> Rows -> Columns
Elasticsearch -> Indices -> Types -> Documents -> Fields
索引 类型 文件 字段
其中index、type是必须提供的。
id是可选的,不提供es会自动生成。
index、type将信息进行分层,利于管理。
index可以理解为数据库;type理解为数据表;id相当于数据库表中记录的主键,是唯一的。
关于索引的基本操作
测试
首先打开ES-head
创建一个索引
PUT /索引名/~类型名~(默认是_doc)/文档id
{
请求体
}
完成了自动增加了索引!
数据类型
keyword就是一个整体,分析时不被拆分的类型
使用GET获取索引
查看默认类型
如果自己的文档字段没有指定,那么es就会给我们默认配置字段类型!
更新
推荐使用post方式,put方式需要把原来的所有值都需要提交,容易出错。post只需提交要修改的字段
删除
通过DELETE命令来实现删除,根据你请求来判断是删除索引还是删除文档记录!
关于文档的基本操作
执行操作,
- 添加数据
PUT /nanfeng/user/1
{
"name": "南风",
"age": 22,
"desc": "一顿操作猛如虎",
"tags": ["技术宅","健身达人","直男"]
}
PUT /nanfeng/user/2
{
"name": "张三",
"age": 29,
"desc": "法外狂徒",
"tags": ["旅游","交友","渣男"]
}
PUT /nanfeng/user/3
{
"name": "李四",
"age": 29,
"desc": "mmp,不知道如何形容",
"tags": ["美女","大长腿","沙雕"]
}
PUT /nanfeng/user/4
{
"name": "南风学java",
"age": 29,
"desc": "mmp,不知道如何形容",
"tags": ["美女","大长腿","沙雕"]
}
- 查询数据
GET nanfeng/user/1
带条件的搜索
GET nanfeng/user/_search?q=name:南风
复杂的查询(排序、分页、高亮)
模糊匹配
hit: 索引和文档的信息、查询的总数、然后就是查询出来的具体的文档。我们可以通过_socre获取更加符合的结果_
查询后结果的过滤,只查询其中两个
GET nanfeng/user/_search
{
"query": {
"match": {
"name": "南风"
}
},
"_source": ["name","desc"]
}
排序
GET nanfeng/user/_search
{
"query": {
"match": {
"name": "南风"
}
},
"sort": [
{
"age": {
"order": "asc"
}
}
]
}
分页
GET nanfeng/user/_search
{
"query": {
"match": {
"name": "南风"
}
},
"sort": [
{
"age": {
"order": "asc"
}
}
],
"from": 0, //从第几个数据开始
"size": 1 //返回多少条数据
}
布尔查询
GET nanfeng/user/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "南风"
}
},
{
"match": {
"age": 22
}
}
]
}
}
}
must-(and)、should-(or)
过滤
GET nanfeng/user/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "南风"
}
},
{
"match": {
"age": 22
}
}
],
"filter": [
{
"range": {
"age": {
"gte": 10,
"lte": 25
}
}
}
]
}
}
}
匹配多个条件
GET nanfeng/user/_search
{
"query": {
"match": {
"tags": "男 技术"
}
}
}
//多个条件使用空格隔开,只要满足其中一个结果即可以被查出,这个时候可以通过分值判断。
精确查询
GET nanfeng/user/_search
{
"query": {
"term": {
"name" : "南"
}
}
}
高亮查询
GET nanfeng/user/_search
{
"query": {
"match": {
"name" : "南风"
}
},
"highlight": {
"pre_tags": "<p class='key' style='color:red'>",
"post_tags": "</p>",
"fields": {
"name":{}
}
}
}
集成SpringBoot
文档 :https://www.elastic.co/guide/en/elasticsearch/client/index.html
本文使用文档地址:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high.html
- 找到依赖
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.12.0</version>
</dependency>
- 找对象
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost", 9200, "http"),
new HttpHost("localhost", 9201, "http")));
client.close();
- 分析类中的方法
配置基本的项目
问题:一定要保证我们导入的依赖和我们的es版本一致
<!--自己定义es版本依赖,保证和本地一致-->
<elasticsearch.version>7.10.1</elasticsearch.version>
/**
* ElasticSearch 配置类
*
* @Author nanfeng
* @Date 2021/3/27 17:21
*/
@Configuration
public class ElasticSearchConfig {
@Bean
public RestHighLevelClient restHighLevelClient() {
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost", 9200, "http")));
return client;
}
}
实战
工具类
/**
* ES工具类
*
* Relational DB -> Databases -> Tables -> Rows -> Columns
* Elasticsearch -> Indices -> Types -> Documents -> Fields
* 索引 类型 文件 字段
* 其中index、type是必须提供的。
* id是可选的,不提供es会自动生成。
* index、type将信息进行分层,利于管理。
* index可以理解为数据库;type理解为数据表;id相当于数据库表中记录的主键,是唯一的。
*
* @Author nanfeng
* @Date 2021/3/27 21:00
*/
@Slf4j
@Component
public class ElasticSearchUtil {
@Autowired
private RestHighLevelClient restHighLevelClient;
/**
* 创建索引
*
* @param index 索引
* @return
*/
public boolean createIndex(String index) throws IOException {
if(isIndexExist(index)){
log.error("Index is exits!");
return false;
}
//1.创建索引请求
CreateIndexRequest request = new CreateIndexRequest(index);
//2.执行客户端请求
CreateIndexResponse response = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
log.info("创建索引{}成功",index);
return response.isAcknowledged();
}
/**
* 删除索引
*
* @param index
* @return
*/
public boolean deleteIndex(String index) throws IOException {
if(!isIndexExist(index)) {
log.error("Index is not exits!");
return false;
}
//删除索引请求
DeleteIndexRequest request = new DeleteIndexRequest(index);
//执行客户端请求
AcknowledgedResponse delete = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
log.info("删除索引{}成功",index);
return delete.isAcknowledged();
}
/**
* 判断索引是否存在
*
* @param index
* @return
*/
public boolean isIndexExist(String index) throws IOException {
GetIndexRequest request = new GetIndexRequest(index);
boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
return exists;
}
/**
* 数据添加,正定ID
*
* @param jsonObject 要增加的数据
* @param index 索引,类似数据库
* @param id 数据ID, 为null时es随机生成
* @return
*/
public String addData(JSONObject jsonObject, String index, String id) throws IOException {
//创建请求
IndexRequest request = new IndexRequest(index);
//规则 put /test_index/_doc/1
request.id(id);
request.timeout(TimeValue.timeValueSeconds(1));
//将数据放入请求 json
IndexRequest source = request.source(jsonObject, XContentType.JSON);
//客户端发送请求
IndexResponse response = restHighLevelClient.index(request, RequestOptions.DEFAULT);
log.info("添加数据成功 索引为: {}, response 状态: {}, id为: {}",index,response.status().getStatus(), response.getId());
return response.getId();
}
/**
* 批量添加数据
*
* @param index 索引,类似数据库
* @param list 要增加的数据
* @return
* @throws IOException
*/
public BulkResponse insertBatch(String index, List<T> list) throws IOException {
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.timeout("10s");
for (T item : list) {
IndexRequest indexRequest = new IndexRequest(index).source(JSON.toJSONString(item), XContentType.JSON);
bulkRequest.add(indexRequest);
}
BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
log.info("批量添加数据成功 索引为: {},response状态: {}", index, bulkResponse.status().getStatus());
return bulkResponse;
}
/**
* 批量删除数据
*
* @param index 索引,类似数据库
* @param idList 要删除的数据id
* @return
* @throws IOException
*/
public BulkResponse deleteBatch(String index, List<T> idList) throws IOException {
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.timeout("10s");
for (T id : idList) {
DeleteRequest deleteRequest = new DeleteRequest(index, id.toString());
bulkRequest.add(deleteRequest);
}
BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
log.info("批量删除数据成功 索引为: {},response状态: {}", index, bulkResponse.status().getStatus());
return bulkResponse;
}
/**
* 数据添加 随机id
*
* @param jsonObject 要增加的数据
* @param index 索引,类似数据库
* @return
*/
public String addData(JSONObject jsonObject, String index) throws IOException {
return addData(jsonObject, index, IdUtils.fastSimpleUUID());
}
/**
* 通过ID删除数据
*
* @param index 索引,类似数据库
* @param id 数据ID
*/
public void deleteDataById(String index, String id) throws IOException {
//删除请求
DeleteRequest request = new DeleteRequest(index, id);
//执行客户端请求
DeleteResponse delete = restHighLevelClient.delete(request, RequestOptions.DEFAULT);
log.info("索引为: {}, id为: {}删除数据成功",index, id);
}
/**
* 通过ID 更新数据
*
* @param object 要增加的数据
* @param index 索引,类似数据库
* @param id 数据ID
* @return
*/
public void updateDataById(Object object, String index, String id) throws IOException {
//更新请求
UpdateRequest update = new UpdateRequest(index, id);
update.timeout("1s");
update.doc(JSON.toJSONString(object), XContentType.JSON);
//执行更新请求
UpdateResponse update1 = restHighLevelClient.update(update, RequestOptions.DEFAULT);
log.info("索引为: {}, id为: {}, 更新数据成功",index, id);
}
/**
* 通过ID 更新数据,保证实时性
*
* @param object 要增加的数据
* @param index 索引,类似数据库
* @param id 数据ID
* @return
*/
public void updateDataByIdNoRealTime(Object object, String index, String id) throws IOException {
//更新请求
UpdateRequest update = new UpdateRequest(index, id);
//保证数据实时更新
update.setRefreshPolicy("wait_for");
update.timeout("1s");
update.doc(JSON.toJSONString(object), XContentType.JSON);
//执行更新请求
UpdateResponse update1 = restHighLevelClient.update(update, RequestOptions.DEFAULT);
log.info("索引为: {}, id为: {}, 更新数据成功",index, id);
}
/**
* 通过ID获取数据
*
* @param index 索引,类似数据库
* @param id 数据ID
* @param fields 需要显示的字段,逗号分隔(缺省为全部字段)
* @return
*/
public Map<String,Object> searchDataById(String index, String id, String fields) throws IOException {
GetRequest request = new GetRequest(index, id);
if (StringUtils.isNotEmpty(fields)){
//只查询特定字段。如果需要查询所有字段则不设置该项。
request.fetchSourceContext(new FetchSourceContext(true,fields.split(","), Strings.EMPTY_ARRAY));
}
GetResponse response = restHighLevelClient.get(request, RequestOptions.DEFAULT);
Map<String, Object> map = response.getSource();
//为返回的数据添加id
map.put("id",response.getId());
return map;
}
/**
* 通过ID判断文档是否存在
* @param index 索引,类似数据库
* @param id 数据ID
* @return
*/
public boolean existsById(String index,String id) throws IOException {
GetRequest request = new GetRequest(index, id);
//不获取返回的_source的上下文
request.fetchSourceContext(new FetchSourceContext(false));
request.storedFields("_none_");
return restHighLevelClient.exists(request, RequestOptions.DEFAULT);
}
/**
* 获取低水平客户端
* @return
*/
public RestClient getLowLevelClient() {
return restHighLevelClient.getLowLevelClient();
}
/**
* 高亮结果集 特殊处理
* map转对象 JSONObject.parseObject(JSONObject.toJSONString(map), Content.class)
* @param searchResponse
* @param highlightField
*/
public List<Map<String, Object>> setSearchResponse(SearchResponse searchResponse, String highlightField) {
//解析结果
ArrayList<Map<String,Object>> list = new ArrayList<>();
for (SearchHit hit : searchResponse.getHits().getHits()) {
Map<String, HighlightField> high = hit.getHighlightFields();
HighlightField title = high.get(highlightField);
hit.getSourceAsMap().put("id", hit.getId());
Map<String, Object> sourceAsMap = hit.getSourceAsMap();//原来的结果
//解析高亮字段,将原来的字段换为高亮字段
if (title!=null){
Text[] texts = title.fragments();
String nTitle="";
for (Text text : texts) {
nTitle+=text;
}
//替换
sourceAsMap.put(highlightField,nTitle);
}
list.add(sourceAsMap);
}
return list;
}
/**
* 查询并分页
* @param index 索引名称
* @param query 查询条件
* @param size 文档大小限制
* @param from 从第几页开始
* @param fields 需要显示的字段,逗号分隔(缺省为全部字段)
* @param sortField 排序字段
* @param highlightField 高亮字段
* @return
*/
public List<Map<String, Object>> searchListData(String index,
SearchSourceBuilder query,
Integer size,
Integer from,
String fields,
String sortField,
String highlightField) throws IOException {
SearchRequest request = new SearchRequest(index);
SearchSourceBuilder builder = query;
if (StringUtils.isNotEmpty(fields)){
//只查询特定字段。如果需要查询所有字段则不设置该项。
builder.fetchSource(new FetchSourceContext(true,fields.split(","), Strings.EMPTY_ARRAY));
}
from = from <= 0 ? 0 : from*size;
//设置确定结果要从哪个索引开始搜索的from选项,默认为0
builder.from(from);
builder.size(size);
if (StringUtils.isNotEmpty(sortField)){
//排序字段,注意如果proposal_no是text类型会默认带有keyword性质,需要拼接.keyword
builder.sort(sortField+".keyword", SortOrder.ASC);
}
//高亮
HighlightBuilder highlight = new HighlightBuilder();
highlight.field(highlightField);
//关闭多个高亮
highlight.requireFieldMatch(false);
highlight.preTags("<span style='color:red'>");
highlight.postTags("</span>");
builder.highlighter(highlight);
//不返回源数据。只有条数之类的数据。
//builder.fetchSource(false);
request.source(builder);
SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
log.error("=="+response.getHits().getTotalHits());
if (response.status().getStatus() == 200) {
// 解析对象
return setSearchResponse(response, highlightField);
}
return null;
}
}