袁庭新ES系列18节|Spring Data Elasticsearch高级

news2024/11/15 17:30:20

前言

这一章节袁老师将带领同学们来学习Spring Data Elasticsearch高级操作相关的内容。我们继续来探索SDE是如何将原始操作Elasticsearch的客户端API进行封装的,以及通过Spring Data Elasticsearch如何来操作ES。准备好了吗?我们继续来探索ES的内容。

一. 索引数据CRUD操作

SDE的索引数据CRUD操作并没有封装在ElasticsearchTemplate类中,封装在ElasticsearchRepository这个接口中。

在com.yx.respository包下自定义ProductRepository接口,并继承ElasticsearchRespository接口。

package com.yx.respository;
import com.yx.pojo.Product;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

public interface ProductRepository extends ElasticsearchRepository<Product, Long> {

}

1.创建索引数据

创建索引数据时,有单个创建和批量创建之分。

1.先来看单个创建。在SpringDataESTests类中定义addDocument()方法。

@Autowired
private ProductRepository productRepository;

@Test
public void addDocument() {
    Product product = new Product(1L, "小米手机Mix", "手机", "小米", 2899.00, "http://image.yx.com/12479122.jpg");
    
    // 添加索引数据
    productRepository.save(product);
}

2.再来看批量创建。在SpringDataESTests类中定义addDocuments()方法。

@Test
public void addDocuments() {
    // 准备文档数据
    List<Product> list = new ArrayList<>();

    // 添加数据
    list.add(new Product(2L, "坚果手机R1", "手机", "锤子", 3699.00, "http://image.yx.com/12479122.jpg"));
    list.add(new Product(3L, "华为META20", "手机", "华为", 4499.00, "http://image.yx.com/12479122.jpg"));
    list.add(new Product(4L, "小米Pro", "手机", "小米", 4299.00, "http://image.yx.com/12479122.jpg"));
    list.add(new Product(5L, "荣耀V20", "手机", "华为", 2799.00, "http://image.yx.com/12479122.jpg"));

    // 添加索引数据
    productRepository.saveAll(list);
}

2.查询索引数据

2.1.根据id查询数据

ElasticsearchRepository接口中封装了根据id查询的findById(ID var1)方法。

1.在SpringDataESTests类中定义findById()方法。

@Test
public void findById() {
    Optional<Product> optional = productRepository.findById(1L);
    
    Product defaultProduct = new Product();
    defaultProduct.setTitle("默认商品数据");
    
    // orElse(T other)方法:如果Optional对象中封装的泛型为null,则将orElse()方法的参数作为返回值
    Product product = optional.orElse(defaultProduct);
    
    System.err.println(product);
}

2.运行findById()方法,输出结果见下:

