1.环境以及依赖
- JDK 11.0.8
- SpringBoot 2.2.7.RELEASE
- spring-data-elasticsearch 4.0.5.RELEASE 对应 ES版本7.6.2
- lombok
<!--ElasticSearch-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>4.0.5.RELEASE</version>
</dependency>
2.背景介绍
原来的mysql库里有张商品表, 对商品名进行模糊搜索, 一开始走mysql时间还可以接受. 随着数据累积, 需要走ES搜索优化接口响应速度.
3.引入并集成Elasticsearch
3.1引入ES依赖
其实这里可以引入ES的SpringBoot starter. 我这里是因为已经先有了7.6版本的ES环境,导致和项目需要的版本不一致, 才自己另外引入了其他版本的依赖
3.2 yml文件配置
spring:
elasticsearch:
rest:
uris: http://localhost:9200
3.3实体类的改造
原本已经存在了一个ItemInfoAnalysed.java的实体类. 我选择了新建一个ESItemInfoAnalysed.java类,然后在类上加@Document
注解声明这个类是映射到ES中的一个doc
原本的ItemInfoAnalysed类中createTime是LocalDateTime类型的, 但是保存到ES中会自动转换成时间戳, 所以我们ESItemInfoAnalysed类中需要把createTime声明成Long类型.
@Data
@NoArgsConstructor
@Document(indexName = "iteminfoanalysed", refreshInterval = "0")
public class EsItemInfoAnalysed {
@Id
private Long id;
private Long createTime;
}
实体类中涉及的注解有@Document
, @Id
, @Field
, 具体的解释可以参见这篇博客的1.2章: 创建文档实体对象. 这篇博客写的还是很好的, 就是时间有点久远,里面用到的方法现在都已经被官方废弃了.
4.动态查询
4.1构造查询语句
为了在jpa中实现复杂的动态查询, 我们借助NativeSearchQuery
构造查询条件. 创建一个EsItemInfoAnalysedCmd类来接收前端的查询参数并在类里面构造查询语句.
@Data
@NoArgsConstructor
public class EsItemInfoAnalysedCmd {
private Integer type;
private Integer dateFrom;
private Integer dateTo;
private List<String> sourceType;
private Integer salesVolumeFrom;
private Integer salesVolumeTo;
private String title;
public EsItemInfoAnalysedCmd(String title, List<String> sourceType, Integer salesVolumeFrom,
Integer salesVolumeTo, Integer dateFrom, Integer dateTo) {
this.title = title;
this.sourceType = sourceType;
this.salesVolumeFrom = salesVolumeFrom;
this.salesVolumeTo = salesVolumeTo;
this.type = type;
this.dateFrom = dateFrom;
this.dateTo = dateTo;
}
/**
* JPA的复杂查询
*
* @return /
*/
public Query buildQuery(Pageable pageable) {
BoolQueryBuilder builder = QueryBuilders.boolQuery();
if (type != null) {
//term用来精确查询数字(numbers)、布尔值(Booleans)、日期(dates)
builder.must(QueryBuilders.termQuery("type", type));
}
//范围查询, between
if (dateFrom != null && dateTo != null) {
builder.must(QueryBuilders.rangeQuery("date").from(dateFrom).to(dateTo));
}
if (sourceType != null && sourceType.size() > 0) {
//terms用来查询文本或者多个值
builder.must(QueryBuilders.termsQuery("sourceType", toStringArray(sourceType)));
}
//销量xx以上, >=
if (salesVolumeFrom != null && salesVolumeTo == null) {
builder.must(QueryBuilders.rangeQuery("salesVolume").gte(salesVolumeFrom));
}
//模糊查询, 只要是包换title的都返回
if (!StringUtils.isEmpty(title)) {
builder.must(QueryBuilders.matchPhraseQuery("title", title));
}
//构建查询语句
return new NativeSearchQueryBuilder().withQuery(builder)
.withPageable(pageable) //SpringBoot提供的分页参数
.build();
}
private String[] toStringArray(List<String> sourceType) {
String[] res = new String[sourceType.size()];
for (int i = 0; i < sourceType.size(); i++) {
res[i] = sourceType.get(i);
}
return res;
}
}
4.2Controller
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@GetMapping("/es")
public ResponseEntity<Page<EsItemInfoAnalysed>> esList(@RequestParam(required = false) String title,
@RequestParam(required = false) List<String> sourceType,
@RequestParam(required = false) Integer type,
@RequestParam(required = false) Integer salesVolumeFrom,
@RequestParam(required = false) Integer salesVolumeTo,
@RequestParam(required = false) Integer dateFrom,
@RequestParam(required = false) Integer dateTo, Pageable pageable) {
EsItemInfoAnalysedCmd cmd = new EsItemInfoAnalysedCmd(title, sourceType, type, salesVolumeFrom, salesVolumeTo, dateFrom, dateTo);
Page<EsItemInfoAnalysed> page = itemInfoAnalysedService.esList(cmd, pageable);
return new ResponseEntity<>(page, HttpStatus.OK);
}
4.3ServiceImp
官方提供了一个ElasticsearchRestTemplate来实现与es的交互. 传统的jpa模式是新建一个接口实现Repository接口, 不过ElasticsearchRepository的方法几乎全部被废弃, 因此我这里使用了官方推荐的方法.
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
@Service
public class ItemInfoAnalysedServiceImpl implements ItemInfoAnalysedService {
private final ElasticsearchRestTemplate elasticsearchRestTemplate;
public ItemInfoAnalysedServiceImpl(ElasticsearchRestTemplate elasticsearchRestTemplate) {
this.elasticsearchRestTemplate = elasticsearchRestTemplate;
}
@Override
public Page<EsItemInfoAnalysed> esList(EsItemInfoAnalysedCmd cmd, Pageable pageable) {
SearchHits<EsItemInfoAnalysed> hits = elasticsearchRestTemplate.search(cmd.buildQuery(pageable), EsItemInfoAnalysed.class);
return EsItemInfoAnalysed.build(hits, pageable);
}
}
4.4 EsItemInfoAnalysed.build()
public static Page<EsItemInfoAnalysed> build(SearchHits<EsItemInfoAnalysed> hits, Pageable pageable) {
List<EsItemInfoAnalysed> content = new ArrayList<>(hits.getSearchHits().size());
for (SearchHit<EsItemInfoAnalysed> hit : hits) {
content.add(hit.getContent());
}
return new PageImpl<>(content, pageable, hits.getTotalHits());
}