Spring Data Elasticsearch Demo

Spring Data Elasticsearch



环境

Elasticsearch7.8.0

SpringBoot2.3.6

1 创建测试环境

1.1 添加依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>cn.iforeverhz</groupId>
    <artifactId>es-spring</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

</project>

1.2 添加配置

@Configuration
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration {

    @Override
    public RestHighLevelClient elasticsearchClient() {
        RestHighLevelClient restHighLevelClient = new RestHighLevelClient(
                RestClient.builder(new HttpHost("localhost",9200))
        );
        return restHighLevelClient;
    }
}

2 实体类及注解

@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "goods" ,shards = 3,replicas = 1 )
public class Item {
    @Field(type = FieldType.Long)
    @Id
    Long id;
    @Field(type = FieldType.Text,analyzer = "ik_max_word")
    String title; //标题
    @Field(type = FieldType.Keyword)
    String category;// 分类
    @Field(type = FieldType.Keyword)
    String brand; // 品牌
    @Field(type = FieldType.Double)
    Double price; // 价格
    @Field(index = false, type = FieldType.Keyword )
    String images; // 图片地址
}

Spring Data通过注解来声明字段的映射属性,有下面的三个注解:

  • @Document 作用在类,标记实体类为文档对象,一般有两个属性
    • indexName:对应索引库名称
    • shards:分片数量,默认1
    • replicas:副本数量,默认1
  • @Id 作用在成员变量,标记一个字段作为id主键
  • @Field 作用在成员变量,标记为文档的字段,并指定字段映射属性:
    • type:字段类型,取值是枚举:FieldType
    • index:是否索引,布尔类型,默认是true
    • store:是否存储,布尔类型,默认是false
    • analyzer:分词器名称

3 创建Repository

@Repository
public interface ItemRepository extends ElasticsearchRepository<Item,Long> {

    /**
     * 根据价格区间查询
     * @param price1
     * @param price2
     * @return
     */
    List<Item> findByPriceBetween(double price1, double price2);

}

ElasticsearchRepository继承关系

image-20220426115550872

4 索引操作

4.1 创建索引

    public void createIndex() {
        // 创建索引库1 方法过时
        // template.createIndex(Item.class);
        // 映射关系 方法过时
        //template.putMapping(Item.class);

        // 创建索引2
        IndexOperations indexOperations = template.indexOps(Item.class);
        if (indexOperations.exists()) {
            System.out.println("索引已存在");
            return;
        }
        indexOperations.create();
        System.out.println("索引创建成功");
    }

4.2 删除索引

    public void deleteIndex() {
        //System.out.println(template.deleteIndex(Item.class));

        IndexOperations indexOperations = template.indexOps(Item.class);
        if (indexOperations.exists()) {
            if (indexOperations.delete()) {
                System.out.println("索引删除成功");
            }else {
                System.out.println("索引删除失败");
            }
            return;
        }
        System.out.println("索引不存在");
    }

5 文档操作

5.1 新增文档

    public void addDoc() {
        Item item = new Item(1L, "小米手机7", "手机", "小米", 4299.00, "http://image.iforeverhz.com/13123.jpg");
        Item save = repository.save(item);
        System.out.println(save);
    }

