Spring Data Elasticsearch - 在Spring应用中操作Elasticsearch数据库

这篇具有很好参考价值的文章主要介绍了Spring Data Elasticsearch - 在Spring应用中操作Elasticsearch数据库。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Spring Data Elasticsearch


Spring Data Elasticsearch为文档的存储,查询,排序和统计提供了一个高度抽象的模板。使用Spring Data ElasticSearch来操作Elasticsearch,可以较大程度的减少我们的代码量,提高我们的开发效率。

要使用Elasticsearch我们需要引入如下依赖:

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

还需要在配置文件中增加如下配置

spring:
  elasticsearch:
    rest:
      # elasticsearch server的地址
      uris: 192.168.0.102:9200
      # 连接超时时间
      connection-timeout: 6s
      # 访问超时时间
      read-timeout: 10s

1. 定义文档映射实体类

类比于MyBatis-Plus可以定义实体类去映射数据库中的表中的数据,使用Spring Data Elasticsearch时,我们也可以通过定义一个实体类映射ES索引中的文档。

@Data
@Document(indexName = "goods", shards = 1, replicas = 0)
public class Goods {
    // 商品Id skuId _id
    @Id
    private Long id;

    @Field(type = FieldType.Keyword, index = false)
    private String defaultImg;

    // elasticsearch 中能分词的字段,这个字段数据类型必须是 text!keyword 不分词!
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String title;

    @Field(type = FieldType.Double)
    private Double price;

    @Field(type = FieldType.Long)
    private Long tmId;

    @Field(type = FieldType.Keyword)
    private String tmName;

    @Field(type = FieldType.Keyword)
    private String tmLogoUrl;

    @Field(type = FieldType.Long)
    private Long firstLevelCategoryId;

    @Field(type = FieldType.Keyword)
    private String firstLevelCategoryName;

    @Field(type = FieldType.Long)
    private Long secondLevelCategoryId;

    @Field(type = FieldType.Keyword)
    private String secondLevelCategoryName;

    @Field(type = FieldType.Long)
    private Long thirdLevelCategoryId;

    @Field(type = FieldType.Keyword)
    private String thirdLevelCategoryName;

    // 商品的热度! 我们将商品被用户点查看的次数越多,则说明热度就越高!
    @Field(type = FieldType.Long)
    private Long hotScore = 0L;

    // 平台属性集合对象
    // Nested 支持嵌套查询
    @Field(type = FieldType.Nested)
    private List<SearchAttr> attrs;

}
/*
   该类映射nested平台属性
*/
@Data
public class SearchAttr {
    // 平台属性Id
    @Field(type = FieldType.Long)
    private Long attrId;
    // 平台属性值名称
    @Field(type = FieldType.Keyword)
    private String attrValue;
    // 平台属性名
    @Field(type = FieldType.Keyword)
    private String attrName;
}

在Goods类上,通过添加@Document注解,我们将Goods类映射的文档所属的索引:

  • @Document注解的indexName属性,用来定义实体类所映射的文档所属的目标索引名称
  • @Document的shards属性,表示目标索引的住分片数量
  • @Document的replicas属性,表示每个主分片所拥有的副本分片的数量

在Goods类的成员变量Id上通过添加@Id注解指定,Id成员变量映射到Goods索引中文档的id字段,同时也映射到文档的唯一表示_id字段。

在Goods类的其他成员变量上,通过添加@Field注解,定义成员变量和文档字段的映射关系:

  • 默认同名成员变量,映射到文档中的同名字段(也可以由@Field注解的name属性显示指定)
  • 通过@Field注解的type属性指定文档中同名字段的数据类型
  • 通过@Field注解的analyzer属性,指定成员变量所映射的文档字段所使用的分词器

2. Repository

类比于Mybatis-Plus中定义BaseMaper子接口即可对单表做增删改查的操作,Spring Data Elastisearch中我们可以通过定义ElasticsearchRepository子接口,迅速实现对索引中的文档数据的增删改查,以及通过自定义方法,实现自定义查询。

public interface GoodsRepository extends ElasticsearchRepository<Goods,Long> {

}

ElasticsearchRepository接口需要接收两个泛型,第一个泛型即映射实体类,第二个泛型是在实体类中加了@Id注解的成员变量的数据类型,即映射到文档唯一标识_id字段的成员变量类型。

一旦我们定义好了ElasticsearchRepository的子接口,马上就可以实现对goods索引中文档的增删改查功能

    // 注入repository对象
    @Autowired
    private GoodsRepository goodsRepository;

    
    // 保存单个文档对象
    Goods good = ....
    goodsRepository.save(good);



    // 批量保存多个文档对象
    List<Goods> goods = ...
    goodsRepository.save(goods);

   // 根据id查询
   goodsRepository.findById(id);

   // 根据id删除
   goodsRepository.deleteById(id);
    

