首先简单介绍下写这篇博文的背景,最近负责的一个聚合型的新项目要大量使用ES的检索功能,之前对es的了解还只是纯理论最多加个基于postman的索引创建操作,所以这次我得了解在java端如何编码实现;网上搜索是一搜一大堆,但是大部分都是些复制粘贴,毫无上下文可言,所以我是阵痛了好几天一点点的梳理整合,最后选择了基于springData封装的es操作,一点点编码测试最终满足了业务端的查询需求;下面就来从零开始介绍下如何在springboot项目中引入es并操控它;

ES简介

es是一个支持全文检索的开源的、高拓展的分布式搜索引擎,它基于Lucene而来,与solr这里也不比较了,这里更关注与java的整合,然后我们知道ES通过分词、倒排索引等技术能支持关系型数据库做不到或者说做不好的全文快速索引就行了,至于es的索引、文档、字段等关键属性这里也不介绍了,下面直接开始整合

项目搭建

一、整合springdata

需要注意的是springboot的版本不能太低,太低有些API不支持,我这里用的是2.6.8

1、引入依赖

    <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

2、创建索引实体

package com.darling.po;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType; import java.util.Date; @Data
@Document(indexName = "dll_index", createIndex=true)
public class EsTestInfo { @Id
@Field(type = FieldType.Keyword)
private String id; @Field(type = FieldType.Text)
private String name; @Field(type = FieldType.Text)
private String desc; @Field(type = FieldType.Date)
private Date publishDt; }

上面的实体类就相当于索引的映射了,可以在里面根据业务需求设置索引名称、每个字段在es中的类型

3、创建DAO

package com.darling.repository;

import com.darling.po.EsTestInfo;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository; @Repository
public interface EsReportInfoDao extends ElasticsearchRepository<EsTestInfo,Long> {
}

这里的写法类似于mybatisPlus,由springdata封装es的操作,后续针对单个索引的增删改查都会基于这个类

4、在项目的配置文件添加ES的配置信息

#配置ES的服务端地址和端口
elasticsearch.host=127.0.0.1
elasticsearch.port=9200

至此,配置基本完成,接下来启动项目就会自动根据实体的配置创建索引和映射了,非常方便;但是有两点需要注意:

  • 一是第一次启动时会创建,第二次启动时如果索引被删除了也会创建,但是如果索引没删除,只是改了索引实体里的配置,比方说把Text改成了Keyword,或者新增了映射,那么按上面的配置索引是不会自动修改的
  • 二是我自身理解的一个坑,刚开始我一直在纠结索引实体类应该放哪项目启动才会自动创建,或者说哪里有配置能指向索引实体所在的包,后来通过测试发现这里自动创建索引与实体类无关与DAO有关,只要你按要求创建了DAO并交给spring管理了,那么dao里泛型传入的实体类就会创建索引

二、增删改查操作

package com.darling.test;