5.2 批量新增文档

    public void addBulkDoc() {
        List<Item> list = new ArrayList<>();
        list.add(new Item(1L, "小米手机7", "手机", "小米", 3299.00, "http://image.iforeverhz.com/13123.jpg"));
        list.add(new Item(2L, "坚果手机R1", "手机", "锤子", 3699.00, "http://image.iforeverhz.com/13123.jpg"));
        list.add(new Item(3L, "华为META10", "手机", "华为", 4499.00, "http://image.iforeverhz.com/13123.jpg"));
        list.add(new Item(4L, "小米Mix2S", "手机", "小米", 4299.00, "http://image.iforeverhz.com/13123.jpg"));
        list.add(new Item(5L, "荣耀V10", "手机", "华为", 2799.00, "http://image.iforeverhz.com/13123.jpg"));
        list.add(new Item(6L, "小米手机7", "手机", "小米", 3299.00, "http://image.iforeverhz.com/13123.jpg"));
        list.add(new Item(7L, "坚果手机R1", "手机", "锤子", 3699.00, "http://image.iforeverhz.com/13123.jpg"));
        list.add(new Item(8L, "华为META10", "手机", "华为", 4499.00, "http://image.iforeverhz.com/13123.jpg"));
        list.add(new Item(9L, "小米Mix2S", "手机", "小米", 4299.00, "http://image.iforeverhz.com/13123.jpg"));
        list.add(new Item(10L, "荣耀V10", "手机", "华为", 2799.00, "http://image.iforeverhz.com/13123.jpg"));
        // 接收对象集合,实现批量新增
        repository.saveAll(list);
    }

5.3 修改文档

    public void updateDoc() {
        Item item = new Item(1L, "小米手机7", "手机", "小米", 5299.00, "http://image.iforeverhz.com/13123.jpg");
        Item save = repository.save(item);
        System.out.println(save);
    }

5.4 删除文档

    public void delete() {
        // 根据id删除
        repository.deleteById(1L);
        // 根据item删除doc
        Item item = new Item();
        item.setId(2l);
        repository.delete(item);
        // 删除所有
        repository.deleteAll();
        
        // 删除指定的doc
        List<Item> list = new ArrayList<>();
        list.add(new Item(1L, "小米手机7", "手机", "小米", 3299.00, "http://image.iforeverhz.com/13123.jpg"));
        list.add(new Item(2L, "坚果手机R1", "手机", "锤子", 3699.00, "http://image.iforeverhz.com/13123.jpg"));
        repository.deleteAll(() -> list.stream().iterator());        
    }

5.5 基本查询

5.5.1 查询所有
    public void findAll() {
        // 查询所有
        Iterable<Item> items = repository.findAll();
        items.forEach(item -> System.out.println(item));

        // 查询所有并按照价格升序排序
        Iterable<Item> items1 = repository.findAll(Sort.by(Sort.Direction.ASC, "price"));
        // 查询所有并按照价格降序排序
        Iterable<Item> items2 = repository.findAll(Sort.by(Sort.Direction.DESC, "price"));
        items1.forEach(System.out::println);
        items2.forEach(System.out::println);

        // 查询所有并分页
        int page = 0;
        int size = 3;
        Page<Item> itemPage = repository.findAll(PageRequest.of(page, size));
        // 解析分页结果
        // 分页结果 第1页
        List<Item> items4 = itemPage.getContent();
        // 总页数
        int totalPages = itemPage.getTotalPages();
        // 总数
        long totalElements = itemPage.getTotalElements();

        System.out.println(totalElements+"==="+totalPages);

        items4.forEach(System.out::println);

    }
5.5.2 根据id查询
    public void findById() {
        Optional<Item> optionalItem = repository.findById(1L);
        optionalItem.ifPresent(item -> System.out.println(item));
    }
5.5.3 自定义方法查询

Spring Data 的另一个强大功能,是根据方法名称自动实现功能。

比如:你的方法名叫做:findByTitle,那么它就知道你是根据title查询,然后自动帮你完成,无需写实现类。

当然,方法名称要符合一定的约定:

