Spring Data Elasticsearch

15 篇文章 0 订阅
7 篇文章 0 订阅

Elasticsearch提供的Java客户端不太方便:

  • 很多地方需要拼接json字符串;
  • 需要自己把对象序列化为json存储;
  • 查询到结果也需要自己反序列化为对象;

所以不适用原生的Java客户端,而使用Spring提供的Spring Data Elasticsearch


简介

Spring Data Elasticsearch是Spring Data项目下的一个子模块。

官网:http://projects.spring.io/spring-data/

Spring Data的使命是微数据访问提供熟悉且一致基于Spring的编程模型,同时仍保留底层数据存储的特殊特性;
Spring Data的使命是给各种数据访问提供统一的编程接口,不断试关系型数据库还是菲关系型数据库,或者类似于Elasticsearch这样的索引数据库,从而简化开发人员的代码,提高开发效率。

Spring Data Elasticsearch的特征

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

创建一个Demo工程

添加依赖

<?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.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>es-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>es-demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </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</artifactId>
        </dependency>

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

实体类

@Data
public class Item {
    Long id;
    String title; //标题
    String category; //分类
    String brand; //品牌
    Double price; //价格
    String images; //图片地址
}

映射

SpringData 通过注解来声明字段的映射属性

  • @Document作用在类,标记实体类为文档对象,一般有两个属性

    • indexName:对应的索引库名;
    • type:对应在索引库中的类型;
    • shards:分片数量,默认为5;
    • replicas:副本数量,默认1
  • @Id:作用在成员变量,标记一个字段为id主键;

  • @Field:作用在成员变量,标记为文档的字段,并指定字段映射属性:

    • type:字段类型,取值为枚举FieldType
    • index:是否为索引,布尔类型,默认为true;
    • store:是都存储,布尔类型,默认为false;
    • analyzer:分词器名称

通过注解建立映射

@Data
@Document(indexName = "item",type = "docs",shards = 1,replicas = 0)
public class Item {
    @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(type = FieldType.Keyword,index = false)
    String images; //图片地址
}

Template索引操作

创建索引和映射

@RunWith(SpringRunner.class)
@SpringBootTest(classes = EsDemoApplication.class)
public class indexTest {

    @Autowired
    private ElasticsearchTemplate template;

    @Test
    public void testCreate(){
        //创建索引,会根据Item类的@Document注解信息来创建
        template.createIndex(Item.class);
        //配置映射,会根据Item类中的id,Field等字段来自动完成映射
        template.putMapping(Item.class);
    }
}

在这里插入图片描述

删除索引

在这里插入图片描述

可以根据类名或索引名删除;

Repository文档操作

Spring Data的强大之处在于不用写任何DAO处理,自动根据方法名或类的的信息进行CRUD,只要定义一个接口,然后继承Repository提供的一些子接口,就具备各种基本的CRUD功能;

创建接口并继承ElasticsearchRepository

public interface ItemRepository extends ElasticsearchRepository<Item,Long> {
}

新增文档


    @Test
    public void createDocument(){
        Item item = new Item(1L, "小米手机7", " 手机",
                "小米", 3499.00, "http://image.leyou.com/13123.jpg");
        repository.save(item);
    }

查看是否添加成功

在这里插入图片描述

批量新增

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

结果

{
  "took": 10,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 5,
    "max_score": 1,
    "hits": [
      {
        "_index": "item",
        "_type": "docs",
        "_id": "1",
        "_score": 1,
        "_source": {
          "id": 1,
          "title": "小米手机7",
          "category": "手机",
          "brand": "小米",
          "price": 3299,
          "images": "http://image.leyou.com/13123.jpg"
        }
      },
      {
        "_index": "item",
        "_type": "docs",
        "_id": "2",
        "_score": 1,
        "_source": {
          "id": 2,
          "title": "坚果手机R1",
          "category": "手机",
          "brand": "锤子",
          "price": 3699,
          "images": "http://image.leyou.com/13123.jpg"
        }
      },
      {
        "_index": "item",
        "_type": "docs",
        "_id": "3",
        "_score": 1,
        "_source": {
          "id": 3,
          "title": "华为META10",
          "category": "手机",
          "brand": "华为",
          "price": 4499,
          "images": "http://image.leyou.com/13123.jpg"
        }
      },
      {
        "_index": "item",
        "_type": "docs",
        "_id": "4",
        "_score": 1,
        "_source": {
          "id": 4,
          "title": "小米Mix2S",
          "category": "手机",
          "brand": "小米",
          "price": 4299,
          "images": "http://image.leyou.com/13123.jpg"
        }
      },
      {
        "_index": "item",
        "_type": "docs",
        "_id": "5",
        "_score": 1,
        "_source": {
          "id": 5,
          "title": "荣耀V10",
          "category": "手机",
          "brand": "华为",
          "price": 2799,
          "images": "http://image.leyou.com/13123.jpg"
        }
      }
    ]
  }
}

