最近学了一下Elasticsearch
、把网站的搜索功能实现了一下、然后准备把api的调用方式给小结一下。
创建一个SpringBoot项目
POM依赖
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
</dependency>
开始使用
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
先创建实体类
/**
* ik_smart:进行最小粒度分词,ik_max_word进行最大粒度分词
*/
@Data
@Document(indexName = "xhs")
public class EsData {
/**
* 博客id
*/
@Id
private Long id;
/**
* 博客标题
*/
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String blogTable;
/**
* 前端展示标题
*/
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String blogWebContent;
/**
* 博客内容
*/
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String content;
public static String getIndexName(){
return EsData.class.getAnnotation(Document.class).indexName();
}
}
@Document
和@Field
注解是在执行Api的是可以做查询对应数据的对象映射、或者索引的创建、及文档数据的字段类型的限制
@Document
public @interface Document {
/**
* 索引的名称
*/
String indexName();
/**
* 弃用的类型现在默认是_doc
*/
@Deprecated
String type() default "";
/**
* 是否使用这个注解的默认配置、false使用、true不使用
*/
boolean useServerConfiguration() default false;
/**
* 分片数量
*/
short shards() default 1;
/**
* 副本数量
*/
short replicas() default 1;
/**
* 刷新间隔
*/
String refreshInterval() default "1s";
/**
* 索引存储类型
*/
String indexStoreType() default "fs";
/**
* 是否启动时创建索引、我做这个启动时不会自动创建、自行研究看源码
*/
boolean createIndex() default true;
/**
* 版本号
*/
VersionType versionType() default VersionType.EXTERNAL;
}
@Field
public @interface Field {
/**
* 别名
*/
@AliasFor("name")
String value() default "";
/**
* 文档中存储的名称、如果不设置别名使用属性名
*/
@AliasFor("value")
String name() default "";
/**
* 文档中存储的数据类型
*/
FieldType type() default FieldType.Auto;
/**
* 是否能被搜索、true:可以作为搜索条件、false:不可以作为搜索条件
*/
boolean index() default true;
/**
* 日期格式化 参考官方文档使用 https://www.elastic.co/guide/reference/mapping/date-format/
*/
DateFormat format() default DateFormat.none;
String pattern() default "";
boolean store() default false;
boolean fielddata() default false;
/**
* 搜索时使用对应使用的分词器
*/
String searchAnalyzer() default "";
/**
* 使用对应使用的分词器
*/
String analyzer() default "";
String normalizer() default "";
String[] ignoreFields() default {};
boolean includeInParent() default false;
String[] copyTo() default {};
int ignoreAbove() default -1;
boolean coerce() default true;
boolean docValues() default true;
boolean ignoreMalformed() default false;
IndexOptions indexOptions() default IndexOptions.none;
boolean indexPhrases() default false;
IndexPrefixes[] indexPrefixes() default {};
boolean norms() default true;
String nullValue() default "";
int positionIncrementGap() default -1;
Similarity similarity() default Similarity.Default;
TermVector termVector() default TermVector.none;
double scalingFactor() default 1;
int maxShingleSize() default -1;
}
以上没写注释的没去找具体那个地方使用、写的注释的基本都是常用的、上面说的一个问题是否会自动创建对应的索引、你可以使用另一种方式创建索引、并创建对应字段的映射类型Mapping
@PostConstruct
public void init() {
try {
// 判断索引是否存在
boolean exists = elasticsearchRestTemplate.indexOps(EsBlogData.class).exists();
if (!exists) {
log.info("没有{}索引开始创建索引.....", EsBlogData.getIndexName());
// 创建索引
elasticsearchRestTemplate.indexOps(EsBlogData.class).create();
// 创建映射关系
elasticsearchRestTemplate.indexOps(EsBlogData.class)
.putMapping(elasticsearchRestTemplate.indexOps(EsBlogData.class).createMapping());
}
} catch (Exception e) {
log.error("判断索引是否存在出现异常", e);
}
}
增
单个
public Boolean insert(EsBlogData target) {
EsBlogData save = elasticsearchRestTemplate.save(target);
return true;
}
批量
public Integer batchInsert(List<EsBlogData> targetS) {
List<IndexQuery> indexQueryList = new ArrayList<>();
for (EsBlogData esBlogData : targetS) {
if (null == esBlogData.getId()) continue;
indexQueryList.add(new IndexQueryBuilder()
.withId(String.valueOf(esBlogData.getId()))
.withObject(esBlogData)
.build()
);
}
List<String> list = elasticsearchRestTemplate.bulkIndex(indexQueryList, elasticsearchRestTemplate.getIndexCoordinatesFor(EsBlogData.class));
return indexQueryList.size();
}
删
单个
@Override
public Boolean delete(Object id) {
String delete = elasticsearchRestTemplate.delete(String.valueOf(id), EsBlogData.class);
return true;
}
多个(按条件)
@Override
public Integer batchDelete(List<Object> idS) {
NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.termsQuery("id", idS))
.build();
elasticsearchRestTemplate.delete(nativeSearchQuery, EsBlogData.class, elasticsearchRestTemplate.getIndexCoordinatesFor(EsBlogData.class));
return 0;
}
条件删除构造的条件和查询一样、相当于就把能查询出来的数据删除。
改
单个(根据id)
@Override
public Boolean update(EsBlogData target) {
UpdateQuery builder = UpdateQuery
.builder(String.valueOf(target.getId()))
.withDocument(Document.parse(JSONUtil.toJsonStr(target)))
.build();
UpdateResponse response = elasticsearchRestTemplate
.update(builder, elasticsearchRestTemplate.getIndexCoordinatesFor(EsBlogData.class));
return true;
}
要注意的就是如果es中修改已经存在的字段数据就是修改、如果es中没有这个字段的数据它会添加、如果需要修改单个字段的值的话对象的不需要修改可以不用设置值。
批量修改
@Override
public Integer batchUpdate(List<EsBlogData> targetS) {
List<UpdateQuery> indexQueryList = new ArrayList<>();
for (EsBlogData esBlogData : targetS) {
if (null == esBlogData.getId()) continue;
indexQueryList.add(UpdateQuery
.builder(String.valueOf(esBlogData.getId()))
.withDocument(Document.parse(JSONUtil.toJsonStr(esBlogData)))
.build());
}
elasticsearchRestTemplate.bulkUpdate(indexQueryList, elasticsearchRestTemplate.getIndexCoordinatesFor(EsBlogData.class));
return indexQueryList.size();
}
查
@Override
public List<EsBlogData> search(SearchCondition content) {
before(Operation.SELECT, content);
if (ObjectUtil.isNull(content.getPageNo())) {
content.setPageNo(1);
}
if (ObjectUtil.isNull(content.getPageSize())) {
content.setPageSize(2);
}
if (StrUtil.isEmpty(content.getKeyWord())) {
log.error("查询内容不能为空.....");
return null;
}
if (CollUtil.isEmpty(content.getFieldS())) {
log.error("查询的字段不能为空.....");
return null;
}
// 构建高亮对象
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.preTags("<span style='color:red'>");
highlightBuilder.postTags("</span>");
for (String field : content.getFieldS()) {
// 设置高亮的字段名
highlightBuilder.field(field);
}
// 构建查询对象
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
// 设置匹配的要的字段名
boolQueryBuilder.must(QueryBuilders.multiMatchQuery(content.getKeyWord(), content.getFieldS().toArray(new String[]{})));
// 是否有时间筛选
if (ObjectUtil.isNotNull(content.getRecently())) {
boolQueryBuilder.filter(QueryBuilders
.rangeQuery("createTime")
.format("yyyy-MM-dd HH:mm:ss")
.gt(content.getRecently().getRecently())
.lt(DateTime.now().toString()));
}
// 构建搜索对象
NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
.withQuery(boolQueryBuilder)
.withHighlightBuilder(highlightBuilder)
.withPageable(PageRequest.of(content.getPageNo() - 1, content.getPageSize()))
.build();
// 输出es dsl语句
// System.out.println(nativeSearchQuery.getQuery().toString());
SearchHits<EsBlogData> search = elasticsearchRestTemplate.search(nativeSearchQuery, EsBlogData.class);
if (search.getTotalHits() == 0) {
return null;
}
List<EsBlogData> list = new ArrayList<>();
for (SearchHit<EsBlogData> hit : search.getSearchHits()) {
for (String field : content.getFieldS()) {
// 高亮后的数据
List<String> highlightField = hit.getHighlightField(field);
StringBuilder newContent = new StringBuilder();
if (ObjectUtil.isNotNull(highlightField) && highlightField.size() > 0) {
newContent.append(highlightField.get(0));
}
try {
if (StrUtil.isNotBlank(newContent)) {
// 得到对应属性的操作对象
Field declaredField = hit.getContent().getClass().getDeclaredField(field);
declaredField.setAccessible(Boolean.TRUE);
// 将新值查询设置到对象中
declaredField.set(hit.getContent(), newContent.toString());
}
} catch (NoSuchFieldException | IllegalAccessException e) {
log.error("高亮设置值错误", e);
}
}
list.add(hit.getContent());
}
after(Operation.SELECT, search);
return list;
}
查询这一块我写多个字段中如果检索到我设置的keyWord
搜索的值就将数据查出来
看下这个查询图、其实你会发现es的api还有有一定规律的、后续你不知道如何使用他的api的话可以去网上找接口调用时提供的json请求体中的数据一步一步的把对象给build出来作为查询使用。