同时,还需要注意一点,一旦定义好了ElasticsearchRepository接口,而且被SpringBoot启动类扫描到,那么在应用启动的时候,如果ElasticsearchRepository子接口所访问的索引在ES中不存在,Spring Data Elasticsearch会在ES中自动创建索引,并根据映射实体类定义索引的映射

但是,大多数时候,我们可能需要对索引中的文档数据做自定义查询,此时仅仅使用ElasticsearchRepository接口中继承的方法无法满足我们的需求。此时就需要在自己的Repository接口中,通过自定义方法来实现各种自定义查询。

  • 利用@Query注解自定义查询脚本
  /* 
     1. 通过Query注解定义具体的查询字符串(也可以替换为其他查询)
     2. 字符串中的?0是固定格式,表示第0个参数的占位符,在实际查询时会被方法的第一个参数值title的值替换,         如果有多个参数,依次类推即可
     3. List<Goods>
     // 模糊查询
	 @Query("{\"fuzzy\": {\"title\": \"?0\"}}")  
	 // 范围查询
     @Query("{\"range\": {\"price\": {\"gte\": ?0, \"lte\": ?1}}}") 
	 // 前缀查询
	 @Query("{\"prefix\": {\"title\": \"?0\"}}")
    */
    @Query(
                "{ " +
                        "\"match\": {\n" +
                    "      \"title\": \"?0\"\n" +
                            "}" +
                        "}"
    )
    List<Goods> matchSearch(String title);

这里的@Query注解中,只需要包含我们查询脚本中"query"{}里面的内容即可,比如上面的@Query注解所表示的查询等价于

GET goods/_search
{
  "query": {
    "match": {
      "title": 具体待查询的参数值
    }
  }
}
    
	@Autowired
    ProductRepository productRepository;

	@Test
    public void testMatchSearch() {
        // 在调用的时候传递查询的参数值
        List<Goods> list = goodsRepository.matchSearch("荣耀手机");
        System.out.println(list);
    }
  • 利用@Query注解结合分页参数,实现分页查询
    /*
          1. 针对一个查询结果,返回对应的一页数据
          2. Pageable参数是当想要获取分页数据的时候,必须携带的参数,表示分页信息
             比如,查询第多少页数据,每页多少条数据等,该参数不会用来替换我们的@Query字符串中的参数
          3. 返回的结果是一个包含一页文档数据的Page对象
     */
    @Query(
            " {" +
                    "   \"match\": {\n" +
                    "      \"title\": \"?0\"\n" +
                    "    }" +
                    "}"
    )
    Page<Goods> testSearchPage(String title, Pageable pageable);
    // 注入repository对象
    @Autowired
    private GoodsRepository goodsRepository;
   /*
          测试分页查询
     */
    @Test
    public void testSearchPage() {
        // 创建表示分页信息的Pageable对象
        // 表示查询第几页数据,这里一定要注意,页数是从0开始算的
        int page = 0;
        // 每页假设10个文档
        int pageCount = 10;
  
        // 调用Sort方法得到Sort对象,一个Sort对象表示
        Sort sort = Sort.by(Sort.Direction.ASC, "price");
        // PageRequest 是 Pageable接口子类对象
         PageRequest pageInfo = PageRequest.of(page, pageCount,sort);

        // 这里的Page对象可以被看做是List
        Page<Goods> pageResult = goodsRepository.testSearchPage("小米手机", pageInfo);

        // 遍历集合,从每个SearchHit对象中取出文档对象,
        // 如果需要返回可以在遍历的时候将其,放入一个List中返回
        pageResult.forEach( goods -> {
            // 访问查询到的一条文档

            // ...
        });

        // 获取满足条件的总的文档数量
        long totalElements = pageResult.getTotalElements();

    }
  • @Query注解 + @Highlight + 分页参数实现高亮,分页自定义查询
    @Query(
            "  {  \"match\": {\n" +
                    "      \"title\": \"?0\"\n" +
                    "    }" +
                    "}"
    )
    @Highlight(
            fields = {
                    @HighlightField(name = "title",
                            parameters = @HighlightParameters(
                                    preTags = "<font color='red'>", postTags = "</font>"))
            }
    )
    List<SearchHit<Goods>> testHighlight(String title, Pageable pageable);