修改文档

修改和新增是同一个接口,区分的依据就是id,这一点跟在页面发起PUT请求时类似的;

基本查询

在这里插入图片描述

 @Test
    public void testFind(){
        //查询所有,并根据价格降序
        Iterable<Item> items = this.repository.findAll(Sort.by(Sort.Direction.DESC, "price"));
        items.forEach(item -> System.out.println(item));
    }

在这里插入图片描述

自定义方法

SpringData的另一个强大的功能是根据方法名称自动实现功能。比如方法名为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}}}}

我们来按照价格区间查询,定义一个方法

public interface ItemRepository extends ElasticsearchRepository<Item,Long> {

    List<Item> findByPriceBetween(double start,double end);
}
@Test
    public void queryItemByPrice(){
        List<Item> items = this.repository.findByPriceBetween(2000d, 3500d);
        items.forEach(item -> System.out.println(item));
    }

在这里插入图片描述

分页查询

利用NativeSearchQueryBuilder可以方便的实现分页

@Test
    public void testNativeQuery(){
        //构建查询条件
        NativeSearchQueryBuilder queryBuilder= new NativeSearchQueryBuilder();
        //添加基本的分词查询
        queryBuilder.withQuery(QueryBuilders.termQuery("category","手机"));
        //添加分页参数
        int page=0;
        int size=3;

        //设置分页参数
        queryBuilder.withPageable(PageRequest.of(page,size));

        //执行搜索,获取结果
        Page<Item> items = this.repository.search(queryBuilder.build());
        //打印总条数
        System.out.println(items.getTotalElements());
        //打印总页数
        System.out.println(items.getTotalPages());
        //每页大小
        System.out.println(items.getSize());
        //当前页
        System.out.println(items.getNumber());
        items.forEach(System.out::println);
    }

在这里插入图片描述

排序

排序也可以通过NativeSearchQueryBuilder来完成

 @Test
    public void testSort(){
        //构建查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //添加基本分词查询
        queryBuilder.withQuery(QueryBuilders.termQuery("category","手机"));
        queryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.DESC));

        //指定搜索结果
        Page<Item> items = this.repository.search(queryBuilder.build());
        System.out.println(items.getTotalElements());
        items.forEach(System.out::println);
    }

在这里插入图片描述

聚合

聚合为桶

@Test
    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、查询,需要把结果强转为AggregatePage类型
        AggregatedPage<Item> aggPage = (AggregatedPage <Item>)this.repository.search(queryBuilder.build());
        //3、解析
        //3.1、从结果中取出brands的那个聚合,因为是利用String类型字段进行的term聚合,所以要强转为StringTerm类型
        StringTerms agg = (StringTerms)aggPage.getAggregation("brands");
        //3.2、获取桶
        List<StringTerms.Bucket> buckets = agg.getBuckets();
        //3.3、遍历
        for (StringTerms.Bucket bucket : buckets) {
            //获取桶中的key,即品牌名称
            System.out.println(bucket.getKeyAsString());
            //获取桶中的文档数量
            System.out.println(bucket.getDocCount());
        }
    }

在这里插入图片描述

关键API

AggregationBuilders:聚合的构建工厂类,所有聚合都由这个类来构建;

查询JSON结果和Java类的对照关系

在这里插入图片描述

嵌套聚合,求平均值

@Test
    public void testSubAgg(){
        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、查询需要把结果强转为AggregatedPage类型
        AggregatedPage<Item> aggPage =(AggregatedPage<Item>) this.repository.search(queryBuilder.build());
        //3、解析
        //3.1、从结果中取出brands的那个聚合,因为是利用String类型字段进行的term聚合,所以要强转为StringTerm类型
        StringTerms agg = (StringTerms)aggPage.getAggregation("brands");
        //3.2、获取桶
        List<StringTerms.Bucket> buckets = agg.getBuckets();
        //3.3、遍历
        for (StringTerms.Bucket bucket : buckets) {
            //获取桶中的key,即品牌名称
            System.out.println(bucket.getKeyAsString()+",共"+bucket.getDocCount()+"台");
            //获取子聚合结果
            InternalAvg priceAvg =(InternalAvg) bucket.getAggregations().asMap().get("priceAvg");
            System.out.println("平均售价:"+priceAvg.getValue());
        }
    }

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值