分布式全文搜索引擎——Elasticsearch
1.安装Elasticsearch
a.下载:从官网下载 Elasticsearch,地址:https://www.elastic.co/cn/downloads/past-releases#elasticsearch
b.启动:解压后,在 bin 目录打开 elasticsearch.bat 启动服务
c.访问:在浏览器中访问 http://127.0.0.1:9200/ 检查是否安装成功
注意:端口9300:Java程序访问的端口;端口9200:浏览器访问的端口
Elasticsearch 的版本需要跟后面的 Spring-Data-Elasticsearch 的版本相符,不然会不兼容(Spring 5.1.1 对应 Elasticsearch 6.8.0)
2.安装Head管理Elasticsearch插件
a.安装nodejs:在nodejs官网下载msi,地址:https://nodejs.org/en/download/,安装后在CMD命令行输入 node -v 查看是否安装成功
b.安装grunt:在CMD命令行输入 npm install -g grunt-cli 安装grunt,安装后输入 grunt -version 查看是否安装成功
c.下载Head:在GitHub下载并解压 elasticsearch-head-master,地址:https://github.com/mobz/elasticsearch-head
d.修改Head配置:修改Head根目录下Gruntfile.js,添加:hostname:'*',
e.修改Elasticsearch配置:修改 Elasticsearch 的 config 目录下的 elasticsearch.yml 文件,添加如下配置。并重启
network.host: 127.0.0.1 # 解决elasticsearch-head 集群健康值: 未连接问题 http.cors.enabled: true http.cors.allow-origin: "*"
f.安装Head:在 elasticsearch-head-master 根目录下执行命令 npm install 进行安装
g.启动Head:同样在 elasticsearch-head-master 根目录下执行命令 grunt server(或者npm run start)
h.访问Head:在浏览器中访问 http://localhost:9100/ 即可进入管理界面
3.安装IK中文分词器
a.下载:在GitHub上下载对应版本的IK分词器zip包,地址:https://github.com/medcl/elasticsearch-analysis-ik/releases
b.注意:Elasticsearch和IK分词器必须版本统一
c.解压安装:解压到 elasticsearch 的 plugins 目录下,并改名为 ik。并重启 elasticsearch
4.使用 Spring-Data-Elasticsearch 操作 Elasticsearch
a.导入pom依赖
<properties> ...... <!-- spring --> <spring.version>5.1.1.RELEASE</spring.version> </properties> <dependencies> <!-- elasticsearch --> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-elasticsearch</artifactId> <version>3.1.1.RELEASE</version> </dependency> <!-- spring-5.X --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-oxm</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <!-- AOP-AspectJ spring-aop依赖 --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.6</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.6</version> </dependency> <!-- jackson-json spring-mvc依赖 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.4</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.4</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.9.4</version> </dependency> <!-- fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies>
b.创建配置文件
spring.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 自动扫描的包名 --> <context:component-scan base-package="com.wode" /> <!-- 开启AOP代理 --> <aop:aspectj-autoproxy proxy-target-class="true" /> <!--开启注解处理器 --> <context:annotation-config> </context:annotation-config> <context:property-placeholder location="classpath:elasticsearch.properties"/> <!-- Spring中引入其他配置文件 --> <import resource="classpath*:/spring-elasticsearch.xml" /> </beans>
spring-elasticsearch.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:elasticsearch="http://www.springframework.org/schema/data/elasticsearch" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/elasticsearch http://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch-1.0.xsd"> <!-- 搜索DAO 扫描 --> <elasticsearch:repositories base-package="com.wode.dao" /> <!-- 配置Client --> <elasticsearch:transport-client id="client" cluster-nodes="${elasticsearch.host}:${elasticsearch.port}"/> <!-- 配置搜索模板 --> <bean id="elasticsearchTemplate" class="org.springframework.data.elasticsearch.core.ElasticsearchTemplate"> <constructor-arg name="client" ref="client" /> </bean> </beans>
elasticsearch.properties
elasticsearch.host=localhost
elasticsearch.port=9300
b.创建BaseEntity基础实体类
public class BaseEntity { @Id private String id; public String getId() { return id; } public void setId(String id) { this.id = id; } }
c.创建Article和Author实体类
//index相当于数据库,type相当于表 @Document(indexName = "elasticsearch", type = "article") public class Article extends BaseEntity { private String title; private String content; //内嵌对象 @Field(type = FieldType.Nested) private Author author; public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public Author getAuthor() { return author; } public void setAuthor(Author author) { this.author = author; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } @Override public String toString() { return "Article: id[" + this.getId() + "], title[" + title + "], content[" + content + "], author[" + author + "]"; } }
public class Author { private String name; private int year; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getYear() { return year; } public void setYear(int year) { this.year = year; } @Override public String toString() { return "Author: name[" + name + "], year[" + year + "]"; } }
d.创建BaseDao基础DAO
public class BaseDao<T extends BaseEntity> { @Resource(name="elasticsearchTemplate") protected ElasticsearchTemplate esTemplate; protected Class<T> clazz; @PostConstruct private void construct(){ clazz = (Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]; } /** * 保存单个对象 * @param t 对象 * @return */ public boolean save(T t){ String id = t.getId(); if (id == null) { id = UUID.randomUUID().toString().replaceAll("-", ""); t.setId(id); } IndexQuery indexQuery = new IndexQueryBuilder().withId(id).withObject(t).build(); esTemplate.index(indexQuery); return true; } /** * 保存多个 * @param list 对象集合 * @return */ public boolean save(List<T> list){ List<IndexQuery> queries = new ArrayList<IndexQuery>(); for (T t : list) { String id = t.getId(); if (id == null) { id = UUID.randomUUID().toString().replaceAll("-", ""); t.setId(id); } IndexQuery indexQuery = new IndexQueryBuilder().withId(id).withObject(t).build(); queries.add(indexQuery); } esTemplate.bulkIndex(queries); return true; } /** * 根据ID删除 * @param id 对象ID * @return */ public boolean deleteById(String id){ esTemplate.delete(clazz, id); return true; } /** * 根据多个ID删除 * @param idList 对象ID集合 * @return */ public boolean deleteByIds(List<String> idList) { DeleteQuery deleteQuery = new DeleteQuery(); Map<String, Object> filter = new HashMap<>(); filter.put("ids", idList); BoolQueryBuilder queryBuilder = this.getQueryBuilder(filter, QueryBuilders.boolQuery()); deleteQuery.setQuery(queryBuilder);; esTemplate.delete(deleteQuery, clazz); return true; } /** * 根据过滤条件删除 * @param filter 过滤条件Map * @return */ public boolean delete(Map<String,Object> filter){ DeleteQuery deleteQuery = new DeleteQuery(); BoolQueryBuilder queryBuilder = this.getQueryBuilder(filter, QueryBuilders.boolQuery()); deleteQuery.setQuery(queryBuilder); esTemplate.delete(deleteQuery, clazz); return true; } /** * 根据条件查询集合 * @param filter 过滤条件Map * @param highFields 高亮字段 * @param sortField 排序字段 * @param order 正序倒序 * @return */ public List<T> queryList(Map<String, Object> filter, final List<String> highFields, String sortField, SortOrder order) { NativeSearchQueryBuilder searchBuilder = new NativeSearchQueryBuilder(); //-----------------------------高亮----------------------------- List<Field> fieldList = new ArrayList<>(); if(highFields != null) { for (String highField : highFields) { fieldList.add(new HighlightBuilder.Field(highField).preTags("<em>").postTags("</em>").fragmentSize(250)); } } searchBuilder.withHighlightFields(fieldList.toArray(new Field[fieldList.size()])); //-----------------------------排序----------------------------- if (sortField != null && order != null){ searchBuilder.withSort(new FieldSortBuilder(sortField + ".keyword").order(order)); } //-----------------------------过滤条件----------------------------- BoolQueryBuilder queryBuilder = this.getQueryBuilder(filter, QueryBuilders.boolQuery()); searchBuilder.withQuery(queryBuilder); //-----------------------------查询建立----------------------------- SearchQuery searchQuery = searchBuilder.build(); Page<T> page = null; //如果设置高亮 if (highFields != null && highFields.size() > 0) { page = esTemplate.queryForPage(searchQuery, clazz, new SearchResultMapper() { @Override public <T> AggregatedPage<T> mapResults(SearchResponse response,Class<T> clazz, Pageable pageable) { List<T> list = new ArrayList<T>(); for (SearchHit searchHit : response.getHits()) { if (response.getHits().getHits().length <= 0) { return null; } Map<String, Object> entityMap = searchHit.getSourceAsMap(); for (String highName : highFields) { String highValue = searchHit.getHighlightFields().get(highName).fragments()[0].toString(); entityMap.put(highName, highValue); } T t = JSONObject.parseObject(JSONObject.toJSONString(entityMap), clazz); list.add(t); } if (list.size() > 0) { return new AggregatedPageImpl<T>(list); } return null; } }); //如果不设置高亮 } else{ page = esTemplate.queryForPage(searchQuery, clazz); } List<T> resultList = new ArrayList<>(); if(page != null){ resultList = page.getContent(); } return resultList; } /** * 根据条件查询分页列表 * @param filter 过滤条件Map * @param highFields 高亮字段 * @param sortField 排序字段 * @param order 正序倒序 * @param pageIndex 当前页 * @param pageSize 分页大小 * @return */ public Map<String, Object> queryPage(Map<String,Object> filter, final List<String> highFields, String sortField, SortOrder order, int pageIndex, int pageSize) { NativeSearchQueryBuilder searchBuilder = new NativeSearchQueryBuilder(); //-----------------------------高亮----------------------------- List<Field> fieldList = new ArrayList<>(); if(highFields != null) { for (String highField : highFields) { fieldList.add(new HighlightBuilder.Field(highField).preTags("<em>").postTags("</em>").fragmentSize(250)); } } searchBuilder.withHighlightFields(fieldList.toArray(new Field[fieldList.size()])); //-----------------------------排序----------------------------- if (sortField != null && order != null){ searchBuilder.withSort(new FieldSortBuilder(sortField + ".keyword").order(order)); } //-----------------------------分页----------------------------- searchBuilder.withPageable(PageRequest.of(pageIndex, pageSize)); //-----------------------------过滤条件----------------------------- BoolQueryBuilder queryBuilder = this.getQueryBuilder(filter, QueryBuilders.boolQuery()); searchBuilder.withQuery(queryBuilder); //-----------------------------查询建立----------------------------- SearchQuery searchQuery = searchBuilder.build(); Page<T> page = null; //如果设置高亮 if (highFields != null && highFields.size() > 0) { page = esTemplate.queryForPage(searchQuery, clazz, new SearchResultMapper() { @Override public <T> AggregatedPage<T> mapResults(SearchResponse response,Class<T> clazz, Pageable pageable) { List<T> list = new ArrayList<T>(); for (SearchHit searchHit : response.getHits()) { if (response.getHits().getHits().length <= 0) { return null; } Map<String, Object> entityMap = searchHit.getSourceAsMap(); for (String highName : highFields) { String highValue = searchHit.getHighlightFields().get(highName).fragments()[0].toString(); entityMap.put(highName, highValue); } T t = JSONObject.parseObject(JSONObject.toJSONString(entityMap), clazz); list.add(t); } if (list.size() > 0) { return new AggregatedPageImpl<T>(list); } return null; } }); //如果不设置高亮 } else{ page = esTemplate.queryForPage(searchQuery, clazz); } List<T> resultList = new ArrayList<>(); if(page != null){ resultList = page.getContent(); } //-----------------------------查询数据总条数----------------------------- searchQuery = new NativeSearchQueryBuilder().withQuery(queryBuilder).build(); long totalCount = esTemplate.count(searchQuery, clazz); //组装结果 Map<String, Object> resultMap = new HashMap<>(); resultMap.put("pageIndex", pageIndex); resultMap.put("pageSize", pageSize); resultMap.put("data", resultList); resultMap.put("totalCount", totalCount); return resultMap; } /** * 根据过滤条件获取BoolQueryBuilder * @param filter 过滤条件 * @param query BoolQueryBuilder * @return */ protected BoolQueryBuilder getQueryBuilder(Map<String, Object> filter, BoolQueryBuilder query){ if(query == null){ query = QueryBuilders.boolQuery(); } if(CollectionUtils.isEmpty(filter)){ return query; } for(Map.Entry<String, Object> entry : filter.entrySet()){ String key = entry.getKey(); Object value = entry.getValue(); switch (key){ case "id": query = query.must(QueryBuilders.matchQuery("id.keyword", value)); break; case "ids": List<String> idList = (List<String>) value; for (String id : idList) { query.should(QueryBuilders.matchQuery("id.keyword", id)); } break; case "idNot": query = query.mustNot(QueryBuilders.matchQuery("id.keyword", value)); default: break; } } return query; } }
e.创建 ArticleDao 继承 BaseDao
@Repository public class ArticleDao extends BaseDao<Article> { @Override protected BoolQueryBuilder getQueryBuilder(Map<String, Object> filter, BoolQueryBuilder query) { query = super.getQueryBuilder(filter, query); for(Map.Entry<String, Object> entry : filter.entrySet()){ switch (entry.getKey()){ case "contentMatch": query = query.must(QueryBuilders.matchQuery("content", entry.getValue())); break; case "title": // ".keyword" 表示不进行分词(精准查询),解决 "title" 分词后 term 查不到数据的问题 query = query.must(QueryBuilders.matchQuery("title.keyword", entry.getValue())); break; case "authorYearBt": query = query.must(QueryBuilders.rangeQuery("author.year").gt(entry.getValue())); default: break; } } return super.getQueryBuilder(filter, query); } }
参考文档:https://blog.csdn.net/chen_2890/article/details/83757022
https://blog.csdn.net/lihuanlin93/article/details/83448967