文章目录
- 一、导入依赖和配置
- 1. 导入依赖
- 2. 配置连接
- 3. 解决启动Netty冲突
- 二、SpringData集成ES测试
- 1. 配置实体类与ES索引字段相对应
- `@Document(indexName = "discusspost", type = "_doc", shards = 5, replicas = 2)`
- `@Id`
- `@Field(type = FieldType.Integer)`
- `analyzer = "ik_max_word", searchAnalyzer = "ik_smart"`
- 2. dao层接口定义
- 3. 测试对ES服务器的CRUD基本操作
- 3.1 向ES库中添加数据
- `save(discussPostMapper.selectPost(241))`
- `saveAll(discussPostMapper.getPosts(101, 0, 100))`
- 3.2 修改ES库中的数据
- 3.3 删除数据
- `delete(discussPostMapper.selectDiscussPostById(231))`
- `deleteAll()`
- 3.4 搜索数据-ElasticsearchRepository
- `SearchQuery`
- `QueryBuilders.multiMatchQuery("互联网寒冬", "title", "content")`
- `SortBuilders.fieldSort("type").order(SortOrder.DESC)`
- `PageRequest.of(0, 10)`
- `withHighlightFields()`
- `new HighlightBuilder.Field("title").preTags("").postTags("")`
- `search(searchQuery)`
- `page.getTotalElements()`
- `page.getTotalPages()`
- `page.getNumber()`
- `page.getSize()`
- 测试结果:
- 3.5 搜索数据-ElasticsearchTemplate
- `new SearchResultMapper()`
- `HighlightField`
- `getFragments()`
- 测试结果
参考牛客网高级项目教程
狂神说Elasticsearch教程笔记
尚硅谷Elasticsearch教程笔记
一、导入依赖和配置
1. 导入依赖
- 版本在父依赖中已经指定,为6.3.0
<!-- 整合elasticsearch-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
2. 配置连接
- 指定集群名称
- TCP,客户端访问的端口是9300
# ElasticsearchProperties
spring.data.elasticsearch.cluster-name=community
#TCP访问端口
spring.data.elasticsearch.cluster-nodes=127.0.0.1:9300
3. 解决启动Netty冲突
-
如果不解决Netty冲突,启动ES时,会报错误
-
原因是ES启动时,会在Netty4Utils类的setAvailableProcessors方法中进行检查netty是否启动了,
- 如果已经启动了就会报错
-
解决策略是:将方法中留的set布尔变量设置为false,这样,就会中断检查
-
因此,在整个项目启动函数CommunityApplication中,设置初始化函数,将set的布尔值设置为false
java">@PostConstruct public void init() { // 解决netty启动冲突问题 // see Netty4Utils.setAvailableProcessors() System.setProperty("es.set.netty.runtime.available.processors", "false"); }
二、SpringData集成ES测试
1. 配置实体类与ES索引字段相对应
@Document(indexName = "discusspost", type = "_doc", shards = 5, replicas = 2)
- indexName 对应的索引名称-没有创建,就自动创建一个
- type 对应的类型,逐渐舍去
- shards,replicas ,设定的分片和副本
@Id
- 必须有 id,这里的 id 是全局唯一的标识,等同于 es 中的"/id"
@Field(type = FieldType.Integer)
- 字段的注解,指定字段的数据类型
analyzer = "ik_max_word", searchAnalyzer = "ik_smart"
- 指定储存分词器和搜索分词器的类型
java">@Document(indexName = "discusspost", type = "_doc", shards = 5, replicas = 2)
public class DiscussPost {
//必须有 id,这里的 id 是全局唯一的标识,等同于 es 中的"_id"
@Id
private int id;
/**
* type : 字段数据类型
* analyzer : 储存分词器类型:ik_max_word,最多分切
* searchAnalyzer: 搜索分词器类型:ik_smart,最少分切
* index : 是否索引(默认:true)
* Keyword : 短语,不进行分词
*/
@Field(type = FieldType.Integer)
private int userId;
@Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
private String title;
@Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
private String content;
@Field(type = FieldType.Integer)
private int type; // '0-普通; 1-置顶;
@Field(type = FieldType.Integer)
private int status; // '0-正常; 1-精华; 2-拉黑;
@Field(type = FieldType.Date)
private Date createTime;
@Field(type = FieldType.Integer)
private int commentCount; // 评论数
@Field(type = FieldType.Double)
private double score;
...
}
2. dao层接口定义
ElasticsearchRepository<DiscussPost, Integer>
- SpringData对ES封装的处理ES数据CRUD的接口,基本的操作都能完成
- 接口中指定要操作的实体类型,以及文档id即实体类型主键的数据类型
java">@Repository
public interface DiscussPostRepository extends ElasticsearchRepository<DiscussPost, Integer> {
}
3. 测试对ES服务器的CRUD基本操作
3.1 向ES库中添加数据
save(discussPostMapper.selectPost(241))
- 插入一条数据
java">/**
* 测试添加文档数据-一条数据
*/
@Test
public void testPUT() {
discussRepository.save(discussPostMapper.selectPost(241));
discussRepository.save(discussPostMapper.selectPost(242));
discussRepository.save(discussPostMapper.selectPost(243));
}
-
测试结果:
{ "discusspost": { "aliases": {}, "mappings": { "_doc": { "properties": { "commentCount": { "type": "integer" }, "content": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart" }, "createTime": { "type": "date" }, "id": { "type": "long" }, "score": { "type": "double" }, "status": { "type": "integer" }, "title": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart" }, "type": { "type": "integer" }, "userId": { "type": "integer" } } } }, "settings": { "index": { "refresh_interval": "1s", "number_of_shards": "5", "provided_name": "discusspost", "creation_date": "1648823705739", "store": { "type": "fs" }, "number_of_replicas": "2", "uuid": "u0zDxIykSbWUehgQXP3Wow", "version": { "created": "6040399" } } } } }
saveAll(discussPostMapper.getPosts(101, 0, 100))
- 一次性插入多条数据-从offset到limit的所有条数据
java">/**
* 测试添加多条数据-
*/
@Test
public void testPUTList() {
discussRepository.saveAll(discussPostMapper.getPosts(101, 0, 100));
discussRepository.saveAll(discussPostMapper.getPosts(102, 0, 100));
discussRepository.saveAll(discussPostMapper.getPosts(103, 0, 100));
discussRepository.saveAll(discussPostMapper.getPosts(111, 0, 100));
discussRepository.saveAll(discussPostMapper.getPosts(112, 0, 100));
discussRepository.saveAll(discussPostMapper.getPosts(131, 0, 100));
discussRepository.saveAll(discussPostMapper.getPosts(132, 0, 100));
discussRepository.saveAll(discussPostMapper.getPosts(133, 0, 100));
discussRepository.saveAll(discussPostMapper.getPosts(134, 0, 100));
}
-
结果测试
{ "took": 18, "timed_out": false, "_shards": { "total": 5, "successful": 5, "skipped": 0, "failed": 0 }, "hits": { "total": 141, // 一共查询到了141条数据 "max_score": 1, ... }
3.2 修改ES库中的数据
- 为保证数据同步,先在mysql中修改数据,再将数据重新保存覆盖进ES库中
java">/**
* 修改数据
* 为保证数据同步,先在mysql中修改数据,再将数据重新保存覆盖进ES库中
*/
@Test
public void testUpdate() {
DiscussPost post = discussPostMapper.selectPost(231);
post.setContent("我其实不是新人,哈哈哈");
// discussPostMapper.update(231, post.getContent());
discussRepository.save(post);
}
3.3 删除数据
delete(discussPostMapper.selectDiscussPostById(231))
- 删除一条数据
deleteAll()
- 删除所有数据
java"> /**
* 删除数据
*/
@Test
public void testDelete() {
// discussRepository.delete(discussPostMapper.selectDiscussPostById(231));
discussRepository.deleteAll();
}
-
测试结果:
{ "took": 0, "timed_out": false, "_shards": { "total": 5, "successful": 5, "skipped": 0, "failed": 0 }, "hits": { "total": 0, // 没有数据 "max_score": null, "hits": [] } }
3.4 搜索数据-ElasticsearchRepository
SearchQuery
- 查询体-可以构建查询的规则
NativeSearchQueryBuilder
- 查询的实现类
withQuery()
-
查询规则的制订方法,返回NativeSearchQueryBuilder实体,可以重复设置
QueryBuilders.multiMatchQuery("互联网寒冬", "title", "content")
- 定义组合查询的条件
SortBuilders.fieldSort("type").order(SortOrder.DESC)
- 定义组合查询后根据字段排序的规则
PageRequest.of(0, 10)
- 查询结果分页的设定:
- 当前页码-从0开始
- 每页显示多少条数据:10条
withHighlightFields()
- 设定高亮规则
new HighlightBuilder.Field("title").preTags("<em>").postTags("</em>")
- 对指定字段的前后添加标签,前端可以用css渲染高亮
search(searchQuery)
- 根据之前设定的查询规则,进行搜索
- 返回Page类型的文档集合
page.getTotalElements()
- 一共匹配的数据条数
page.getTotalPages()
- 一共多少页
page.getNumber()
- 当前页码,默认从0开始
page.getSize()
- 每页显示的数量
java"> /**
* 使用ElasticsearchRepository搜索匹配的数据
*/
@Test
public void testSearchByRepository() {
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.multiMatchQuery("互联网寒冬", "title", "content")) // 匹配
.withSort(SortBuilders.fieldSort("type").order(SortOrder.DESC)) // 排序规则
.withSort(SortBuilders.fieldSort("score").order(SortOrder.DESC))
.withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC))
.withPageable(PageRequest.of(0, 10)) // 分页规则:0页,每页10行
.withHighlightFields( // 设定高亮规则
new HighlightBuilder.Field("title").preTags("<em>").postTags("</em>"),
new HighlightBuilder.Field("content").preTags("<em>").postTags("</em>")
).build();
// elasticTemplate.queryForPage(searchQuery, class, SearchResultMapper)
// 底层获取得到了高亮显示的值, 但是没有返回.
Page<DiscussPost> page = discussRepository.search(searchQuery);
System.out.println(page.getTotalElements()); // 一共匹配的数据条数
System.out.println(page.getTotalPages()); // 一共多少页
System.out.println(page.getNumber()); // 当前页码,默认从0开始
System.out.println(page.getSize()); // 每页显示的数量
for (DiscussPost post : page) { // 当前页的10条数据
System.out.println(post);
}
}
}
测试结果:
3.5 搜索数据-ElasticsearchTemplate
- 使用ElasticsearchRepository,虽也查询到了高亮,但无法将高亮标签与原数据进行组合,因此,很难实现高亮效果
- 因此,使用ElasticsearchTemplate 原生的API进行组装处理
new SearchResultMapper()
-
创建新的结果映射规则,对结果自定义封装
将搜索的结果处理后用一个集合装着 获取的搜索结果是JSON格式,可以转为map,获取map中的每个字段 对于高亮显示的title、content字段,先获取到原始数据,因为有可能没有匹配到,防止空指针 最后再处理高亮部分的数据,如果有,就将原数据覆盖即可
HighlightField
- 接受高亮的字段
getFragments()
- getFragments()返回的是一个数组,因为匹配的不止一个,只高亮第一个匹配的结果即可
java">/**
* 使用ElasticsearchTemplate 搜索匹配的数据
*/
@Test
public void testSearchByTemplate() {
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.multiMatchQuery("互联网寒冬", "title", "content"))
.withSort(SortBuilders.fieldSort("type").order(SortOrder.DESC))
.withSort(SortBuilders.fieldSort("score").order(SortOrder.DESC))
.withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC))
.withPageable(PageRequest.of(0, 10))
.withHighlightFields(
new HighlightBuilder.Field("title").preTags("<em>").postTags("</em>"),
new HighlightBuilder.Field("content").preTags("<em>").postTags("</em>")
).build();
Page<DiscussPost> page = elasticTemplate.queryForPage(searchQuery, DiscussPost.class, new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> aClass, Pageable pageable) {
// 获取搜索的结果
SearchHits hits = response.getHits();
if (hits.getTotalHits() <= 0) {
return null;
}
// 将搜索的结果处理后用一个集合装着
// 获取的搜索结果是JSON格式,可以转为map,获取map中的每个字段
// 对于高亮显示的title、content字段,先获取到原始数据,因为有可能没有匹配到,防止空指针
// 最后再处理高亮部分的数据,如果有,就将原数据覆盖即可
List<DiscussPost> list = new ArrayList<>();
for (SearchHit hit : hits) {
DiscussPost post = new DiscussPost();
String id = hit.getSourceAsMap().get("id").toString();
post.setId(Integer.valueOf(id));
String userId = hit.getSourceAsMap().get("userId").toString();
post.setUserId(Integer.valueOf(userId));
String title = hit.getSourceAsMap().get("title").toString();
post.setTitle(title);
String content = hit.getSourceAsMap().get("content").toString();
post.setContent(content);
String status = hit.getSourceAsMap().get("status").toString();
post.setStatus(Integer.valueOf(status));
String createTime = hit.getSourceAsMap().get("createTime").toString();
post.setCreateTime(new Date(Long.valueOf(createTime)));
String commentCount = hit.getSourceAsMap().get("commentCount").toString();
post.setCommentCount(Integer.valueOf(commentCount));
// 处理高亮显示的结果
HighlightField titleField = hit.getHighlightFields().get("title");
if (titleField != null) { // 判空,没有匹配到,就不处理
// getFragments()返回的是一个数组,因为匹配的不止一个,只高亮第一个匹配的结果即可
post.setTitle(titleField.getFragments()[0].toString());
}
HighlightField contentField = hit.getHighlightFields().get("content");
if (contentField != null) {
post.setContent(contentField.getFragments()[0].toString());
}
list.add(post);
}
return new AggregatedPageImpl(list, pageable,
hits.getTotalHits(), response.getAggregations(), response.getScrollId(), hits.getMaxScore());
}
});
System.out.println(page.getTotalElements());
System.out.println(page.getTotalPages());
System.out.println(page.getNumber());
System.out.println(page.getSize());
for (DiscussPost post : page) {
System.out.println(post);
}
}