SpringBoot Jpa集成Elasticsearch7.6.x, 支持高级动态查询

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());
}
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值