Product(id=1, title=小米手机Mix, category=手机, brand=小米, price=2899.0, images=http://image.yx.com/12479122.jpg)

2.2.查询所有数据

ElasticsearchRepository接口中封装了查询所有数据的findAll()方法。

1.在SpringDataESTests类中定义findAll()方法。

@Test
public void findAll() {
    Iterable<Product> list = productRepository.findAll();
    
    list.forEach(System.err::println);
}

2.运行findAll()方法,输出结果见下:

Product(id=2, title=坚果手机R1, category=手机, brand=锤子, price=3699.0, images=http://image.yx.com/12479122.jpg)
Product(id=4, title=小米Pro, category=手机, brand=小米, price=4299.0, images=http://image.yx.com/12479122.jpg)
Product(id=5, title=荣耀V20, category=手机, brand=华为, price=2799.0, images=http://image.yx.com/12479122.jpg)
Product(id=1, title=小米手机Mix, category=手机, brand=小米, price=2899.0, images=http://image.yx.com/12479122.jpg)
Product(id=3, title=华为META20, category=手机, brand=华为, price=4499.0, images=http://image.yx.com/12479122.jpg)

3.自定义方法查询

3.1.存储库查询关键字

ElasticsearchRepository提供的查询方法有限,但是它却提供了非常强大的自定义查询功能。只要遵循Spring Data Elasticsearch提供的语法,我们可以任意定义方法声明。

关键字

示例

Elasticsearch查询字符串

And

findByNameAndPrice

{"bool" : {"must" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}}

Or

findByNameOrPrice

{"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}}

Is

findByName

{"bool" : {"must" : {"field" : {"name" : "?"}}}}

Not

findByNameNot

{"bool" : {"must_not" : {"field" : {"name" : "?"}}}}

Betweend

findByPriceBetween

{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}

LessThanEqual

findByPriceLessThan

{"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}

GreaterThanEqual

findByPriceGreaterThan

{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}}

Before

findByPriceBefore

{"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}

After

findByPriceAfter

{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}}

Like

findByNameLike

{"bool" : {"must" : {"field" : {"name" : {"query" : "? *","analyze_wildcard" : true}}}}}

StartingWith

findByNameStartingWith

{"bool" : {"must" : {"field" : {"name" : {"query" : "? *","analyze_wildcard" : true}}}}}

EndingWith

findByNameEndingWith

{"bool" : {"must" : {"field" : {"name" : {"query" : "*?","analyze_wildcard" : true}}}}}

Contains/Containing

findByNameContaining

{"bool" : {"must" : {"field" : {"name" : {"query" : "","analyze_wildcard" : true}}}}}

In

findByNameIn(Collection<String>names)

{"bool" : {"must" : {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}}

NotIn

findByNameNotIn(Collection<String>names)

{"bool" : {"must_not" : {"bool" : {"should" : {"field" : {"name" : "?"}}}}}}

Near

findByStoreNear

Not Supported Yet !

True

findByAvailableTrue

{"bool" : {"must" : {"field" : {"available" : true}}}}

False

findByAvailableFalse

{"bool" : {"must" : {"field" : {"available" : false}}}}

OrderBy

findByAvailableTrueOrderByNameDesc

{"sort" : [{ "name" : {"order" : "desc"} }],"bool" : {"must" : {"field" : {"available" : true}}}}

3.2.自定义方法查询案例

1.在ProductRepository接口中定义根据商品的价格区间查询商品数据的findByPriceBetween()方法。

/**
* 根据商品的价格区间查询商品数据
* @param from 开始价格
* @param to 结束价格
* @return 符合条件的商品数据
*/
List<Product> findByPriceBetween(Double from, Double to);

2.无需写实现,SDE会自动帮我们实现该方法,我们只需要用即可。在SpringDataESTests类中定义findByPriceBetween()方法。

@Test
public void findByPriceBetween() {
    List<Product> products = productRepository.findByPriceBetween(3000d, 5000d);
    products.forEach(System.err::println);
}

3.运行findByPriceBetween()方法,输出结果见下:

Product(id=2, title=坚果手机R1, category= 手机, brand=锤子, price=3699.0, images=http://image.yx.com/12479122.jpg)
Product(id=4, title=小米Pro, category= 手机, brand=小米, price=4299.0, images=http://image.yx.com/12479122.jpg)
Product(id=3, title=华为META20, category= 手机, brand=华为, price=4499.0, images=http://image.yx.com/12479122.jpg)

二. 原生查询

1.原生查询介绍

如果觉得上述接口依然不符合你的需求,SDE也支持原生查询。这个时候还是使用ElasticsearchTemplate,而查询条件的构建是通过一个名为NativeSearchQueryBuilder的类来完成的,不过这个类的底层还是使用的原生API中的QueryBuilders、AggregationBuilders、HighlightBuilders等工具来实现的。

2.原生查询案例

需求描述:

  • 查询title中包含“手机”的商品;
  • 且以价格升序进行排序;
  • 进行分页查询:每页展示2条数据,查询第1页;
  • 最后对查询结果进行聚合分析:获取品牌及个数。

1.在SpringDataESTests类中定义nativeQuery()方法。

@Test
public void nativeQuery() {
    /* 1 查询结果 */
    
    // 1.1 原生查询构建器
    NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
    
    // 1.2 source过滤
    queryBuilder.withSourceFilter(new FetchSourceFilter(new String[0], new String[0]));
    
    // 1.3 搜索条件
    queryBuilder.withQuery(QueryBuilders.matchQuery("title", "手机"));
    
    // 1.4 分页及排序条件
    queryBuilder.withPageable(PageRequest.of(0, 2, Sort.by(Sort.Direction.ASC, "price")));
    
    // 1.5 高亮显示
    // TODO
    
    // 1.6 聚合
    queryBuilder.addAggregation(AggregationBuilders.terms("brandAgg").field("brand"));
    
    // 1.7 构建查询条件,并且查询
    AggregatedPage<Product> result = template.queryForPage(queryBuilder.build(), Product.class);

    /* 2 解析结果 */
    
    // 2.1 分页结果
    long total = result.getTotalElements();
    int totalPages = result.getTotalPages();
    List<Product> list = result.getContent();
    System.out.println("总条数:" + total);
    System.out.println("总页数:" + totalPages);
    System.out.println("数据:" + list);
    
    // 2.2 聚合结果
    Aggregations aggregations = result.getAggregations();
    // 导包org.elasticsearch.search.aggregations.bucket.terms.Terms
    Terms terms = aggregations.get("brandAgg");
    terms.getBuckets().forEach(b -> {
        System.out.println("品牌:" + b.getKeyAsString());
        System.out.println("count:" + b.getDocCount());
    });
}

注意:上述查询不支持高亮结果。

2.运行nativeQuery()方法,输出结果见下:

总条数:2
总页数:1
数据:[Product(id=1, title=小米手机Mix, category=手机, brand=小米, price=2899.0, images=http://image.yx.com/12479122.jpg), Product(id=2, title=坚果手机R1, category=手机, brand=锤子, price=3699.0, images=http://image.yx.com/12479122.jpg)]
品牌:小米
count:1
品牌:锤子
count:1

三. 高亮显示

1.高亮显示需求

需求描述:查询title中包含“小米手机”的商品,并将title中对应的分词通过<span style='color:red'></span>标签进行高亮修饰。

2.高亮显示实现

1.在com.yx.mapper包下自定义搜索结果映射ProductSearchResultMapper类。

package com.yx.mapper;
import com.google.gson.Gson;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/** 自定义查询结果映射,用于处理高亮显示 */
public class ProductSearchResultMapper implements SearchResultMapper {
    /**
	 * 完成查询结果映射。将_source取出,然后放入高亮的数据
 	 */
    @Override
    public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
        // 记录总条数
        long totalHits = searchResponse.getHits().getTotalHits();
        
        // 记录列表(泛型) - 构建Aggregate使用
        List<T> list = new ArrayList<>();
        
        // 获取搜索结果(真正的的记录)
        SearchHits hits = searchResponse.getHits();
        
        for (SearchHit hit : hits) {
            if (hits.getHits().length <= 0) {
                return null;
            }
            
            // 将原本的JSON对象转换成Map对象
            Map<String, Object> map = hit.getSourceAsMap();
            
            // 获取高亮的字段Map
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            
            for (Map.Entry<String, HighlightField> highlightField : highlightFields.entrySet()) {
                // 获取高亮的Key
                String key = highlightField.getKey();
                // 获取高亮的Value
                HighlightField value = highlightField.getValue();
                // 实际fragments[0]就是高亮的结果,无需遍历拼接
                Text[] fragments = value.getFragments();
                // 因为高亮的字段必然存在于Map中,就是key值
                map.put(key, fragments[0].toString());
            }
            
            // 把Map转换成对象
            Gson gson = new Gson();
            T item = gson.fromJson(gson.toJson(map), aClass);
            list.add(item);
        }
        
        // 返回的是带分页的结果
        return new AggregatedPageImpl<>(list, pageable, totalHits);
    }
}

2.高亮实现。重构nativeQuery()方法,使用自定义查询结果映射,来实现高亮显示。

@Test
public void nativeQuery() {
    /* 1 查询结果 */
    
    // 1.1 原生查询构建器
    NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
    
    // 1.2 source过滤
    queryBuilder.withSourceFilter(new FetchSourceFilter(new String[0], new String[0]));
    
    // 1.3 搜索条件
    queryBuilder.withQuery(QueryBuilders.matchQuery("title", "手机"));
    
    // 1.4 分页及排序条件
    queryBuilder.withPageable(PageRequest.of(0, 2, Sort.by(Sort.Direction.ASC, "price")));
    
    // 1.5 高亮显示
    HighlightBuilder.Field field = new HighlightBuilder.Field("title");
    field.preTags("<span style='color:red'>");
    field.postTags("</span>");
    queryBuilder.withHighlightFields(field);
    
    // 1.6 聚合
    queryBuilder.addAggregation(AggregationBuilders.terms("brandAgg").field("brand"));
    
    // 1.7 构建查询条件,并且查询
    // AggregatedPage<Product> result = template.queryForPage(queryBuilder.build(), Product.class);
    AggregatedPage<Product> result = template.queryForPage(queryBuilder.build(), Product.class, new ProductSearchResultMapper());

    /* 2 解析结果 */
    
    // 2.1 分页结果
    long total = result.getTotalElements();
    int totalPages = result.getTotalPages();
    List<Product> list = result.getContent();
    System.out.println("总条数:" + total);
    System.out.println("总页数:" + totalPages);
    System.out.println("数据:" + list);
    
    // 2.2 聚合结果
    /*
    Aggregations aggregations = result.getAggregations();
    // 导包org.elasticsearch.search.aggregations.bucket.terms.Terms
    Terms terms = aggregations.get("brandAgg");
    terms.getBuckets().forEach(b -> {
    System.out.println("品牌:" + b.getKeyAsString());
    System.out.println("count:" + b.getDocCount());
    });
	*/
}

3.运行nativeQuery()测试方法后,输出结果见下。

总条数:2
总页数:1
数据:[Product(id=1, title=小米<span style='color:red'>手机</span>Mix, category=手机, brand=小米, price=2899.0, images=http://image.yx.com/12479122.jpg), Product(id=2, title=坚果<span style='color:red'>手机</span>R1, category=手机, brand=锤子, price=3699.0, images=http://image.yx.com/12479122.jpg)]

四. 结语

Spring Data Elasticsearch基于spring data API大大简化了Elasticsearch的操作,从而简化开发人员的代码,提高开发效率。然后我们给大家介绍了使用Spring Data对Elasticsearch进行了增、删、改、查操作。

同学们,关于Elasticsearch的所有内容袁老师就给大家介绍到这里。还有很多前沿技术等着大家去探索,“路漫漫其修远昔,吾将上下而求索”。最后,祝愿各位小伙伴未来学业有成,一切如意!

今天的内容就分享到这里吧。关注「袁庭新」,干货天天都不断!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1638281.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

探索AI工具的巅峰:个人体验与深度剖析

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

【Go语言快速上手(六)】管道, 网络编程,反射,用法讲解

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:Go语言专栏⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多Go语言知识   &#x1f51d;&#x1f51d; GO快速上手 1. 前言2. 初识管道3. 管…

自定义SpringBoot的starter

案例需求&#xff1a;自定义redis-stater。要求当导入redis坐标时&#xff0c;SpringBoot自动创建Jedis的Bean。 实现步骤&#xff1a; 1、创建redis-spring-boot-autoconfigure模块 2、创建redis-spring-boot-starter模块&#xff0c;依赖redis-spring-boot-autoconfigure的…

Codeforces Round 938 (Div. 3)H-The Most Reckless Defense

来源 题目 You are playing a very popular Tower Defense game called "Runnerfield 2". In this game, the player sets up defensive towers that attack enemies moving from a certain starting point to the players base. You are given a grid of size nm&a…

Delta lake with Java--利用spark sql操作数据2

上一篇文章尝试了建库&#xff0c;建表&#xff0c;插入数据&#xff0c;还差删除和更新&#xff0c;所以在这篇文章补充一下&#xff0c;代码很简单&#xff0c;具体如下&#xff1a; import org.apache.spark.sql.SaveMode; import org.apache.spark.sql.SparkSession;publi…

DRF中的请求入口分析及request对象分析

DRF中的请求入口分析及request对象分析 django restframework框架是在django的基础上又给我们提供了很多方便的功能&#xff0c;让我们可以更便捷基于django开发restful API 1 drf项目 pip install django pip install djangorestframework1.1 核心配置 INSTALLED_APPS [d…

【源码阅读】Golang中的go-sql-driver库源码探究

文章目录 前言一、go-sql-driver/mysql1、驱动注册&#xff1a;sql.Register2、驱动实现&#xff1a;MysqlDriver3、RegisterDialContext 二、总结 前言 在上篇文章中我们知道&#xff0c;database/sql只是提供了驱动相关的接口&#xff0c;并没有相关的具体实现&#xff0c;具…

PG数据库结构与oracle比较

1.数据库集簇逻辑结构 数据库集簇概念&#xff1a;一个大的数据库是由若干个小的数据库组成&#xff0c;实现数据的隔离存放&#xff0c;在概念上应该是与mysql一样的 在mysql中可以用show database列出数据库 PG中用\l 数据库对象存放在数据库中&#xff1a; PG中的所有数据…

Mac 上安装多版本的 JDK 且实现 自由切换

背景 当前电脑上已经安装了 jdk8; 现在再安装 jdk17。 期望 完成 jdk17 的安装&#xff0c;并且完成 环境变量 的配置&#xff0c;实现自由切换。 前置补充知识 jdk 的安装路径 可以通过查看以下目录中的内容&#xff0c;确认当前已经安装的 jdk 版本。 cd /Library/Java/Java…

Maven3.9.6下载安装教程

(/≧▽≦)/~┴┴ 嗨~我叫小奥 ✨✨✨ &#x1f440;&#x1f440;&#x1f440; 个人博客&#xff1a;小奥的博客 &#x1f44d;&#x1f44d;&#x1f44d;&#xff1a;个人CSDN ⭐️⭐️⭐️&#xff1a;Github传送门 &#x1f379; 本人24应届生一枚&#xff0c;技术和水平有…

Typescript精进:前端必备的5大技巧(AI写作)

首先&#xff0c;这篇文章是基于笔尖AI写作进行文章创作的&#xff0c;喜欢的宝子&#xff0c;也可以去体验下&#xff0c;解放双手&#xff0c;上班直接摸鱼~ 按照惯例&#xff0c;先介绍下这款笔尖AI写作&#xff0c;宝子也可以直接下滑跳过看正文~ 笔尖Ai写作&#xff1a;…

瑞_23种设计模式_解释器模式

文章目录 1 解释器模式&#xff08;Interpreter Pattern&#xff09;1.1 介绍1.2 概述1.2.1 文法&#xff08;语法&#xff09;规则1.2.2 抽象语法树 1.3 解释器模式的结构1.4 解释器模式的优缺点1.5 解释器模式的使用场景 2 案例一2.1 需求2.2 代码实现 3 案例二3.1 需求3.2 代…

【右一的开发日记】全导航,持续更新...

文章目录 &#x1f4da;前端【跟课笔记】&#x1f407;核心技术&#x1f407;高级技术 &#x1f4da;捣鼓捣鼓&#x1f407;小小案例&#x1f407;喵喵大王立大功&#x1f407;TED自用学习辅助网站&#x1f407;世界top2000计算机科学家可视化大屏&#x1f407;基于CBDB的唐代历…

【Java EE】MyBatis使用注解操作数据库

文章目录 &#x1f340;参数传递&#x1f334;增(Insert)&#x1f338;返回主键 &#x1f343;删(Delete)&#x1f333;改(Update)&#x1f332;查(Select)&#x1f338;起别名&#x1f338;结果映射&#x1f338;开启驼峰命名(推荐) ⭕总结 &#x1f340;参数传递 需求: 查找…

【JavaEE】进程的概念

文章目录 1、什么是进程&#xff08;Process&#xff09;2、PCB1.pid进程的id/标识符2.内存指针3.文件描述符表4、进程调度4.1状态4.2优先级4.3上下文4.4记账信息 1、什么是进程&#xff08;Process&#xff09; 一个程序&#xff0c;运行起来/跑起来&#xff0c;在操作系统中…

Delta lake with Java--利用spark sql操作数据1

今天要解决的问题是如何使用spark sql 建表&#xff0c;插入数据以及查询数据 1、建立一个类叫 DeltaLakeWithSparkSql1&#xff0c;具体代码如下&#xff0c;例子参考Delta Lake Up & Running第3章内容 import org.apache.spark.sql.SaveMode; import org.apache.spark.…

Ollamallama

Olllama 直接下载ollama程序&#xff0c;安装后可在cmd里直接运行大模型&#xff1b; llama 3 meta 开源的最新llama大模型&#xff1b; 下载运行 1 ollama ollama run llama3 2 github 下载仓库&#xff0c;需要linux环境&#xff0c;windows可使用wsl&#xff1b; 接…

面试:Spring(IOC、AOP、事务失效、循环引用、SpringMVC、SpringBoot的自动配置原理、Spring框架常见注解)

目录 一、Spring的单例Bean是否是线程安全的&#xff1f; 二、什么是AOP 1、介绍 &#xff08;1&#xff09;记录操作日志 &#xff08;2&#xff09;实现Spring中的事务 三、spring中事务失效的场景有哪些&#xff1f; 1、异常捕获处理 2、抛出检查异常 3、非public方…

ElasticSearch教程入门到精通——第四部分(基于ELK技术栈elasticsearch 7.x新特性)

ElasticSearch教程入门到精通——第四部分&#xff08;基于ELK技术栈elasticsearch 7.x新特性&#xff09; 1. Elasticsearch进阶1.1 核心概念1.1.1 索引Index1.1.1.1 索引创建原则1.1.1.2 Inverted Index 1.1.2 类型Type1.1.3 文档Document1.1.4 字段Field1.1.5 映射Mapping1.…

【Mac】Mac安装软件常见问题解决办法

前言 刚开始用Mac系统的小伙伴或者在更新系统版本后运行App的朋友会经常碰到弹窗提示「xxx已损坏&#xff0c;无法打开&#xff0c;您应该将它移到废纸篓」、「打不开xxx&#xff0c;因为Apple无法检查其是否包含恶意软件」、「打不开xxx&#xff0c;因为它来自身份不明的开发…