import com.darling.model.PaginationModel;
import com.darling.model.TestQuery;
import com.darling.po.EsTestInfo;
import com.darling.repository.EsTestInfoDao;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.junit.Test;
import org.junit.platform.commons.util.StringUtils;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.test.context.junit4.SpringRunner; import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects; @RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class SpringDataESIndexTest { @Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate; @Autowired
private EsTestInfoDao reportInfoDao; /**
* 新增
*/
@Test
public void save(){
EsTestInfo reportInfo = new EsTestInfo();
reportInfo.setId("123");
reportInfo.setName("张三01");
reportInfo.setDesc("描述01");
reportInfo.setCreateTime(new Date());
reportInfoDao.save(reportInfo);
} /**
* 修改 ID不变 内容改变会自动修改,但是这种方法不推荐,因为只会更新设置的字段,不设置的字段会被置空
*/
@Test
public void update(){
EsTestInfo reportInfo = new EsTestInfo();
reportInfo.setId("123");
reportInfo.setName("张三02");
reportInfo.setDesc("描述02");
reportInfo.setCreateTime(new Date());
reportInfoDao.save(reportInfo);
} /**
* 根据文档ID修改文档 推荐使用此方法修改数据
*/
public void updateRecommend() {
String id = "123";
//构建文档对象
Document document = Document.create();
// 需要修改的字段
document.putIfAbsent("likeStatus",1);
// 文档ID
document.setId(id);
//构建UpdateQuery
UpdateQuery build = UpdateQuery.builder(id).withDocument(document) .withScriptedUpsert(true) .build();
// 执行修改操作
elasticsearchRestTemplate.update(build, IndexCoordinates.of("index_name"));
}
/**
* 根据id查询
*/
@Test
public void findById(){
EsTestInfo reportInfo = reportInfoDao.findById(123L).get();
System.out.println(reportInfo);
} /**
* 查询所有数据
*/
@Test
public void findAll(){
Iterable<EsTestInfo> products = reportInfoDao.findAll();
for (EsTestInfo reportInfo : products) {
System.out.println(reportInfo);
}
} /**
* 根据ID删除文档
*/
@Test
public void delete(){
EsTestInfo reportInfo = new EsTestInfo();
reportInfo.setId("222");
reportInfoDao.delete(reportInfo);
} /**
* 批量新增
*/
@Test
public void saveAll(){
List<EsTestInfo> productList = new ArrayList<>();
for (int i = 0; i < 5; i++) {
EsTestInfo reportInfo = new EsTestInfo();
reportInfo.setId("123");
reportInfo.setName("张三01");
reportInfo.setDesc("描述01");
reportInfo.setCreateTime(new Date());
productList.add(reportInfo);
}
reportInfoDao.saveAll(productList);
} /**
* 分页查询
* 这里我没找如何添加条件进行分页查询所有在下面通过elasticsearchRestTemplate查了
*/
@Test
public void findByPageable(){
//设置排序(排序方式,正序还是倒序,排序的id)
Sort sort = Sort.by(Sort.Direction.DESC,"id");
//当前期望查询的页码,第一页从0开始,1表示第二页
int currentPage=0;
int pageSize = 5;
//设置查询分页
PageRequest pageRequest = PageRequest.of(currentPage, pageSize,sort);
//分页查询
Page<EsTestInfo> productPage = reportInfoDao.findAll(pageRequest);
for (EsTestInfo EsTestInfo : productPage.getContent()) {
System.out.println(EsTestInfo);
}
} @Test
public PaginationModel<EsTestInfo> queryTestDateQuery(TestQuery query){
PaginationModel<EsTestInfo> res = new PaginationModel<>();
int currentPage=query.getPageIndex()-1;
int pageSize = query.getPageSize();
PageRequest pageRequest = PageRequest.of(currentPage, pageSize);
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
BoolQueryBuilder textKeyBqb = new BoolQueryBuilder();
if (StringUtils.isNotBlank(query.getTextKey())) {
/**
* 由于下面会用到must查询,所以此处用textKeyBqb再封装一个builder出来,否则
* 和must同时查询此处会出现0匹配也返回结果的情况
* 如果不想封装textKeyBqb,加上boolQueryBuilder.minimumShouldMatch(1)强制使es
* 最少满足一个should子句才能返回结果也行
*/
textKeyBqb.should(QueryBuilders.matchQuery("id", query.getTextKey()))
.should(QueryBuilders.matchQuery("name", query.getTextKey()))
.should(QueryBuilders.matchQuery("desc", query.getTextKey()))
.should(QueryBuilders.matchQuery("createTime", query.getTextKey()));
}
if (Objects.nonNull(query.getStartDate()) && Objects.nonNull(query.getEndDate())) {
RangeQueryBuilder timeRangeQuery = QueryBuilders.rangeQuery("publishDt")
.gte(query.getStartDate().getTime())
.lte(query.getEndDate().getTime());
boolQueryBuilder.must(timeRangeQuery);
}
if (Objects.nonNull(query.getRptStatus())) {
boolQueryBuilder.must(QueryBuilders.matchQuery("rptStatus", query.getRptStatus()));
}
// 将上面封装的子句加入到主查询条件中
boolQueryBuilder.must(textKeyBqb);
log.info("<<<<<<<<<<<<<<<<<<boolQueryBuilder:{}",boolQueryBuilder);
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(boolQueryBuilder).withPageable(pageRequest).build();
SearchHits<EsTestInfo> search = elasticsearchRestTemplate.search(searchQuery, EsTestInfo.class);
List<EsTestInfo> list = new ArrayList<>();
for (SearchHit<EsTestInfo> productSearchHit : search) {
EsTestInfo pro = productSearchHit.getContent();
list.add(pro);
}
res.setList(list);
res.setTotal(search.getTotalHits());
res.setPageIndex(query.getPageIndex());
res.setPageSize(query.getPageSize());
return res;
} }

提示:代码中的PaginationModel为分页出参封装类、TestQuery为分页查询条件,这里就不贴出来了

三、总结

其实上面的基于dao的增删改查很简单,关键在于分页查询这块,特别是当BoolQueryBuilder 的should和must同时使用时把我一顿坑,下面简单介绍下

  • must表示返回的结果必须满足must子句的条件,并且参与计算分值;
  • should表示返回的结果可能满足should子句的条件.在一个bool查询中,如果没有must,有一个或者多个should子句,那么只要满足一个就可以返回.但是如果既有must,又有should,然后不做特殊设置的情况下,可能即使should字句一个都不匹配

    也会有结果返回;所以需要进行相应设置,具体代码里的注释有写供参考;