@Test
    public void testHighlight() {
        // 分页参数
        PageRequest of = PageRequest.of(0, 10);

        // 调用Repository方法获取搜索结果
        List<SearchHit<Goods>> result = goodsRepository.testHighlight("小米手机", of);

        // 结果集
        List<Goods> itemDocuments = new ArrayList<>();

        result.forEach(hit -> {
            // 获取目标文档
            Goods content = hit.getContent();
            // 获取高亮字段title对应的高亮字符串
            List<String> title = hit.getHighlightField("title");
            // 在文档对象中,用高亮字符串替换掉原来的值
            content.setTitle(title.get(0));
            // 加入结果集
            itemDocuments.add(content);
        });

        System.out.println(itemDocuments.size());

    }

虽然,testHighlight方法既实现了分页查询,又实现了高亮查询,但是有一个缺陷就是,该方法无法获取到满足查询条件的总的文档数量,它只会返回满足条件的一页文档数据。不知道满足条件的文档总数,前端就无法完成分页。

所以,很明显Repository好用,但是具有一定的局限性,如果面对比较复杂的查询,此时就只能使用Spring Data Elasticsearch提供的另外一个工具ElasticsearchRestTemplate了。

3. ElasticsearchRestTemplate

3.1 查询相关特性

3.1.1 过滤

使用BoolQuery进行过滤:

BoolQueryBuilder qb = QueryBuilders.boolQuery();
qb.filter(QueryBuilders.termQuery("price", 199)); // 过滤条件
3.1.2 排序

使用SortBuilders构建排序条件:

SortBuilder sort = SortBuilders.fieldSort("price").order(SortOrder.ASC);
3.1.3 自定义分词器

在字段上使用analyzer属性指定分词器:

@Field(analyzer = "ik_max_word") 
private String title;

3.2 高级查询

构造自定义分页,高亮,nested以及聚合查询,并发起请求

 
    @Autowired
    ElasticsearchRestTemplate restTemplate;

    @Autowired
    GoodsConverter goodsConverter;

    @Test
    public void testRestTemplate() {
        // 该Builder包含所有搜索请求的参数
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();


        // 获取bool查询Builder
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();


        // 构造bool查询中match查询
        MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("title", "小米手机");
        // 将该查询加入bool查询must中
        boolQueryBuilder.must(matchQuery);

        TermQueryBuilder subQueryForAttrNested = QueryBuilders.termQuery("attrs.attrValue", "8G");
        // 构造nested查询
        NestedQueryBuilder attrsNestedQuery = QueryBuilders.nestedQuery("attrs", subQueryForAttrNested, ScoreMode.None);
        // 将nested查询作为一个过滤条件
        boolQueryBuilder.filter(attrsNestedQuery);


        // 将整个bool查询添加到NativeSearchQueryBuilder
        queryBuilder.withQuery(boolQueryBuilder);

        // 构造分页参数
        //PageRequest price = PageRequest.of(0, 10, Sort.by(Sort.Order.desc("price")));
        PageRequest price = PageRequest.of(0, 10);
        // 向NativeSearchQueryBuilder添加分页参数
        queryBuilder.withPageable(price);

        // 按照指定字段值排序
        FieldSortBuilder priceSortBuilder = SortBuilders.fieldSort("price").order(SortOrder.ASC);
        queryBuilder.withSort(priceSortBuilder);

        // 构造高亮参数
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title").preTags("<font color='red'>").postTags("</font>");
        // 向NativeSearchQueryBuilder添加高亮参数
        queryBuilder.withHighlightBuilder(highlightBuilder);

        // 设置品牌聚合(平台属性等的聚合也是相同的方式)
        TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("tmIdAgg").field("tmId")
                .subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"))
                .subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogUrl"));


        // 向NativeSearchQueryBuilder添加聚合参数
        queryBuilder.addAggregation(termsAggregationBuilder);

        // 结果集过滤,只包含原始文档的id,defaultImg,title,price
        queryBuilder.withFields("id", "defaultImg", "title", "price");

        // 使用ElasticsearchRestTemplate发起搜索请求
        NativeSearchQuery build = queryBuilder.build();
        SearchHits<Goods> search = restTemplate.search(build, Goods.class);

        //封装所有的查询数据
        SearchResponseDTO searchResponseDTO = new SearchResponseDTO();

        // 获取满足条件的总文档数量
        long totalHits = search.getTotalHits();
        // 设置查询到的总文档条数
        searchResponseDTO.setTotal(totalHits);


        // 获取包含所有命中文档的SearchHit对象
        List<SearchHit<Goods>> searchHits = search.getSearchHits();


        // 处理搜索到的结果集即SearchHit<Goods>集合, 并使用高亮字符串替换
        List<GoodsDTO> goodsList = searchHits.stream().map(hit -> {
            // 获取命中的文档
            Goods content = hit.getContent();

            //获取高亮字段
            List<String> title = hit.getHighlightField("title");

            // 用高亮字段替换
            content.setTitle(title.get(0));
            // 将Goods对象转化为GoodsDTO对象
            GoodsDTO goodsDTO = goodsConverter.goodsPO2DTO(content);
            return goodsDTO;
        }).collect(Collectors.toList());

        // 设置查询到的结果列表
        searchResponseDTO.setGoodsList(goodsList);

        // 从品牌聚合中获取品牌集合

        // 根据id获取品牌id terms聚合结果
       Terms terms = search.getAggregations().get("tmIdAgg");
        List<SearchResponseTmDTO> trademarkList = terms.getBuckets().stream().map(tmIdBucket -> {
            // 封装品牌数据
            SearchResponseTmDTO searchResponseTmDTO = new SearchResponseTmDTO();

            String tmIdStr = tmIdBucket.getKeyAsString();
            // 获取品牌id
            Long tmId = Long.parseLong(tmIdStr);
            // 设置品牌id
            searchResponseTmDTO.setTmId(tmId);

            // 获取品牌名称聚合(子聚合)
            Terms tmNameAgg = tmIdBucket.getAggregations().get("tmNameAgg");
            // 通过聚合桶的名称获取品牌名称
            String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
            // 设置品牌名称
            searchResponseTmDTO.setTmName(tmName);


            // 获取品牌logo聚合(子聚合)
            Terms tmLogoUrlAgg = tmIdBucket.getAggregations().get("tmLogoUrlAgg");
            // 通过聚合桶的名称获取品牌名称
            String tmLogoUrl = tmLogoUrlAgg.getBuckets().get(0).getKeyAsString();
            // 设置品牌名称
            searchResponseTmDTO.setTmLogoUrl(tmLogoUrl);

            return searchResponseTmDTO;
        }).collect(Collectors.toList());

        // 设置聚合品牌数据
        searchResponseDTO.setTrademarkList(trademarkList);
        
        // .....

    }

