1.SpringBoot整合Elasticsearch
1.1创建工程,引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
<!--<version>2.1.5.RELEASE</version>-->
<version>2.4.5</version>
</dependency>
网址:https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-elasticsearch/2.4.5
1.2.配置yml
spring:
data:
elasticsearch:
cluster-name: es6
cluster-nodes: 192.168.1.187:9300
#集群的时候
#cluster-nodes: 192.168.1.187:9300,192.168.1.188:9300,192.168.1.189:9300
1.3版本协调
目前springboot-data-elasticsearch中的es版本贴合为es-2.4.5,如此一来版本需要统一,把es进行降级。等springboot升级es版本后可以在对接最新版的7.4。
1.4 Netty issue fix
如果没有引起,暂时可不加
@Configuration
public class ESConfig {
/**
* 解决netty引起的issue
*/
@PostConstruct
void init() {
System.setProperty("es.set.netty.runtime.available.processors", "false");
}
}
1.5elasticsearch6.4.3配置文件
elasticsearch.yml
cluster.name: es6
node.name: node0
path.data: /usr/local/elasticsearch-6.4.3/data
path.logs: /usr/local/elasticsearch-6.4.3/logs
network.host: 0.0.0.0
./elasticsearch
如果出现如下错误:
那么需要切换到root用户下去修改配置:
vim /etc/security/limits.conf
* soft nofile 65536
* hard nofile 131072
* soft nproc 2048
* hard nproc 4096
vim /etc/sysctl.conf
vm.max_map_count=262146
#刷新一下,然后重启elasticsearch
sysctl -p
注意:中文分词器也需要去配置一下,下载自己使用的elasticsearch的对应版本
ik中文分词器下载网址:https://github.com/medcl/elasticsearch-analysis-ik/releases
2.ElasticsearchTemplate 模版的使用
@Autowired
private ElasticsearchTemplate esTemplate;
package com.test;
import com.imooc.Application;
import com.imooc.es.pojo.Stu;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class ESTest {
@Autowired
private ElasticsearchTemplate esTemplate;
/**
* 不建议使用 ElasticsearchTemplate 对索引进行管理(创建索引,更新映射,删除索引)
* 索引就像是数据库或者数据库中的表,我们平时是不会是通过java代码频繁的去创建修改删除数据库或者表的
* 我们只会针对数据做CRUD的操作
* 在es中也是同理,我们尽量使用 ElasticsearchTemplate 对文档数据做CRUD的操作
* 1. 属性(FieldType)类型不灵活
* 2. 主分片与副本分片数无法设置
*/
@Test
public void createIndexStu() {
Stu stu = new Stu();
stu.setStuId(1005L);
stu.setName("iron man");
stu.setAge(54);
stu.setMoney(1999.8f);
stu.setSign("I am iron man");
stu.setDescription("I have a iron army");
IndexQuery indexQuery = new IndexQueryBuilder().withObject(stu).build();
esTemplate.index(indexQuery);
}
@Test
public void deleteIndexStu() {
esTemplate.deleteIndex(Stu.class);
}
// ------------------------- 我是分割线 --------------------------------
@Test
public void updateStuDoc() {
Map<String, Object> sourceMap = new HashMap<>();
// sourceMap.put("sign", "I am not super man");
sourceMap.put("money", 99.8f);
// sourceMap.put("age", 33);
IndexRequest indexRequest = new IndexRequest();
indexRequest.source(sourceMap);
UpdateQuery updateQuery = new UpdateQueryBuilder()
.withClass(Stu.class)
.withId("1004")
.withIndexRequest(indexRequest)
.build();
// update stu set sign='abc',age=33,money=88.6 where docId='1002'
esTemplate.update(updateQuery);
}
//根据id查询记录
@Test
public void getStuDoc() {
GetQuery query = new GetQuery();
query.setId("1002");
Stu stu = esTemplate.queryForObject(query, Stu.class);
System.out.println(stu);
}
//根据id删除对应的数据
@Test
public void deleteStuDoc() {
esTemplate.delete(Stu.class, "1002");
}
// ------------------------- 我是分割线 --------------------------------
@Test
public void searchStuDoc() {
//分页
Pageable pageable = PageRequest.of(0, 2);
SearchQuery query = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.matchQuery("description", "save man"))
.withPageable(pageable)
.build();
AggregatedPage<Stu> pagedStu = esTemplate.queryForPage(query, Stu.class);
System.out.println("检索后的总分页数目为:" + pagedStu.getTotalPages());
List<Stu> stuList = pagedStu.getContent();
for (Stu s : stuList) {
System.out.println(s);
}
}
@Test
public void highlightStuDoc() {
String preTag = "<font color='red'>";
String postTag = "</font>";
Pageable pageable = PageRequest.of(0, 10);
SortBuilder sortBuilder = new FieldSortBuilder("money")
.order(SortOrder.DESC);
SortBuilder sortBuilderAge = new FieldSortBuilder("age")
.order(SortOrder.ASC);
SearchQuery query = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.matchQuery("description", "save man"))
.withHighlightFields(new HighlightBuilder.Field("description")
.preTags(preTag)
.postTags(postTag))
.withSort(sortBuilder)
.withSort(sortBuilderAge)
.withPageable(pageable)
.build();
AggregatedPage<Stu> pagedStu = esTemplate.queryForPage(query, Stu.class, new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
List<Stu> stuListHighlight = new ArrayList<>();
SearchHits hits = response.getHits();
for (SearchHit h : hits) {
HighlightField highlightField = h.getHighlightFields().get("description");
String description = highlightField.getFragments()[0].toString();
Object stuId = (Object)h.getSourceAsMap().get("stuId");
String name = (String)h.getSourceAsMap().get("name");
Integer age = (Integer)h.getSourceAsMap().get("age");
String sign = (String)h.getSourceAsMap().get("sign");
Object money = (Object)h.getSourceAsMap().get("money");
Stu stuHL = new Stu();
stuHL.setDescription(description);
stuHL.setStuId(Long.valueOf(stuId.toString()));
stuHL.setName(name);
stuHL.setAge(age);
stuHL.setSign(sign);
stuHL.setMoney(Float.valueOf(money.toString()));
stuListHighlight.add(stuHL);
}
if (stuListHighlight.size() > 0) {
return new AggregatedPageImpl<>((List<T>)stuListHighlight);
}
return null;
}
});
System.out.println("检索后的总分页数目为:" + pagedStu.getTotalPages());
List<Stu> stuList = pagedStu.getContent();
for (Stu s : stuList) {
System.out.println(s);
}
}
}
3.测试关键字查询
1.实体类
package com.meishi.business.model.dto;
import com.github.wxiaoqi.security.common.dto.BaseDTO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldIndex;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.util.Date;
/**
* @ClassName PltHelpCenterTitleDTO
* @description:
* @author: qsong
* @create: 2021-03-10 10:45
* @Version 1.0
**/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@Document(indexName = "business", type = "doc", createIndex = false)
public class PltHelpCenterSearchDTO {
private static final long serialVersionUID = 1L;
/**
* id
*/
@Id
@Field(store = true, type = FieldType.String, index = FieldIndex.not_analyzed)
private String id;
/**
* 文档标题
*/
@Field(store = true, type = FieldType.String, index = FieldIndex.analyzed,analyzer ="ik_max_word")
private String title;
/**
* 上级目录id
*/
@Field(store = true, type = FieldType.String, index = FieldIndex.not_analyzed)
private String directoryId;
/**
* 上级目录名称
*/
@Field(store = true, type = FieldType.String, index = FieldIndex.not_analyzed)
private String directoryName;
/**
* 目录名称
*/
@Field(store = true, type = FieldType.String, index = FieldIndex.not_analyzed)
private String name;
/**
* 文档内容(富文本)
*/
@Field(store = true, type = FieldType.String, index = FieldIndex.analyzed,analyzer ="ik_max_word")
private String content;
/**
* 所属应用()
*/
@Field(store = true, type = FieldType.String, index = FieldIndex.not_analyzed)
private String application;
/**
* 排序
*/
@Field(store = true, type = FieldType.Integer, index = FieldIndex.not_analyzed)
private Integer sort;
/**
* 状态 0 关闭 1启动
*/
@Field(store = true, type = FieldType.Integer, index = FieldIndex.not_analyzed)
private Integer status;
/**
* 操作人ID
*/
@Field(store = true, type = FieldType.String, index = FieldIndex.not_analyzed)
private String operatorId;
/**
* 操作员姓名
*/
@Field(store = true, type = FieldType.String, index = FieldIndex.not_analyzed)
private String operatorName;
/**
* 操作人编号
*/
@Field(store = true, type = FieldType.String, index = FieldIndex.not_analyzed)
private String operatorCode;
/**
* 文本类型 0菜单 1文本 type as titleType
*/
@Field(store = true, type = FieldType.Integer, index = FieldIndex.not_analyzed)
private Integer titleType;
/**
* 创建时间-开始时间
*/
@Field(store = true, type = FieldType.String, index = FieldIndex.not_analyzed)
private String startTime;
/**
* 创建时间-结束时间
*/
@Field(store = true, type = FieldType.String, index = FieldIndex.not_analyzed)
private String endTime;
/**
* 顶级菜单目录iD
*/
@Field(store = true, type = FieldType.String, index = FieldIndex.not_analyzed)
private String topDirectoryId;
/**
* 乐观锁
*/
@Field(store = true, type = FieldType.Integer, index = FieldIndex.not_analyzed)
private Integer revision;
/**
* 创建人ID
*/
@Field(store = true, type = FieldType.String, index = FieldIndex.not_analyzed)
private String crtUserId;
/**
* 创建人
*/
@Field(store = true, type = FieldType.String, index = FieldIndex.not_analyzed)
private String crtUserName;
/**
* 创建时间
*/
@Field(store = true, type = FieldType.Date, index = FieldIndex.not_analyzed)
private Date crtTime;
/**
* 更新人ID
*/
@Field(store = true, type = FieldType.String, index = FieldIndex.not_analyzed)
private String updUserId;
/**
* 更新人
*/
@Field(store = true, type = FieldType.String, index = FieldIndex.not_analyzed)
private String updUserName;
/**
* 更新时间
*/
@Field(store = true, type = FieldType.Date, index = FieldIndex.not_analyzed)
private Date updTime;
/**
* 部门ID
*/
@Field(store = true, type = FieldType.String, index = FieldIndex.not_analyzed)
private String departId;
/**
* 租户ID
*/
@Field(store = true, type = FieldType.String, index = FieldIndex.not_analyzed)
private String tenantId;
/**
* 逻辑删除(0正常、1已删除)
*/
@Field(store = true, type = FieldType.Integer, index = FieldIndex.not_analyzed)
private Integer deleted;
/**
* 备注
*/
@Field(store = true, type = FieldType.String, index = FieldIndex.not_analyzed)
private String note;
/**
* 业务员工号
*/
@Field(store = true, type = FieldType.String, index = FieldIndex.not_analyzed)
private String crtUserNo;
/**
* 部门名称
*/
@Field(store = true, type = FieldType.String, index = FieldIndex.not_analyzed)
private String departName;
/**
* 视频类别
*/
@Field(store = true, type = FieldType.String, index = FieldIndex.not_analyzed)
private String category;
/**
* url
*
*/
@Field(store = true, type = FieldType.String, index = FieldIndex.not_analyzed)
private String url;
/**
* 视频封面
*
*/
@Field(store = true, type = FieldType.String, index = FieldIndex.not_analyzed)
private String cover;
/**
* 帮助类型 0 是文档 1是视频
*
*/
@Field(store = true, type = FieldType.Integer, index = FieldIndex.not_analyzed)
private Integer helpType;
}
2.分页工具类
package com.meishi.business.utils;
import java.util.List;
/**
* @ClassName PagedGridResult
* @description: 用来返回分页Grid的数据格式
* @author: qsong
* @create: 2021-04-27 17:30
* @Version 1.0
**/
public class PagedGridResult {
private int page; // 当前页数
private int total; // 总页数
private long records; // 总记录数
private List<?> rows; // 每行显示的内容
public int getPage() {
return page;
}
public void setPage(int page) {
this.page = page;
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
public long getRecords() {
return records;
}
public void setRecords(long records) {
this.records = records;
}
public List<?> getRows() {
return rows;
}
public void setRows(List<?> rows) {
this.rows = rows;
}
}
3.接口请求,因为//elasticsearch是从0开始,所以分页需要减去1
@Autowired
private IPltHelpCenterTitleService pltHelpCenterTitleService;
@ApiOperation("关键字查询")
@GetMapping("/searchKeywords")
public Object searchKeywords(@RequestParam("keywords")String keywords,@RequestParam("page")Integer page,@RequestParam("limit") Integer limit){
if (StringUtils.isEmpty(keywords)){
return new ActionResult<>(StatusCode.SUCCESS,"");
}
//elasticsearch是从0开始
if (page == 1) {
page = 0;
} else {
page = page - 1;
}
PagedGridResult gridResult=pltHelpCenterTitleService.searhItems(keywords.trim(), page, limit);
return new ActionResult<>(StatusCode.SUCCESS,gridResult);
}
4.服务接口类
PagedGridResult searhItems(String keywords, Integer page, Integer limit);
5.服务实现类
package com.meishi.business.service.impl;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.meishi.business.mapper.PltHelpCenterTitleMapper;
import com.meishi.business.model.dto.PltHelpCenterSearchDTO;
import com.meishi.business.model.entity.PltHelpCenterTitle;
import com.meishi.business.service.IPltHelpCenterTitleService;
import com.meishi.business.utils.PagedGridResult;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.highlight.HighlightBuilder;
import org.elasticsearch.search.highlight.HighlightField;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* <p>
* 服务实现类
* </p>
*
* @author qsong
* @since 2021-03-10
*/
@Service
public class PltHelpCenterTitleServiceImpl extends ServiceImpl<PltHelpCenterTitleMapper, PltHelpCenterTitle> implements IPltHelpCenterTitleService {
@Resource
private ElasticsearchTemplate esTemplate;
@Override
public PagedGridResult searhItems(String keywords, Integer page, Integer limit) {
//全文检索的字段名称
StringBuffer[] keyFields =new StringBuffer[]{new StringBuffer("title"),new StringBuffer("content")};
Pageable pageable = new PageRequest(page, limit);
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
//过滤
//使用布尔组合查询
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//must 相当于and should相当与or must_not 取反
HighlightBuilder.Field[] fields=new HighlightBuilder.Field[keyFields.length];
for (int i = 0; i < keyFields.length; i++) {
boolQueryBuilder.should(QueryBuilders.matchQuery(keyFields[i].toString(),keywords));
HighlightBuilder.Field field = new HighlightBuilder.Field(keyFields[i].toString()).preTags("<font color='red'>").postTags("</font>");
fields[i]=field;
}
//g关键字高亮
nativeSearchQueryBuilder.withHighlightFields(fields);
nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
nativeSearchQueryBuilder.withPageable(pageable);
NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build();
AggregatedPage<PltHelpCenterSearchDTO> pagedItems = esTemplate.queryForPage(nativeSearchQuery, PltHelpCenterSearchDTO.class, new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> aClass, Pageable pageable) {
List<Map<String, Object>> list = new ArrayList<>();
//得到匹配的所有选项
SearchHits hits = response.getHits();
for (SearchHit hit : hits) {
if (hits.getHits().length <= 0) {
return null;
}
Map<String, Object> source = hit.getSource();
for (StringBuffer keyField : keyFields) {
String keyFieldValue = (String) source.get(keyField.toString());
HighlightField highlightField = hit.getHighlightFields().get(keyField.toString());
if (ObjectUtils.isEmpty(highlightField)) {
source.put(keyField.toString(), keyFieldValue);
} else {
String hcontent = highlightField.fragments()[0].toString();
source.put(keyField.toString(), hcontent);
}
}
list.add(source);
}
return new AggregatedPageImpl<>((List<T>) list, pageable, response.getHits().totalHits());
}
});
PagedGridResult gridResult = new PagedGridResult();
gridResult.setRows(pagedItems.getContent());
gridResult.setPage(page + 1);
gridResult.setTotal(pagedItems.getTotalPages());
gridResult.setRecords(pagedItems.getTotalElements());
return gridResult;
}
}
6.编写ElasticsearchRepository
接口
package com.meishi.business.repository;
import com.meishi.business.model.dto.PltHelpCenterSearchDTO;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
/**
* @ClassName EsHelpCenterRepository
* @description: es帮助中心接口
* @author: qsong
* @create: 2021-05-06 11:15
* @Version 1.0
**/
public interface EsHelpCenterRepository extends ElasticsearchRepository<PltHelpCenterSearchDTO,String> {
}
继承ElasticsearchRepository
接口,可以自定义查询方式或者可以实时更新或者删除ES中的数据
常用的方法:
//新增/修改
save()
//删除
delete()
//查询
search()