ElasticSearch(簡稱ES)是一個基於Lucene的搜索服務器。它提供了一個分佈式多用戶能力的全文搜索引擎,基於RESTful web接口。它也是目前最受歡迎的企業搜索引擎,其次是Apache Solr,也是基於Lucene。
安裝ES、分詞插件IK、圖形插件Head,這些博主這裏就不介紹了。
在講代碼之前,博主先說一下我對索引的理解,比如有個題目:背誦《憫農》,我們立刻就能背誦出來“鋤禾日當午……”,這是因爲我們的腦海裏面有“憫農=>鋤禾日當午”這個索引關係。但如果把題目改成:背誦含有“午”的詩句。那麼恐怕我們都要想一會才能背誦出來,這是因爲我們的腦海裏面沒有有“午=>鋤禾日當午”這個索引關係。如果我們能夠在腦海裏面建立“鋤=>憫農=>鋤禾日當午”、“禾=>憫農=>鋤禾日當午”、“日=>憫農=>鋤禾日當午”、“當=>憫農=>鋤禾日當午”、“午=>憫農=>鋤禾日當午”這些索引關係,那麼是不是無論題目怎麼問,我們都可以很快就能背誦出來了?
ES中建立索引的過程肯定沒這麼簡單,我也沒具體研究過。還有就是,我們可以把ES中的“索引 => 類型 => 文檔 => 字段”想象成關係型數據庫中的“數據庫 => 表 => 行 => 列”,這樣看代碼的時候就比較容易理解了。
先說明一下,SpringBoot繼承ES有兩種方式,第一種寫一個Repository實現類,繼承 ElasticsearchRepository ;第二種是用ElasticsearchTemplate,這個類是SpringBoot給封裝的,可以直接在 Service或者Controller層 中引入該 bean。這裏我們講第二種。
博主是做java後臺開發的,平時不怎麼寫Web端代碼,所以這篇博客的代碼也是博主照着“java知識分享網”上面的一個百度雲資源分享論壇來敲的,也算是“現炒現賣”,和大家一起學習。
第一步,要在pom.xml文件裏面添加ES依賴,如下。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
第二步,在application.yml文件中添加ES配置信息,如下。
spring:
data:
elasticsearch:
#集羣名稱,默認爲elasticsearh
cluster-name: elastic
#ES節點信息,逗號分隔
cluster-nodes: 10.4.1.88:9300,10.4.1.89:9300
第三步,寫一個ArticleInfo實體類,注意不要忘了寫@Document註解。
package com.zznode.entity.es;
import org.springframework.data.elasticsearch.annotations.Document;
import java.io.Serializable;
/**
* 用於ES的帖子實體類
*
* @author Administrator
*
*/
@Document(indexName = "test2", type = "my")
public class ArticleInfo implements Serializable {
private Long id;
private String name;
private String content;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
第四步,編寫ES的Service層代碼,如下,這是一個百度雲資源分享論壇的Service層代碼,其中,search方法是根據關鍵字分頁查詢ES中的信息,並高亮顯示;searchNoHighLight方法是根據關鍵字分頁查詢ES中的信息,不高亮顯示;searchCount方法是根據關鍵字,查詢ES中符合條件信息的條數;deleteIndex方法是根據文檔id來刪除ES中對應的信息。
package com.zznode.service.impl;
import com.zznode.entity.Article;
import com.zznode.entity.es.ArticleInfo;
import com.zznode.repository.ArticleRepository;
import com.zznode.service.ArticleService;
import com.zznode.util.StringUtil;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
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.DeleteQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 帖子Service實現類
*
* @author Administrator
*
*/
@Service("articleService")
public class ArticleServiceImpl implements ArticleService {
@Autowired
private ArticleRepository articleRepository;
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
/**
* 分頁查詢(帶排序)所有帖子內容
*
* @param page
* @param pageSize
* @param direction
* @param properties
* @return
*/
@Override
public List<Article> list(Integer page, Integer pageSize, Sort.Direction direction, String... properties) {
PageRequest pageRequest = PageRequest.of(page - 1, pageSize, direction, properties);
Page<Article> pageArticle = articleRepository.findAll(new Specification<Article>() {
@Override
public Predicate toPredicate(Root<Article> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
Predicate predicate = cb.conjunction();
predicate.getExpressions().add(cb.equal(root.get("state"), 1));
predicate.getExpressions().add(cb.equal(root.get("isIndex"), true));
return predicate;
}
}, pageRequest);
return pageArticle.getContent();
}
/**
* 根據條件分頁查詢(帶排序)所有帖子內容
*
* @param s_article
* @param page
* @param pageSize
* @param direction
* @param properties
* @return
*/
@Override
public List<Article> adminList(Article s_article, Integer page, Integer pageSize, Sort.Direction direction,
String... properties) {
PageRequest pageRequest = PageRequest.of(page - 1, pageSize, direction, properties);
Page<Article> pageArticle = articleRepository.findAll(new Specification<Article>() {
@Override
public Predicate toPredicate(Root<Article> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
Predicate predicate = cb.conjunction();
if (s_article != null) {
if (s_article.getId() != null) {
predicate.getExpressions().add(cb.equal(root.get("id"), s_article.getId()));
}
if (StringUtil.isNotEmpty(s_article.getName())) {
predicate.getExpressions()
.add(cb.like(root.get("name"), "%" + s_article.getName().trim() + "%"));
}
}
return predicate;
}
}, pageRequest);
return pageArticle.getContent();
}
/**
* 分頁查詢(帶排序)所有帖子的條數
*
* @return
*/
@Override
public Long getCount() {
return articleRepository.count(new Specification<Article>() {
@Override
public Predicate toPredicate(Root<Article> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
Predicate predicate = cb.conjunction();
predicate.getExpressions().add(cb.equal(root.get("state"), 1)); // 資源解析成功
predicate.getExpressions().add(cb.equal(root.get("isIndex"), true)); // es索引添加成功
return predicate;
}
});
}
/**
* 根據條件分頁查詢(帶排序)所有帖子的條數
*
* @param s_article
* @return
*/
@Override
public Long getAdminCount(Article s_article) {
return articleRepository.count(new Specification<Article>() {
@Override
public Predicate toPredicate(Root<Article> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
Predicate predicate = cb.conjunction();
if (s_article != null) {
if (s_article.getId() != null) {
predicate.getExpressions().add(cb.equal(root.get("id"), s_article.getId()));
}
if (StringUtil.isNotEmpty(s_article.getName())) {
predicate.getExpressions()
.add(cb.like(root.get("name"), "%" + s_article.getName().trim() + "%"));
}
}
return predicate;
}
});
}
/**
* 根據帖子id查詢帖子實體
*
* @param id
* @return
*/
@Override
public Article get(Integer id) {
return articleRepository.getOne(id);
}
/**
* 添加帖子
*
* @param article
*/
@Override
public void save(Article article) {
articleRepository.save(article);
}
/**
* 根據帖子id刪除帖子實體
*
* @param id
*/
@Override
public void delete(Integer id) {
Article article = articleRepository.getOne(id);
articleRepository.delete(article);
}
/**
* 根據關鍵字分頁查詢ES中所有帖子的信息,並高亮顯示
*
* @param page
* @param pageSize
* @param searchContent
* @return
*/
@Override
public List<ArticleInfo> search(Integer page, Integer pageSize, String searchContent) {
// 根據關鍵字分頁查詢ES中所有帖子的信息
PageRequest pageRequest = PageRequest.of(page - 1, pageSize);
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
.should(QueryBuilders.matchQuery("name", searchContent))
.should(QueryBuilders.matchQuery("content", searchContent));
// 對content和name中的關鍵字進行高亮顯示
NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder)
.withPageable(pageRequest).withIndices("test2")
.withHighlightFields(new HighlightBuilder.Field("content"), new HighlightBuilder.Field("name"))
.withHighlightBuilder(new HighlightBuilder().preTags("<font style='color:red'>").postTags("</font>"))
.build();
AggregatedPage<ArticleInfo> articleInfos = elasticsearchTemplate.queryForPage(nativeSearchQuery,
ArticleInfo.class, new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass,
Pageable pageable) {
ArrayList<ArticleInfo> articleInfos = new ArrayList<>();
SearchHits hits = searchResponse.getHits();
for (SearchHit hit : hits) {
if (hits.getHits().length <= 0) {
return null;
}
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
String name = (String) sourceAsMap.get("name");
String content = (String) sourceAsMap.get("content");
String id = (String) sourceAsMap.get("id");
ArticleInfo articleInfo = new ArticleInfo();
articleInfo.setId(Long.valueOf(id));
HighlightField contentHighlightField = hit.getHighlightFields().get("content");
if (contentHighlightField == null) {
articleInfo.setContent(content);
} else {
String highlightContent = hit.getHighlightFields().get("content").fragments()[0]
.toString();
// 過濾掉換行符、空格、下劃線
articleInfo.setContent(highlightContent.replaceAll("br", "").replaceAll(" ", "")
.replaceAll("_", ""));
}
HighlightField nameHighlightField = hit.getHighlightFields().get("name");
if (nameHighlightField == null) {
articleInfo.setName(name);
} else {
articleInfo.setName(hit.getHighlightFields().get("name").fragments()[0].toString());
}
articleInfos.add(articleInfo);
}
if (articleInfos.size() > 0) {
return new AggregatedPageImpl<T>((List<T>) articleInfos);
}
return null;
}
});
return articleInfos == null ? null : articleInfos.getContent();
}
/**
* 根據關鍵字分頁查詢ES中所有帖子的信息,不高亮顯示
*
* @param page
* @param pageSize
* @param searchContent
* @return
*/
@Override
public List<ArticleInfo> searchNoHighLight(Integer page, Integer pageSize, String searchContent) {
PageRequest pageRequest = PageRequest.of(page - 1, pageSize);
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
.should(QueryBuilders.matchQuery("name", searchContent))
.should(QueryBuilders.matchQuery("content", searchContent));
NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder)
.withPageable(pageRequest).withIndices("test2").build();
AggregatedPage<ArticleInfo> articleInfos = elasticsearchTemplate.queryForPage(nativeSearchQuery,
ArticleInfo.class, new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass,
Pageable pageable) {
ArrayList<ArticleInfo> articleInfos = new ArrayList<>();
SearchHits hits = searchResponse.getHits();
for (SearchHit hit : hits) {
if (hits.getHits().length <= 0) {
return null;
}
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
String name = (String) sourceAsMap.get("name");
String content = (String) sourceAsMap.get("content");
String id = (String) sourceAsMap.get("id");
ArticleInfo articleInfo = new ArticleInfo();
articleInfo.setId(Long.valueOf(id));
articleInfo.setName(name);
articleInfo.setContent(content);
articleInfos.add(articleInfo);
}
if (articleInfos.size() > 0) {
return new AggregatedPageImpl<T>((List<T>) articleInfos);
}
return null;
}
});
return articleInfos == null ? null : articleInfos.getContent();
}
/**
* 根據條件查詢ES中帖子的條數
*
* @param searchContent
* @return
*/
@Override
public Long searchCount(String searchContent) {
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
.should(QueryBuilders.matchQuery("name", searchContent))
.should(QueryBuilders.matchQuery("content", searchContent));
NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder)
.withIndices("test2").build();
return elasticsearchTemplate.count(nativeSearchQuery);
}
/**
* 根據帖子id刪除ES中的帖子記錄
*
* @param id
*/
@Override
public void deleteIndex(String id) {
QueryBuilder queryBuilder = QueryBuilders.termQuery("id", id);
DeleteQuery deleteQuery = new DeleteQuery();
deleteQuery.setIndex("test2");
deleteQuery.setType("my");
deleteQuery.setQuery(queryBuilder);
elasticsearchTemplate.delete(deleteQuery);
}
}
下面,我們看一個controller是怎麼使用的,先看一個search的例子,這個方法是根據關鍵字分頁查詢ES中的信息,並且關鍵字高亮顯示。
@RequestMapping("/search")
public ModelAndView search(@RequestParam(value = "q", required = false) String q,
@RequestParam(value = "page", required = false) String page) throws Exception {
ModelAndView mav = new ModelAndView();
if (StringUtil.isEmpty(q)) {
mav.setViewName("index");
mav.addObject("title", "首頁");
return mav;
}
int pageSize = 10;
if (StringUtil.isEmpty(page)) {
page = "1";
}
mav.addObject("q", q);
List<ArticleInfo> articleInfoList = articleService.search(Integer.parseInt(page), pageSize, q);
Long total = articleService.searchCount(q);
mav.addObject("articleInfoList", articleInfoList);
mav.addObject("total", total);
mav.addObject("title", q);
mav.addObject("modelName", q + " - 搜索結果");
mav.addObject("pageCode", PageUtil.genSearchPagination("/search", total, Integer.parseInt(page), pageSize, q));
mav.setViewName("result");
return mav;
}
再看一個delete的例子,在刪除帖子信息的時候,除了刪除數據庫中的記錄,還要刪除ES中的記錄。
@RequestMapping("/delete")
public Map<String, Object> delete(Integer id) throws Exception {
articleService.delete(id);
articleService.deleteIndex(String.valueOf(id));
Map<String, Object> resultMap = new HashMap<String, Object>();
resultMap.put("success", true);
return resultMap;
}
至此,ElasticSearch就介紹完了。