4. 索引管理

4.1 创建索引

可以通过ElasticsearchRestTemplatecreateIndex方法创建索引:

// 创建索引,使用实体类进行映射
restTemplate.createIndex(Goods.class);

// 自定义索引设置
restTemplate.createIndex(Goods.class, c -> c
    .settings(s -> s 
        .put("index.number_of_shards", 3) // 指定主分片数
        .put("index.number_of_replicas", 2) // 指定副本分片数
    )
    .mapping(m -> m
        .put("dynamic", false) // 禁用动态映射
    )
);

4.2 检索索引

使用getIndex方法获取索引信息:

GetIndexResponse response = restTemplate.getIndex(Goods.class);
Map<String, Object> settings = response.getSettings();
Map<String, Object> mappings = response.getMappings();

4.3 修改映射

使用putMapping方法更新索引字段映射:

// 新增一个text字段
restTemplate.putMapping(Goods.class, m -> m.textField("newField"));

4.4 删除索引

使用deleteIndex删除索引:

restTemplate.deleteIndex(Goods.class);

5. 异常处理

使用@ExceptionHandler注解处理Elasticsearch异常:

@ExceptionHandler(ElasticsearchException.class)
public Response handleError(Exception e) {
 	// 处理异常逻辑
    return Response.status(500).build();
}

6. 性能优化

  • 加载批量文档,使用 bulk 或 batch 方式
  • 使用 scroll api 避免深分页问题
  • 控制请求缓存大小,不要缓存过多数据
  • 定期优化索引,提高查询性能

7. 应用案例

以电商网站的商品搜索为例:文章来源地址https://www.toymoban.com/news/detail-627788.html

  1. 定义商品索引、文档映射实体
  2. 构建查询,实现精确匹配、分词匹配、过滤、聚合等复杂查询
  3. 使用高级功能如Suggest完成自动补全
  4. 可视化分析商品热门趋势、用户行为数据

8. Spring Data Elasticsearch优势

  • 简化开发,不需要了解 ES 客户端细节
  • 查询方式灵活,支持自定义复杂 DSL 语句
  • 提供索引管理、错误处理等功能
  • 易于测试和扩展
  • 与 Spring Boot 无缝集成,开发效率高