KeywordSampleElasticsearch Query String
AndfindByNameAndPrice{"bool" : {"must" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}}
OrfindByNameOrPrice{"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}}
IsfindByName{"bool" : {"must" : {"field" : {"name" : "?"}}}}
NotfindByNameNot{"bool" : {"must_not" : {"field" : {"name" : "?"}}}}
BetweenfindByPriceBetween{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}
LessThanEqualfindByPriceLessThan{"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}
GreaterThanEqualfindByPriceGreaterThan{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}}
BeforefindByPriceBefore{"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}
AfterfindByPriceAfter{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}}
LikefindByNameLike{"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}}
StartingWithfindByNameStartingWith{"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}}
EndingWithfindByNameEndingWith{"bool" : {"must" : {"field" : {"name" : {"query" : "*?","analyze_wildcard" : true}}}}}
Contains/ContainingfindByNameContaining{"bool" : {"must" : {"field" : {"name" : {"query" : "**?**","analyze_wildcard" : true}}}}}
InfindByNameIn(Collection<String>names){"bool" : {"must" : {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}}
NotInfindByNameNotIn(Collection<String>names){"bool" : {"must_not" : {"bool" : {"should" : {"field" : {"name" : "?"}}}}}}
NearfindByStoreNearNot Supported Yet !
TruefindByAvailableTrue{"bool" : {"must" : {"field" : {"available" : true}}}}
FalsefindByAvailableFalse{"bool" : {"must" : {"field" : {"available" : false}}}}
OrderByfindByAvailableTrueOrderByNameDesc{"sort" : [{ "name" : {"order" : "desc"} }],"bool" : {"must" : {"field" : {"available" : true}}}}

例如,我们来按照价格区间查询,定义这样的一个方法:

@Repository
public interface ItemRepository extends ElasticsearchRepository<Item,Long> {

    /**
     * 根据价格区间查询
     * @param price1
     * @param price2
     * @return
     */
    List<Item> findByPriceBetween(double price1, double price2);

}
    public void query() {
        List<Item> items = repository.findByPriceBetween(2000, 3500D);
        items.forEach(System.out::println);
    }

5.6 高级查询

5.6.1基本查询
    public void termQuery() {
        // 词条查询
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("brand", "小米");
        // 执行查询
        Iterable<Item> items = repository.search(termQueryBuilder);
        items.forEach(System.out::println);
    }

QueryBuilders提供了大量的静态方法,用于生成各种不同类型的查询对象,例如:词条、模糊、通配符等QueryBuilder对象。

5.6.2 分页&排序&结果过滤查询

使用repository

    public void testQuery() {
        // 构建查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 结果过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "title", "price"}, null));
        // 添加查询条件
        queryBuilder.withQuery(QueryBuilders.matchQuery("title", "小米手机")); // title == "小米手机"
        // 排序
        queryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.DESC)); // 根据价格降序排序
        // 分页
        queryBuilder.withPageable(PageRequest.of(0, 2));
        // 方法过时
        Page<Item> items = repository.search(queryBuilder.build());
        // 总条数
        long totalElements = items.getTotalElements();
        // 总页数
        int totalPages = items.getTotalPages();
        System.out.println("总条数:" + totalElements);
        System.out.println("总页数:" + totalPages);
        items.forEach(item -> System.out.println(item));
    }

使用template

    public void testQuery1() {
        // 构建查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 结果过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "title", "price"}, null));
        // 添加查询条件
        queryBuilder.withQuery(QueryBuilders.matchQuery("title", "小米手机")); // title == "小米手机"
        // 排序
        queryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.DESC)); // 根据价格降序排序
        // 分页
        queryBuilder.withPageable(PageRequest.of(0, 2));
        // 执行查询
        SearchHits<Item> searchHits = template.search(queryBuilder.build(), Item.class);
        // 总数
        long totalHits = searchHits.getTotalHits();
        // 结果
        List<Item> itemList = searchHits.get()
                .map(SearchHit::getContent)
                .collect(Collectors.toList());
        System.out.println(totalHits);
        itemList.forEach(System.out::println);
    }

NativeSearchQueryBuilder:Spring提供的一个查询条件构建器,帮助构建json格式的请求体

5.6.3 聚合查询
5.6.3.1 聚合为桶