ES的java端API操作的更多相关文章

  1. es使用java的api操作

    基本环境的创建 pom依赖  <?xml version="1.0" encoding="UTF-8"?> <project xmlns=&q ...

  2. ES系列十五、ES常用Java Client API

    一.简介 1.先看ES的架构图 二.ES支持的客户端连接方式 1.REST API http请求,例如,浏览器请求get方法:利用Postman等工具发起REST请求:java 发起httpClien ...

  3. 读《分布式一致性原理》JAVA客户端API操作3

    更新数据 客户端可以通过zookeeper的API来更新一个节点的数据内容,有如下两个接口: public Stat setData(final String path, byte data[], i ...

  4. 读《分布式一致性原理》JAVA客户端API操作2

    创建节点 通过客户端API来创建一个数据节点,有一下两个接口: public String create(final String path, byte data[], List<ACL> ...

  5. Java原生API操作XML

    使用Java操作XML的开源框架比较多,如著名的Dom4J.JDOM等,但个人认为不管你用那个框架都要对JDK原生的API有所了解才能更得心应手的应用.本篇就来简单了解下原生的XML API. JAV ...

  6. zookeeper的Java端API应用

    1. 基本使用 org.apache.zookeeper.Zookeeper是客户端入口主类,负责建立与server的会话.它提供了表1所示几类主要方法: 功能 描述 create 在本地目录树中创建 ...

  7. 读《分布式一致性原理》JAVA客户端API操作

    创建会话 客户端可以通过创建一个Zookeeper实例来连接服务器.4种构造方法如下 ZooKeeper(connectString, sessionTimeout, watcher): ZooKee ...

  8. MongoDB -- JAVA基本API操作

    package com.example.mongodb.mongodb.demo; import com.mongodb.MongoClient; import com.mongodb.client. ...

  9. 1.java soap api操作和发送soap消息

    转自:https://blog.csdn.net/lbinzhang/article/details/84721359 1. /** * soap请求 * * @return * @throws Ex ...

随机推荐

  1. CF1450G. Communism(状压DP)

    题面 有一个字符串 s \tt s s 和一个有理数 k \tt k k,可以进行如下操作任意次: 选一个当前串中存在的字符 x \tt x x ,令 i 1 , i 2 , . . . , i m ...

  2. 「学习笔记」字符串基础:Hash,KMP与Trie

    「学习笔记」字符串基础:Hash,KMP与Trie 点击查看目录 目录 「学习笔记」字符串基础:Hash,KMP与Trie Hash 算法 代码 KMP 算法 前置知识:\(\text{Border} ...

  3. k8s手动扩缩容

    1. 查询deploy副本数,ready数表示副本数 kubectl get deploy 2.通过命令直接扩容或者缩容,--replicas=1表示把my-dep缩容到副本数1,--replicas ...

  4. 【设计模式】Java设计模式 - 责任链模式

    [设计模式]Java设计模式 - 责任链模式 不断学习才是王道 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 一个有梦有戏的人 @怒放吧德德 目录 [设计模式]Java设计模式 - 责 ...

  5. JTS TopologyException 问题

    计算2个几何相交结果时候,报错了: val geometry = polygon.intersection(lineString) 日志如下 org.locationtech.jts.geom.Top ...

  6. Spring_事务总结

    Spring 事务总结 rollbackFor 设为 Exception.class场景下 如果在函数内部catch住异常消费掉,没有再抛出的话,不会回滚 如果catch住 然后原封不动抛出,会回滚 ...

  7. 【pkuwc2018】随机算法

    我们考虑用状压dp来解决这一道题 设$f[i][S]$表示当前排列的前i位所构成的最大独立集恰好为S的方案数 我们考虑用$f[i][S]$推出$f[i+1][S']$的值 那么我们有两种扩展的方法,一 ...

  8. 命令行配置Windows高级防火墙

    今天正好看到个帖子,询问如何通过命令行配置防火墙策略中远程IP的地址,特别是添加新的地址. 就是图中Scope里Remote IP address的地址. 第一反应就是用netsh firewall来 ...

  9. Python数据科学手册-Pandas:数值运算方法

    Numpy 的基本能力之一是快速对每个元素进行运算 Pandas 继承了Numpy的功能,也实现了一些高效技巧. 对于1元运算,(函数,三角函数)保留索引和列标签 对于2元运算,(加法,乘法),Pan ...

  10. 源码安装最新版keepalived,剥离日志出来并配置日志轮询

    安装 yum install -y gcc openssl-devel popt-devel ipvsadm libnl3-devel net-snmp-devel libnl libnl-devel ...