到了这里,关于Spring Data Elasticsearch - 在Spring应用中操作Elasticsearch数据库的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【JavaEE & Spring】MyBatis 操作数据库(基础操作)

    使⽤MyBatis完成简单的增删改查操作, 参数传递. 掌握MyBatis的两种写法: 注解 和 XML⽅式 掌握MyBatis 相关的⽇志配置 在应⽤分层学习时, 我们了解到web应⽤程序⼀般分为三层,即:Controller、Service、Dao . 之前的案例中,请求流程如下: 浏览器发起请求, 先请求Controller, Controller接收

    2024年01月24日
    浏览(44)
  • Elasticsearch与NoSQL数据库的整合与应用

    Elasticsearch是一个基于分布式、实时、高性能的搜索和分析引擎,它可以处理大量数据并提供快速、准确的搜索结果。NoSQL数据库是一种不遵循关系型数据库的数据库,它可以处理大量不结构化的数据。在现代应用中,Elasticsearch与NoSQL数据库的整合和应用已经成为一种常见的技

    2024年02月20日
    浏览(39)
  • 数据库应用:MySQL数据库SQL高级语句与操作

    目录 一、理论 1.克隆表与清空表 2.SQL高级语句 3.SQL函数 4.SQL高级操作 5.MySQL中6种常见的约束 二、实验  1.克隆表与清空表 2.SQL高级语句 3.SQL函数 4.SQL高级操作 5.主键表和外键表  三、总结 克隆表:将数据表的数据记录生成到新的表中。 (1)克隆表 ① 先创建再导入 ② 创建

    2024年02月13日
    浏览(81)
  • Spring日志完结篇,MyBatis操作数据库(入门)

    目录 Spring可以对日志进行分目录打印 日志持久化(让日志进行长期的保存) MyBatis操作数据库(优秀的持久层框架) MyBatis的写法 开发规范: 单元测试的写法 传递参数 他的意思是说spring相关只打印INFO级别的标准,但是他也能获取到debug级别的日志 日志设置颜色(只能控制控制

    2024年02月04日
    浏览(38)
  • 原生语言操作和spring data中RestHighLevelClient操作Elasticsearch,索引,文档的基本操作,es的高级查询.查询结果处理. 数据聚合.相关性系数打分

    ​ Elasticsearch 是一个分布式、高扩展、高实时的搜索与数据分析引擎。它能很方便的使大量数据具有搜索、分析和探索的能力。充分利用Elasticsearch的水平伸缩性,能使数据在生产环境变得更有价值。Elasticsearch 的实现原理主要分为以下几个步骤,首先用户将数据提交到Elasti

    2024年02月05日
    浏览(86)
  • quarkus数据库篇之三:单应用同时操作多个数据库

    这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 一个应用同时连接多个数据库进行操作,这是常见的场景,quarkus也不例外,今天就随本文一起来实战多数据源操作 如下图,今天要创建名为 multi-db-demo 的应用,此应用同时连接两个数据库,名为

    2024年02月12日
    浏览(35)
  • 【Spring Boot】JdbcTemplate数据连接模板 — 使用JdbcTemplate操作数据库

    成功在Spring Boot项目中集成JdbcTemplate后,如何使用JdbcTemplate数据库连接模板操作数据库呢?接下来以示例演示JdbcTemplate实现学生信息的增、删、改、查等操作,让我们在实践中边学边用,更好地理解和吸收。 步骤01 创建实体类。 根据之前创建的Student表结构创建对应的实体类

    2024年02月12日
    浏览(65)
  • Spring Boot 数据库操作Druid和HikariDataSource

    目录 Spring Boot  数据库操作 应用实例-需求 创建测试数据库和表 进行数据库开发, 在pom.xml 引入data-jdbc starter 参考官方文档 需要在pom.xml 指定导入数据库驱动 在application.yml 配置操作数据源的信息 创建beanFurn.java 测试结果 整合Druid 到Spring-Boot  官方文档 Durid 基本使用 修改

    2024年02月06日
    浏览(106)
  • Spring Boot应用中如何动态指定数据库,实现不同用户不同数据库的场景

    当在 Spring Boot 应用程序中使用Spring Data JPA 进行数据库操作时,配置Schema名称是一种常见的做法。然而,在某些情况下,模式名称需要是动态的,可能会在应用程序运行时发生变化。比如:需要做数据隔离的SaaS应用。 所以,这篇博文将帮助您解决了在 Spring Boot 应用程序中如

    2024年04月26日
    浏览(48)
  • GPT应用-使用中文操作数据库

    本次尝试使用langchain来操作数据库; 下面是数据库相关的表,使用Mysql5.7 数据库,数据库名students 下面是相关表的介绍 学生表,有名字、分数、和老师的备注 学生父母表,其中有学生的名字,父母的电话 下面是notebook 内容的相关介绍 安装langchain和pymysql依赖 配置OPENAI_API_KE

    2024年02月07日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包