本demo实现功能如下:
1、保存索引数据
2、根据ID获取索引数据
3、分页查询所有索引数据:精确匹配、时间范围查询、分词查询、高亮结果
4、利用滚动查询所有数据
pom.xml
org.springframework.boot
spring-boot-starter-data-elasticsearch
application.yml
spring:
data:
elasticsearch:
cluster-name: es-cluster
cluster-nodes: localhost:9300
Message.java
@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
@Document(indexName = "message")
public class Message {
@Id
@Field(type = FieldType.Integer)
private Integer id;
/**
* 类型
*/
@Field(type = FieldType.Keyword)
private String type;
/**
* 标题
*/
@Field(type = FieldType.Text, fielddata = true, searchAnalyzer = "ik_smart", analyzer = "ik_smart")
private String title;
/**
* 内容
*/
@Field(type = FieldType.Text, fielddata = true, searchAnalyzer = "ik_smart", analyzer = "ik_smart")
private String content;
/**
* 创建时间
*/
@Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis")
private Date createTime;
}
MessageRepository.java
@Repository
public interface MessageRepository extends ElasticsearchRepository {
}
MessageService.java
public interface MessageService {
/**
* 查询详情
* @param id
* @return
*/
Message detail(Integer id);
/**
* 分页查询
* @param ao
* @return
*/
Page list(MessageAo ao);
/**
* 查询所有
* @param ao
* @return
*/
List listAll(MessageAo ao);
}
MessageServiceImpl.java
@Service
public class MessageServiceImpl implements MessageService {
@Autowired
private MessageRepository platformBidRepository;
@Autowired
private ElasticSearchUtils elasticSearchUtils;
@Override
public Message detail(Integer id) {
return platformBidRepository.findById(id).orElse(new Message());
}
@Override
public Page list(MessageAo ao) {
SearchQuery searchQuery = getSearchQuery(ao);
// content返回高亮摘要
Page page = elasticSearchUtils.queryForPage(searchQuery, Message.class,
elasticSearchUtils.createSearchResultHighlightMapper("title","content"));
return page;
}
private SearchQuery getSearchQuery(MessageAo ao) {
// 分页参数
Pageable pageable = PageRequest.of(ao.getPage()-1, ao.getSize());
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
if (StringUtils.isNotBlank(ao.getType())) {
// termQuery查询中文,需要加上.keyword,不然查询不到
queryBuilder.must(QueryBuilders.termQuery("type.keyword", ao.getType()));
}
if (ao.getStartTime()!=null) {
queryBuilder.must(QueryBuilders.rangeQuery("createTime").gte(ao.getStartTime().getTime()));
}
if (ao.getEndTime()!=null) {
Date endTime = DateUtils.addDays(ao.getEndTime(), 1);
queryBuilder.must(QueryBuilders.rangeQuery("createTime").lte(endTime.getTime()));
}
if (StringUtils.isNotBlank(ao.getKeyword())) {
queryBuilder.must(QueryBuilders.matchQuery("title", ao.getKeyword()));
}
if (StringUtils.isNotBlank(ao.getKeyword())) {
queryBuilder.must(QueryBuilders.matchQuery("content", ao.getKeyword()));
}
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(queryBuilder)
// 高亮字段
.withHighlightFields(new HighlightBuilder.Field("title"), new HighlightBuilder.Field("content"))
// 返回部分字段
// .withFields("id","title","content","createTime")
.withPageable(pageable)
.build();
// 有关键字按关键词得分排序,没有则按创建日期排序
if (StringUtils.isBlank(ao.getKeyword())) {
searchQuery.addSort(new Sort(Sort.Direction.DESC, "createTime"));
}
return searchQuery;
}
@Override
public List listAll(MessageAo ao) {
// 构建查询语句
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
// TODO 补全查询条件
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(queryBuilder)
// 返回部分字段
// .withFields("id","title","content","createTime")
.withPageable(PageRequest.of(0, 10))
.build();
return elasticSearchUtils.queryAllByScroll(searchQuery, Message.class);
}
}
ElasticSearchUtils.java
@Component
@Slf4j
public class ElasticSearchUtils {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
@Autowired
private RestClientProperties restClientProperties;
@Autowired
private RestTemplate restTemplate;
/**
* 滚动获取所有数据
* @param searchQuery
* @param clazz
* @param
* @return
*/
public List queryAllByScroll(SearchQuery searchQuery, Class clazz) {
List result = new ArrayList<>(1000);
long scrollTimeInMillis = 10 * 1000;
ScrolledPage page = (ScrolledPage) elasticsearchTemplate.startScroll(scrollTimeInMillis, searchQuery, clazz);
result.addAll(page.getContent());
while (page.hasContent()) {
page = (ScrolledPage) elasticsearchTemplate.continueScroll(page.getScrollId(), scrollTimeInMillis, clazz);
result.addAll(page.getContent());
}
elasticsearchTemplate.clearScroll(page.getScrollId());
return result;
}
/**
* 分页查询
* @param searchQuery
* @param clazz
* @param
* @return
*/
public Page queryForPage(SearchQuery searchQuery, Class clazz) {
return elasticsearchTemplate.queryForPage(searchQuery, clazz);
}
public Page queryForPage(SearchQuery searchQuery, Class clazz, SearchResultMapper searchResultMapper) {
return elasticsearchTemplate.queryForPage(searchQuery, clazz,
searchResultMapper);
}
public List queryForList(SearchQuery searchQuery, Class clazz) {
return elasticsearchTemplate.queryForList(searchQuery, clazz);
}
/**
* 高亮结果
*/
public SearchResultMapper createSearchResultHighlightMapper(String... fields) {
return new SearchResultMapper() {
@Override
public AggregatedPage mapResults(SearchResponse response, Class clazz, Pageable pageable) {
List result = new ArrayList<>();
for (SearchHit searchHit : response.getHits()) {
if (response.getHits().getHits().length <= 0) {
break;
}
T t = JSON.parseObject(searchHit.getSourceAsString(), clazz);
// 内容高亮摘要
for (String field : fields) {
reWriteHighlightField(searchHit.getHighlightFields(), field, t);
}
result.add(t);
}
return new AggregatedPageImpl(result, pageable, response.getHits().getTotalHits(), response.getAggregations(), response.getScrollId());
}
};
}
/**
* 内容高亮摘要
*
* @param highlightFields
* @param field
* @param t
* @param
*/
private void reWriteHighlightField(Map highlightFields, String field, T t) {
// 内容高亮摘要
HighlightField highlightField = highlightFields.get(field);
if (highlightField != null) {
// 每段摘要之间...分隔
String value = StringUtils.join(Arrays.asList(highlightFields.get(field).fragments()), "...");
try {
FieldUtils.writeDeclaredField(t, field, value, true);
} catch (Exception e) {
log.error("【ES高亮】类{}字段{}写入异常", t.getClass(), field, e);
}
}
}
/**
* 获取分词列表
*
* @param keyword
* @return
*/
public List getAnalyzerResult(String keyword) {
String uris = restClientProperties.getUris().get(0);
Map request = new HashMap<>();
request.put("analyzer", "ik_smart");
request.put("text", keyword);
// 调用ES分词获取分词
String response = restTemplate.postForObject(uris + "/_analyze", request, String.class);
JSONArray tokens = JSON.parseObject(response).getJSONArray("tokens");
List result = new ArrayList<>();
for (int i = 0; i < tokens.size(); i++) {
JSONObject jsonObject = tokens.getJSONObject(i);
String token = jsonObject.getString("token");
result.add(token);
}
return result;
}
}
MessageRepositoryTest.java
public class MessageRepositoryTest extends BasicTest {
@Autowired
private MessageRepository platformBidRepository;
@Test
public void saveTest() {
for (int i = 0; i < 100; i++) {
Message bean = Message.builder()
.id(i)
.type("新闻事件")
.title("WHO:全球累计确诊新冠肺炎病例超11万 除中国外韩国最多")
.content("据美国消费者新闻与商业频道(CNBC)网站报道,世界卫生组织(WHO)官员9日表示,随着新型冠状病毒肺炎疫情在全球范围的迅速蔓延,出现全球性流行病的威胁正在上升。报道称,尽管新冠肺炎疫情在中国的蔓延速度正在放缓,但在世界其他地区正在加速扩散,并已传播到100多个国家,全球累计确诊病例逾11.1万。美国约翰斯·霍普金斯大学的数据显示,韩国是中国以外确诊病例最多的国家,近7500例;紧随其后的是意大利和伊朗,截至9日上午,这两个国家均有7000多确诊病例;在美国,截至美东时间3月8日晚7点,全美共报告新冠肺炎确诊病例572例,分布在至少30个州。")
.createTime(new Date())
.build();
platformBidRepository.save(bean);
}
}
}
MessageServiceImplTest.java
public class MessageServiceImplTest extends BasicTest {
@Autowired
private MessageServiceImpl messageService;
@Test
public void detail() throws Exception{
Message message = messageService.detail(1);
System.out.println(new ObjectMapper().writeValueAsString(message));
}
@Test
public void list() throws Exception{
MessageAo messageAo = MessageAo.builder()
.type("新闻事件")
.startTime(DateUtils.parseDate("2020-03-20","yyyy-MM-dd"))
.endTime(DateUtils.parseDate("2020-03-21","yyyy-MM-dd"))
.keyword("WHO")
.build();
Page messages = messageService.list(messageAo);
System.out.println(new ObjectMapper().writeValueAsString(messages));
}
@Test
public void listAll() throws Exception{
MessageAo messageAo = MessageAo.builder()
.build();
List messages = messageService.listAll(messageAo);
System.out.println(messages.size());
}
}
拓展部分,摘抄自官方文档
2.2.3. Using @Query Annotation
Example 54. Declare query at the method using the @Query annotation.
public interface BookRepository extends ElasticsearchRepository {
@Query("{"bool" : {"must" : {"field" : {"name" : "?0"}}}}")
Page findByName(String name,Pageable pageable);}
Table 2. Supported keywords inside method names |
||
Keyword |
Sample |
Elasticsearch Query String |
And |
findByNameAndPrice |
{"bool" : {"must" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}} |
Or |
findByNameOrPrice |
{"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}} |
Is |
findByName |
{"bool" : {"must" : {"field" : {"name" : "?"}}}} |
Not |
findByNameNot |
{"bool" : {"must_not" : {"field" : {"name" : "?"}}}} |
Between |
findByPriceBetween |
{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
LessThanEqual |
findByPriceLessThan |
{"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
GreaterThanEqual |
findByPriceGreaterThan |
{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}} |
Before |
findByPriceBefore |
{"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
After |
findByPriceAfter |
{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}} |
Like |
findByNameLike |
{"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}} |
StartingWith |
findByNameStartingWith |
{"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}} |
EndingWith |
findByNameEndingWith |
{"bool" : {"must" : {"field" : {"name" : {"query" : "*?","analyze_wildcard" : true}}}}} |
Contains/Containing |
findByNameContaining |
{"bool" : {"must" : {"field" : {"name" : {"query" : "?","analyze_wildcard" : true}}}}} |
In |
findByNameIn(Collection |
{"bool" : {"must" : {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}} |
NotIn |
findByNameNotIn(Collection |
{"bool" : {"must_not" : {"bool" : {"should" : {"field" : {"name" : "?"}}}}}} |
Near |
findByStoreNear |
Not Supported Yet ! |
True |
findByAvailableTrue |
{"bool" : {"must" : {"field" : {"available" : true}}}} |
False |
findByAvailableFalse |
{"bool" : {"must" : {"field" : {"available" : false}}}} |
OrderBy |
findByAvailableTrueOrderByNameDesc |
{"sort" : [{ "name" : {"order" : "desc"} }],"bool" : {"must" : {"field" : {"available" : true}}}} |
本文demo下载:https://download.csdn.net/download/kuyuyingzi/12261433
参考资料:
https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html
https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#reference
https://blog.csdn.net/lisongjia123/article/details/79041402
https://www.cnblogs.com/sxdcgaq8080/p/10411423.html
https://my.oschina.net/chuibilong/blog/1830382
https://blog.csdn.net/garvey_wong/article/details/86624016