ElasticSearch(7) -- SpringDataElasticsearch

tech2024-10-27  26

1. Spring Data ElasticSearch简介

1.1. 什么是Spring Data

spring-boot-starter-data-redis > redisTemplate Spring Data 是一个用于简化数据访问,并支持云服务的开源框架。其主要目标是使得对数据的访问变得方便快捷。 Spring Data可以极大的简化数据操作的写法,可以在几乎不用写实现的情况下,实现对数据的访问和操作。除了CRUD外,还包括如分页、排序等一些常用的功能。 Spring Data的官网

1.2. 什么是SpringDataES

SpringDataElasticsearch(后面简称SDE)是Spring Data项目下的一个子模块,是Spring提供的操作ElasticSearch的数据层,封装了大量的基础操作,通过它可以很方便的操作ElasticSearch的数据。 Spring Data 的使命是给各种数据访问提供统一的编程接口,不管是关系型数据库(如MySQL),还是非关系数据库(如Redis),或者类似Elasticsearch这样的索引数据库。从而简化开发人员的代码,提 高开发效率。 包含很多不同数据操作的模块: Spring Data Elasticsearch的页面 特征:

支持Spring的基于@Configuration的java配置方式,或者XML配置方式提供了用于操作ES的便捷工具类ElasticsearchTemplate。包括实现文档到POJO之间的自动智能映射。利用Spring的数据转换服务实现的功能丰富的对象映射基于注解的元数据映射方式,而且可扩展以支持更多不同的数据格式根据持久层接口自动生成对应实现方法,无需人工编写基本操作代码(类似mybatis,根据接口自动得到实现)。当然,也支持人工定制查询

2. 准备工作

2.1. 创建spring操作es的父工程

父工程,删除src目录

2.2. 创建spring data es工程

导入依赖 配置信息

server: port: 8081 spring: application: name: spring-data-es data: elasticsearch: cluster-name: elasticsearch # 集群名称 cluster-nodes: 127.0.0.1:9300 # 节点信息 tcp端口号

需要注意的是,SpringDataElasticsearch底层使用的不是Elasticsearch提供的RestHighLevelClient,而是TransportClient,并不采用Http协议通信,而是访问elasticsearch对外开放的tcp端口,ElasticSearch默认tcp端口。 另外,SpringBoot已经帮我们配置好了各种SDE配置,并且注册了一个ElasticsearchTemplate供我们使用。接下来一起来试试吧。

3. 索引库操作

3.1. 新建实体类Goods

作为与索引库对应的文档,通过实体类上的注解来配置索引库信息的,比如:索引库名、类型名、分片、副本数量、还有映射信息;

package com.test.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; 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; /** * 与索引库对应的文档实体类型。 */ @Data @AllArgsConstructor @NoArgsConstructor //集群时可以设置 : shards:分片数量(默认值:5),replicas:副本数量(默认值:1) @Document(indexName = "goods",type = "goods") public class Goods { /* 必须有id,这里的id是全局唯一的标识,等同于es中的“_id” */ @Id private Long id; /** * 标题 * type: 字段数据类型 * analyzer: 分词器类型 * index: 是否索引(默认值:true) * store: 是否存储(默认值:false) */ @Field(type = FieldType.Text,analyzer = "ik_max_word") private String title; /** * 分类 */ @Field(type = FieldType.Keyword) private String category; /** * 品牌 */ @Field(type = FieldType.Keyword) private String brand; /** * 价格 */ @Field(type = FieldType.Double) private Double price; /** * 图片地址 */ @Field(type = FieldType.Keyword,index = false) private String images; }

几个用到的注解:

@Document:声明索引库配置 indexName:索引库名称 type:类型名称,默认是“docs” shards:分片数量,默认5 replicas:副本数量,默认1@Id:声明实体类的id@Field:声明字段属性 type:字段的数据类型 analyzer:指定分词器类型 index:是否创建索引 默认为true store:是否存储 默认为false

3.2. 创建索引库

package com.test; import com.test.pojo.Goods; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class SpringDataEsApplicationTests { @Autowired private ElasticsearchTemplate template; @Test public void createIndex() { boolean flag = template.createIndex(Goods.class); System.out.println("创建索引是否成功: " + flag); } }

测试:

3.3. 创建映射

package com.test; import com.test.pojo.Goods; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class SpringDataEsApplicationTests { @Autowired private ElasticsearchTemplate template; @Test public void testPutMapping() { boolean flag = template.putMapping(Goods.class); System.out.println("创建映射是否成功: " + flag); } }

测试:

3.4. 删除索引库

