ElasticSearch搜索引擎-4_学习笔记(2021.5.23)
Spring
集成ElasticSearch
(官网)
前言:
Spring Data Elasticsearch 基于 spring data API 简化 Elasticsearch 操作,将原始操作 Elasticsearch 的客户端 API 进行封装 。Spring Data 为 Elasticsearch 项目提供集成搜索引擎。 Spring Data Elasticsearch POJO 的关键功能区域为中心的模型与 Elastichsearch 交互文档和轻 松地编写一个存储索引库数据访问层。
简单来说, 简化了ES的操作。
Spring Data Elasticsearch 版本对比
我们使用的版本是最新的
从
TransportClient
Elasticsearch 7开始不推荐使用,并将在Elasticsearch 8中将其删除。(请参阅Elasticsearch文档)。TransportClient
只要使用的Elasticsearch版本中可用,Spring Data Elasticsearch就会支持,但是从4.0版开始已弃用使用该类的类。新的DataES 使用的是新的客户端
RestHighLevelClient
具体模板ElasticsearchRestTemplate
配置类在这里ElasticsearchDataConfiguration
1.0 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
SpringBoot
中的elasticsearch
依赖包含了spring-data-elasticsearch
非SpringBoot项目, 引入
<artifactId>spring-data-elasticsearch</artifactId>
1.1 编写application.yml
配置文件
spring:
elasticsearch:
rest:
uris:
- "http://119.29.xxx.xxx:9200"
2.0 使用ElasticsearchTemplate
操作ES
从4.0版开始不推荐使用ElasticsearchTemplate,请改用ElasticsearchRestTemplate。
这
ElasticsearchTemplate
是ElasticsearchOperations
使用Transport Client的接口的实现。原因是: 从
TransportClient
Elasticsearch 7开始不推荐使用,并将在Elasticsearch 8中将其删除。
2.1 索引操作
2.1.1 创建索引
@Test
public void index() throws IOException {
// 创建索引
IndexOperations indexOps = restTemplate.indexOps(IndexCoordinates.of("create_my_index"));
boolean result = indexOps.create();
log.info("ES操作结果:{}",result);
}
2.1.2 查询索引
@Test
public void index() throws IOException {
// 查询索引
IndexOperations indexOps = restTemplate.indexOps(IndexCoordinates.of("create_my_index"));
ObjectMapper mapper = new ObjectMapper();
log.info("ES操作结果:{}",indexOps.exists());
log.info("ES操作结果:{}",mapper.writeValueAsString(indexOps.getSettings()));
log.info("ES操作结果:{}",mapper.writeValueAsString(indexOps.getMapping()));
}
2.1.2 删除索引
@Test
public void index() throws IOException {
// 删除索引
IndexOperations indexOps = restTemplate.indexOps(IndexCoordinates.of("my_elasticsearch_test2"));
log.info("ES操作结果:{}",indexOps.delete());
}
2.2 映射操作
2.2.1 创建ES实体对象
@Data
public class MyFromJava implements Serializable {
//必须有 id,这里的 id 是全局唯一的标识,等同于 es 中的"_id"
@Id
private Long id;
/**
* type : 字段数据类型
* analyzer : 分词器类型 (IK分词)
* index : 是否索引(默认:true)
*/
@Field(type = FieldType.Text, index = true)
private String name;
@Field(type = FieldType.Integer,index = false)
private Integer age;
// Keyword : 短语,不进行分词
@Field(type = FieldType.Keyword,index = false)
private String sex;
@Field(type = FieldType.Text,index = true)
private String address;
}
2.2.2 创建索引映射
@Test
public void index() throws Exception {
// 创建映射
IndexOperations indexOps = restTemplate.indexOps(IndexCoordinates.of("create_my_index"));
log.info("ES操作结果:{}",indexOps.putMapping(MyFromJava.class));
}
2.2.3 查询映射
@Test
public void index() throws Exception {
// 查询映射
IndexOperations indexOps = restTemplate.indexOps(IndexCoordinates.of("create_my_index"));
log.info("ES操作结果:{}",indexOps.getMapping());
}
2.2.4 更新映射
PS: 不能更新已经添加好的映射, 只能新增字段的映射关系
// 实体类增加字段
// @Field(type = FieldType.Double,index = true)
// private Double price;
@Test
public void index() throws Exception {
// 创建映射
IndexOperations indexOps = restTemplate.indexOps(IndexCoordinates.of("create_my_index"));
log.info("ES操作结果:{}",indexOps.putMapping(MyFromJava.class));
}
// 在查询就能看到最新结果
2.3 文档操作
2.3.1 保存文档(或全局修改)
@Test
public void document() throws Exception {
// 保存文档(或全局修改),
//public <T> Iterable<T> save(Iterable<T> entities, IndexCoordinates index) 批量添加
MyFromJava java = new MyFromJava();
java.setId(10086L);
java.setAge(18);
java.setName("志豪");
java.setAddress("深圳");
java.setPrice(BigDecimal.ONE.doubleValue());
restTemplate.save(java, IndexCoordinates.of("create_my_index"));
// 下面方式实体类需要标注 @Document(indexName = "my_from_java") 注解
// restTemplate.save(java);
}
2.3.2 简单查看单个文档
@Test
public void document() throws Exception {
// 简单查看单个文档
MyFromJava fromJava = restTemplate.get("10086", MyFromJava.class, IndexCoordinates.of("create_my_index"));
log.info("ES操作结果:{}",fromJava.toString());
}
2.3.3 局部修改文档
@Test
public void document() throws Exception {
// 局部修改文档
MyFromJava java = new MyFromJava();
java.setSex("男");
java.setAddress("深圳66666");
UpdateQuery.Builder builder = UpdateQuery.builder("10086");
UpdateQuery updateQuery = builder.withDocument(Document.parse(JSONUtil.toJsonStr(java))).build();
UpdateResponse create_my_index = restTemplate.update(updateQuery, IndexCoordinates.of("create_my_index"));
log.info("ES操作结果:{}",create_my_index.getResult().toString());
}
2.3.4 删除文档
@Test
public void document() throws Exception {
// 删除文档 (还有根据搜索条件进行删除的)
String delete = restTemplate.delete("10085", IndexCoordinates.of("create_my_index"));
log.info("ES操作结果:{}",delete);
}
2.4 高级查询
2.4.1 查询所有文档
@Test
public void query() throws IOException {
// 查询所有文档
StringQuery stringQuery = new StringQuery(QueryBuilders.matchAllQuery().toString());
SearchHits<MyFromJava> search = restTemplate.search(stringQuery, MyFromJava.class,IndexCoordinates.of("create_my_index"));
List<SearchHit<MyFromJava>> searchHits = search.getSearchHits();
searchHits.stream().map(SearchHit::getContent).forEach(c-> System.out.println(c));
}
2.4.2 匹配查询
@Test
public void query() throws IOException {
StringQuery stringQuery = new StringQuery(QueryBuilders.matchQuery("address","深圳").toString());
SearchHits<MyFromJava> search = restTemplate.search(stringQuery, MyFromJava.class,IndexCoordinates.of("create_my_index"));
List<SearchHit<MyFromJava>> searchHits = search.getSearchHits();
searchHits.stream().map(SearchHit::getContent).forEach(c-> System.out.println(c));
}
2.4.3 分页排序查询 (返回字段指定与过滤)
@Test
public void query() throws IOException {
// 分页,排序查询
int pageNum = 1;
int pageSize = 3;
PageRequest pageRequest = PageRequest.of(pageNum-1, pageSize);
Sort sort = Sort.by(Sort.Order.desc("id"));
StringQuery stringQuery = new StringQuery(QueryBuilders.matchAllQuery().toString(),pageRequest,sort);
// stringQuery.addSort() 单独排序用这个
// stringQuery.addSourceFilter(); 返回字段指定与过滤
SearchHits<MyFromJava> search = restTemplate.search(stringQuery, MyFromJava.class,IndexCoordinates.of("create_my_index"));
List<SearchHit<MyFromJava>> searchHits = search.getSearchHits();
searchHits.stream().map(SearchHit::getContent).forEach(c-> System.out.println(c));
}
2.4.4 高亮查询
@Test
public void query() throws IOException {
StringQuery stringQuery = new StringQuery(QueryBuilders.matchQuery("name","志豪").toString());
// 设置高亮字段
HighlightBuilder highlightBuilder = new HighlightBuilder();
// 设置标签前缀
highlightBuilder.preTags("<font color='red'>");
// 设置标签后缀
highlightBuilder.postTags("</font>");
// 设置高亮字段
highlightBuilder.field("name");
HighlightQuery highlightQuery = new HighlightQuery(highlightBuilder);
stringQuery.setHighlightQuery(highlightQuery);
SearchHits<MyFromJava> search = restTemplate.search(stringQuery, MyFromJava.class,IndexCoordinates.of("create_my_index"));
List<SearchHit<MyFromJava>> searchHits = search.getSearchHits();
searchHits.stream().map(SearchHit::getContent).forEach(c-> System.out.println(c));
searchHits.stream().map(SearchHit::getHighlightFields).forEach(c-> System.out.println(c));
}
2.4.5 范围查询
@Test
public void query() throws IOException {
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("id");
// 大于等于
rangeQuery.gte(10087);
// 小于等于
rangeQuery.lte(10088);
StringQuery stringQuery = new StringQuery(rangeQuery.toString());
SearchHits<MyFromJava> search = restTemplate.search(stringQuery, MyFromJava.class,IndexCoordinates.of("create_my_index"));
List<SearchHit<MyFromJava>> searchHits = search.getSearchHits();
searchHits.stream().map(SearchHit::getContent).forEach(c-> System.out.println(c));
}
其他高级查询与原生态
API
区别不大。
3.0 使用Spring Data Repository
操作ES (官网)
Spring数据存储库抽象的目标是显着减少实现各种持久性存储的数据访问层所需的样板代码量。
从上图可以看出我们只需要写一个dao接口去继承
ElasticsearchRepository
就相当拥有了其类与父类的所有方法
3.1 编写ES-Dao接口
/**
* @Author: ZhiHao
* @Date: 2021/5/23 22:24
* @Description: ES-MyFromJava-dao
* @Versions 1.0
**/
@Repository // 注入ioc容器
public interface MyEsDao extends ElasticsearchRepository<MyFromJava,Long> {
}
3.2 编写ES实体类
/**
* @Author: ZhiHao
* @Date: 2021/5/5 19:33
* @Description: ES文档数据实体, shards(分片) = 3, replicas(副本数) = 1
* @Versions 1.0
**/
@Data
@Document(indexName = "my_from_java_dao",shards = 3, replicas = 1)
public class MyFromJava implements Serializable {
//必须有 id,这里的 id 是全局唯一的标识,等同于 es 中的"_id"
@Id
private Long id;
/**
* type : 字段数据类型
* analyzer : 分词器类型 (IK分词)
* index : 是否索引(默认:true)
*/
@Field(type = FieldType.Text, index = true)
private String name;
@Field(type = FieldType.Integer,index = false)
private Integer age;
// Keyword : 短语,不进行分词
@Field(type = FieldType.Keyword,index = false)
private String sex;
@Field(type = FieldType.Text,index = true)
private String address;
@Field(type = FieldType.Double,index = true)
private Double price;
}
3.3 进行索引测试
@SpringBootTest
@Slf4j
class ElasticsearchTestss {
// 注入操作ES的Dao
@Autowired
private MyEsDao myEsDao;
/**
* ES索引操作
*
* @author: ZhiHao
* @date: 2021/5/5
*/
@Test
public void test() throws Exception {
// 实体类上标记注解, 服务启动时会自动创建索引与其映射
log.info("服务启动完毕, 索引与映射创建成功");
}
3.4 进行文档操作
3.4.1 添加文档
@Test
public void test() throws Exception {
List<MyFromJava> list = new LinkedList<>();
for (int i = 1; i <= 10; i++) {
MyFromJava java = new MyFromJava();
long longValue = Integer.valueOf(i).longValue();
java.setId(longValue);
java.setAge(i+17);
java.setName("志豪"+i);
java.setSex("男"+i);
java.setAddress("深圳"+i);
java.setPrice(BigDecimal.valueOf(longValue).doubleValue());
list.add(java);
}
Iterable<MyFromJava> fromJavas = myEsDao.saveAll(list);
fromJavas.forEach(c-> System.out.println(c));
log.info("批量添加文档成功!!!");
}
3.4.2 查询文档
分页这里遇到了个之前使用原生API, 没有遇到的问题
@Test
public void test() throws Exception {
// 分页,排序查询
int pageNum = 2;
int pageSize = 5;
Sort sort = Sort.by(Sort.Order.desc("id"));
PageRequest pageRequest = PageRequest.of(pageNum-1, pageSize,sort);
Page<MyFromJava> all = myEsDao.findAll(pageRequest);
Iterator<MyFromJava> iterator = all.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
根据ID查询
Optional<MyFromJava> byId = myEsDao.findById(6L);
System.out.println(byId.get());
3.4.3 更新文档
@Test
public void test() throws Exception {
MyFromJava java = new MyFromJava();
java.setId(6L);
java.setAge(18);
java.setName("志豪");
java.setSex("男");
java.setAddress("深圳");
java.setPrice(BigDecimal.valueOf(6).doubleValue());
MyFromJava save = myEsDao.save(java);
// 更新完查询
System.out.println(myEsDao.findById(6L).get());
}
3.4.4 删除文档
@Test
public void test() throws Exception {
myEsDao.deleteById(6L);
myEsDao.delete(对象);
}
3.5 条件查询
不推荐使用
search
方法
自4.0,使用SEARCHQUERY(查询),标准库方法命名或@ Query注解的方法,或org.springframework.data.elasticsearch.core.ElasticsearchOperations
3.5.1 ES-Dao接口改造
@Repository // 注入ioc容器
public interface MyEsDao extends ElasticsearchRepository<MyFromJava,Long> {
/**
* 使用方法命名查询
*
* @param name
* @return com.zhihao.elasticsearch_demo.entity.MyFromJava
* @author: ZhiHao
* @date: 2021/5/24
*/
MyFromJava findByName(String name);
/**
* 模糊加分页
*
* @param name
* @param pageable
* @return org.springframework.data.domain.Page<com.zhihao.elasticsearch_demo.entity.MyFromJava>
* @author: ZhiHao
* @date: 2021/5/24
*/
Page<MyFromJava> findByNameIsLike(String name, Pageable pageable);
/**
* 模糊加排序
*
* @param name
* @param sort
* @return java.util.List<com.zhihao.elasticsearch_demo.entity.MyFromJava>
* @author: ZhiHao
* @date: 2021/5/24
*/
List<MyFromJava> findByNameIsLike(String name, Sort sort);
}
3.5.2 进行测试
@Test
public void test() throws Exception {
//myEsDao.search() 更为复杂的查询可以使用search, 或者ES模板
// 根据名称查询
System.out.println(myEsDao.findByName("志豪1"));
// 模糊带分页
myEsDao.findByNameIsLike("豪", PageRequest.of(1,5)).forEach(c->System.out.println(c));
// 模糊排序
System.out.println(myEsDao.findByNameIsLike("豪", Sort.by(Sort.Order.desc("id"))));
}
扩展信息:
使用集成ElasticsearchTemplate
分页查询遇到的分页失效问题
之前写法: 第一页是正常好好的, 第二页开始就发现一样没有数据
// 分页,排序查询
int pageNum = 2;
int pageSize = 5;
Sort sort = Sort.by(Sort.Order.desc("id"));
PageRequest pageRequest = PageRequest.of((pageNum-1)*pageSize, pageSize,sort);
Page<MyFromJava> all = myEsDao.findAll(pageRequest);
最终通过
debug
源码发现问题出在, 模板获取分页数据是获取计算后的偏移值
然后代码修改为:
PageRequest pageRequest = PageRequest.of(pageNum-1, pageSize,sort);
1
到的分页失效问题
之前写法: 第一页是正常好好的, 第二页开始就发现一样没有数据
// 分页,排序查询
int pageNum = 2;
int pageSize = 5;
Sort sort = Sort.by(Sort.Order.desc("id"));
PageRequest pageRequest = PageRequest.of((pageNum-1)*pageSize, pageSize,sort);
Page<MyFromJava> all = myEsDao.findAll(pageRequest);
最终通过
debug
源码发现问题出在, 模板获取分页数据是获取计算后的偏移值
[外链图片转存中…(img-VkPi27oF-1621788412897)]
然后代码修改为:
PageRequest pageRequest = PageRequest.of(pageNum-1, pageSize,sort);
1