不推荐使用repository

    public void testAgg(){
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 不查询任何结果
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
        // 1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand
        queryBuilder.addAggregation(
                AggregationBuilders.terms("brands").field("brand"));
        // 2、查询,需要把结果强转为AggregatedPage类型 方法已过时
        AggregatedPage<Item> aggPage = (AggregatedPage<Item>) repository.search(queryBuilder.build());
        // 3、解析
        // 3.1、从结果中取出名为brands的那个聚合,
        ParsedStringTerms agg = (ParsedStringTerms) aggPage.getAggregation("brands");
        // 3.2、获取桶
        List<? extends Terms.Bucket> buckets = agg.getBuckets();
        // 3.3、遍历

        for (Terms.Bucket bucket : buckets) {
            System.out.println(bucket.getKeyAsString()+"==="+bucket.getDocCount());
        }

    }

推荐使用template

    public void testAgg() {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 不查询任何结果
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{}, null));
        // 1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand
        queryBuilder.addAggregation(AggregationBuilders.terms("brands").field("brand"));

        // 2 查询
        SearchHits<Item> searchHits = template.search(queryBuilder.build(), Item.class);
        // 3 解析
        Aggregations aggregations = searchHits.getAggregations();
        // 3.1 从结果中取出名为brands的那个聚合,
        Terms agg = (Terms) aggregations.get("brands");
        // 3.2 获取桶
        for (Terms.Bucket bucket : agg.getBuckets()) {
            String key = bucket.getKeyAsString();
            long count = bucket.getDocCount();

            System.out.println(key + "==" + count);
        }
    }

结果

image-20220426113005335

5.6.3.2 嵌套聚合

根据品牌求价格平均值

    public void testAgg() {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 不查询任何结果
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{}, null));
        // 1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand
        queryBuilder.addAggregation(
                AggregationBuilders.terms("brands").field("brand")
                        .subAggregation(AggregationBuilders.avg("priceAvg").field("price"))
        );

        // 2 查询
        SearchHits<Item> searchHits = template.search(queryBuilder.build(), Item.class);
        // 3 解析
        Aggregations aggregations = searchHits.getAggregations();
        // 3.1 从结果中取出名为brands的那个聚合,
        Terms agg = (Terms) aggregations.get("brands");
        // 3.2 获取桶
        for (Terms.Bucket bucket : agg.getBuckets()) {
            String key = bucket.getKeyAsString();
            long count = bucket.getDocCount();
            // 获取嵌套查询结果
            ParsedAvg priceAvg = bucket.getAggregations().get("priceAvg");
            System.out.println(key + "==" + count + "==" + priceAvg.getValue());
        }
    }

根据品牌求价格总合

    public void testAgg() {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 不查询任何结果
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{}, null));
        // 1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand
        queryBuilder.addAggregation(
                AggregationBuilders.terms("brands").field("brand")
                        .subAggregation(AggregationBuilders.avg("priceAvg").field("price"))
                        .subAggregation(AggregationBuilders.sum("priceSum").field("price"))
        );

        // 2 查询
        SearchHits<Item> searchHits = template.search(queryBuilder.build(), Item.class);
        // 3 解析
        Aggregations aggregations = searchHits.getAggregations();
        // 3.1 从结果中取出名为brands的那个聚合,
        Terms agg = (Terms) aggregations.get("brands");
        // 3.2 获取桶
        for (Terms.Bucket bucket : agg.getBuckets()) {
            // 品牌名
            String key = bucket.getKeyAsString();
            // 数量
            long count = bucket.getDocCount();
            // 获取嵌套查询结果
            // 价格平均值
            ParsedAvg priceAvg = bucket.getAggregations().get("priceAvg");
            // 价格总计
            ParsedSum priceSum = bucket.getAggregations().get("priceSum");
            System.out.println(key + "==" + count +"=="+priceSum.getValue()+"=="+ priceAvg.getValue());
        }
    }

结果

image-20220426114215185

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值