package com.test; import com.test.pojo.Goods; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class SpringDataEsApplicationTests { @Autowired private ElasticsearchTemplate template; @Test public void delMapping() { boolean flag = template.deleteIndex(Goods.class); System.out.println("删除索引库是否成功:" + flag); } }

4. 使用ElasticsearchRepository对数据进行增删改

4.1. 新建 GoodRepository 接口

SDE的文档索引数据CRUD并没有封装在ElasticsearchTemplate中,而是有一个叫做ElasticsearchRepository的接口: 我们需要自定义接口,继承ElasticsearchRespository:

package com.test.repository; import com.test.pojo.Goods; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; public interface GoodsESRepository extends ElasticsearchRepository<Goods, Integer> { }

执行springboot的main方法, 发现创建了 索引库和映射type

4.2. 创建单个文档

package com.test.repository; import com.test.pojo.Goods; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class GoodsESRepositoryTest { @Autowired private GoodsESRepository goodsESRepository; @Test public void demo01() { Goods goods = new Goods(1L, "华为手机1", "手机", "华为", 1999.0, "http://wwww.baidu.com"); goodsESRepository.save(goods); } }

测试:

4.3. 批量创建文档

@Test public void demo02() { // 需求: 批量新建 List<Goods> goodsList = new ArrayList<Goods>(); for (long i = 2; i < 10 ; i++) { Goods goods = new Goods(i, "小米手机" + i, "手机", "小米", 1999.0 + 1000, "http://wwww.baidu.com"); goodsList.add(goods); } goodsESRepository.saveAll(goodsList); }

4.4. 修改和删除

@Test public void demo03() { // 需求: 根据id查询商品信息 Optional<Goods> optional = goodsESRepository.findById(2); if (optional.isPresent()) { Goods goods = optional.get(); System.out.println(goods); } } @Test public void demo04() { // 需求: 查询所有 Iterable<Goods> it = goodsESRepository.findAll(); for (Goods goods : it) { System.out.println(goods); } } @Test public void demo05() { // 需求: 查询title中含有华为的数据 QueryBuilder queryBuilder = QueryBuilders.termQuery("title", "华为"); Iterable<Goods> it = goodsESRepository.search(queryBuilder); for (Goods goods : it) { System.out.println(goods); } }

5. 查询文档数据

默认提供了根据id查询, 查询所有两个功能

5.1. 根据id查询

调用goods仓库根据id查询判断返回 Optional 对象是有有值从Optional 对象中获取查询结果 @Test public void demo03() { // 需求: 根据id查询商品信息 Optional<Goods> optional = goodsESRepository.findById(6); if(optional.isPresent()) { Goods goods = optional.get(); System.out.println(goods); } }

结果:

5.2. 查询所有

@Test public void demo04() { // 需求: 查询所有 Iterable<Goods> it = goodsESRepository.findAll(); for (Goods goods : it) { System.out.println(goods); } }

结果:

5.3. 使用search查询

构建QueryBuilder 对象设置查询类型和查询条件调用good仓库search方法进行查询遍历打印输出查询结果 @Test public void demo05() { // 需求: 查询title中含有手机6的数据 QueryBuilder queryBuilder = QueryBuilders.termQuery("title", "华为"); Iterable<Goods> it = goodsESRepository.search(queryBuilder); for (Goods goods : it) { System.out.println(goods); } }

结果:

5.4. 使用search查询并分页排序

构建Sort排序对象,指定排序字段和排序方式使用PageRequest构建Pageable分页对象,指定分页参数,并将排序对象设置到分页对象中调用goods仓库search方法进行查询解析结果 @Test public void demo06() { // 需求: 查询所有 且 按照id排序 // 1. 构建Sort排序对象,指定排序字段和排序方式 Sort sort = new Sort(Sort.Direction.ASC, "id"); // 2. 使用PageRequest构建Pageable分页对象,指定分页参数,并将排序对象设置到分页对象中 Pageable pageable = PageRequest.of(0, 3, sort); // 3. 调用goods仓库search方法进行查询 Page<Goods> page = goodsESRepository.search(QueryBuilders.matchAllQuery(), pageable); // 4 解析结果 // 4.1 获取总记录数 long totalElements = page.getTotalElements(); System.out.println("总记录数: ===== " + totalElements); // 4.2 获取总页数 int totalPages = page.getTotalPages(); System.out.println("总页数: ===== " + totalPages); // 4.2 获取当前页的数据 List<Goods> goodsList = page.getContent(); for (Goods goods : goodsList) { System.out.println("========== " + goods); } }

结果:

6. 使用ElasticsearchTemplate查询

SDE也支持使用ElasticsearchTemplate进行原生查询

而查询条件的构建是通过一个名为NativeSearchQueryBuilder的类来完成的,不过这个类的底层还是使用的原生API中的QueryBuilders、HighlightBuilders等工具。

6.1. 分页和排序

可以通过NativeSearchQueryBuilder类来构建分页和排序、聚合等操作

queryBuilder.withQuery() //设置查询类型和查询条件

queryBuilder.withPageable() //设置分页

queryBuilder.withSort()//设置排序

构建NativeSearchQueryBuilder查询对象使用QueryBuilders指定查询类型和查询条件使用SortBuilders指定排序字段和排序方式使用PageRequest对象指定分页参数调用NativeSearchQueryBuilder的build方法完成构建使用ElasticsearchTemplate完成查询解析结果 package com.test.repository; import com.test.pojo.Goods; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.search.sort.SortOrder; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.PageRequest; import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage; import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class GoodsESRepositoryTest { @Autowired private ElasticsearchTemplate elasticsearchTemplate; /** * 使用ElasticsearchTemplate完成分页排序查询 */ @Test public void nativeSearchQuery(){ //1.构建NativeSearchQueryBuilder查询对象 NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder(); //2.使用QueryBuilders指定查询类型和查询条件 searchQueryBuilder.withQuery(QueryBuilders.matchQuery("title","小米")); //3.使用SortBuilders指定排序字段和排序方式 searchQueryBuilder.withSort(SortBuilders.fieldSort("id").order(SortOrder.DESC)); //4.使用PageRequest对象指定分页参数 searchQueryBuilder.withPageable(PageRequest.of(0,2)); //5.调用NativeSearchQueryBuilder的build方法完成构建 NativeSearchQuery searchQuery = searchQueryBuilder.build(); //6.使用ElasticsearchTemplate完成查询 AggregatedPage<Goods> page = elasticsearchTemplate.queryForPage(searchQuery, Goods.class); //7.解析结果 //7.1获取总记录数 long totalElements = page.getTotalElements(); System.out.println("totalElements: " + totalElements); //7.2获取页总数 int totalPages = page.getTotalPages(); System.out.println("totalPages: " + totalPages); //7.3遍历查询结果 for (Goods goods : page) { System.out.println("结果 : " + goods); } } }

6.2. 高亮显示

@Test public void demo07() { // 1.创建查询器构造者 NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder(); // 设置高亮字段为title nativeSearchQueryBuilder.withHighlightFields(new HighlightBuilder.Field("title").preTags("<font color='pink'>").postTags("</font>")); // 设置查询条件 nativeSearchQueryBuilder.withQuery(QueryBuilders.matchQuery("title","小米")); // 设置排序规则 nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("id").order(SortOrder.ASC)); // 设置分页信息 nativeSearchQueryBuilder.withPageable(PageRequest.of(0,3)); // 获取查询器 NativeSearchQuery searchQuery = nativeSearchQueryBuilder.build(); // 调用elasticsearchTemplate的api获取分页数据对象 AggregatedPage<Goods> page = template.queryForPage(searchQuery, Goods.class, new SearchResultMapper() { @Override public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) { // 用来存放查询出来的商品集合 ArrayList<Goods> goodsList = new ArrayList<>(); // 获取查询命中的hits对象 SearchHits hits = searchResponse.getHits(); // 如果命中为空,直接返回空 if (hits.getHits().length<=0){ return null; } // 遍历命中对象 for (SearchHit hit : hits) { // 命中不为空,从hits中取出数据并封装为goods Goods goods = new Goods(); // 设置字段值 goods.setId(Long.parseLong(hit.getId())); if (hit.getSourceAsMap().get("category")!=null){ goods.setCategory(hit.getSourceAsMap().get("category").toString()); } if (hit.getSourceAsMap().get("brand")!=null){ goods.setBrand(hit.getSourceAsMap().get("brand").toString()); } if (hit.getSourceAsMap().get("price")!=null){ goods.setPrice(Double.parseDouble(hit.getSourceAsMap().get("price").toString())); } if (hit.getHighlightFields().size()>0){ String highLightTitle = hit.getHighlightFields().get("title").fragments()[0].toString(); goods.setTitle(highLightTitle); } goodsList.add(goods); } return new AggregatedPageImpl<>((List<T>) goodsList); } }); long totalElements = page.getTotalElements(); int totalPages = page.getTotalPages(); System.out.println("总条数:" + totalElements); System.out.println("总页数" + totalPages); List<Goods> goodsList = page.getContent(); for (Goods goods : goodsList) { System.out.println(goods); } } }

最新回复(0)