SpringDataES-6.8

SpringData-ElasticSearch简介

SpringDataES

SpringDataElasticsearch(以后简称SDE)是SpringData项目下的一个子模块,是Spring提供的操作ElasticSearch的数据层,封装了大量的基础操作,通过它可以很方便的操作ElasticSearch的数据

SpringData的使命是给各种数据访问提供统一的编程接口,不管是关系型数据库(如MySQL),还是非关系数据库(如Redis),或者类似Elasticsearch这样的索引数据库。从而简化开发人员的代码,提高开发效率

SpringDataES特征

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

创建SpringData-Es工程

通过https://start.spring.io/创建在IDEA中不是网页版的,我就不贴图了,待会在下方给出依赖即可

首先在application.yml文件中引入elasticsearch的hostport

spring:
  data:
    elasticsearch:
      # es集群名称
      cluster-name: elasticsearch
      # 准备连接的es节点tcp地址
      cluster-nodes: 127.0.0.1:9300
      # 集群配置:192.168.129.139:9301,192.168.129.139:9302,192.168.129.139:9303

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

pom.xml

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.16.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
    <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-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </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>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

创建索引库和映射

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

Goods.java

@Data
@AllArgsConstructor
@NoArgsConstructor
// 与索引库对应的文档实体类型 indexNmae相当于DataBases type相当于Table
// 集群时可以设置: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

创建索引库

相当于创建create DataBases, ElasticsearchTemplate:可以用来操作复杂的ES

@SpringBootTest
class SpringbootEsApiApplicationTests {

    @Autowired
    private ElasticsearchTemplate template;

    @Test
    void testCreateIndex() throws IOException {
        boolean flag = template.createIndex(Goods.class);
        System.out.println("创建索引库是否成功" + flag);
        boolean mapping = template.putMapping(Goods.class);
        System.out.println("创建映射是否成功" + mapping);
    }
}

kIbana查看创建结果

GET /goods/_mapping/goods

删除索引

相当于删除drop DataBases

@SpringBootTest
class SpringbootEsApiApplicationTests {

    @Autowired
    private ElasticsearchTemplate template;

    @Test
    public void deleteIndex(){
        // 根据对象删除
        boolean flag = template.deleteIndex(Goods.class);

        // 根据索引名称删除
        // boolean flagIndex = template.deleteIndex("goods");

        System.out.println("删除是否成功" + flag);
    }
}

ElasticsearchRepositoryCRUD

SDE的文档索引数据CRUD并没有封装在ElasticsearchTemplate中,而是有一个叫做ElasticsearchRepository的接口

我们需要自定义接口,继承ElasticsearchRespository

GoodsRepository.java

public interface GoodsRepository extends ElasticsearchRepository<Goods,Long> {

}

创建文档数据

创建索引有单个创建和批量创建之分,先来看单个创建(添加),如果文档已经存在则执行更新操作

  • 创建新增文档对象
  • 调用GoodsRepository接口保存方法
@SpringBootTest
class SpringbootEsApiApplicationTests {

    @Autowired
    private GoodsRepository goodsRepository;

    /**
     * 添加文档
     * 不存在就新增,存在就更新
     */
    @Test
    public void addDoc(){
        // 1.创建新增文档对象
        Goods goods = new Goods(1l, "小米手机", "手机", "小米", 19999.00, "http://www.baidu.com");

        // 2.调用goodsRepository保存
        goodsRepository.save(goods);
    }
}

再来看批量创建(添加)

  • 创建新增文档对象添加至list中
  • 调用GoodsRepository接口批量保存方法
@SpringBootTest
class SpringbootEsApiApplicationTests {

    @Autowired
    private GoodsRepository goodsRepository;

    /**
     * 批量新增
     */
    @Test
    public void batchAddDoc(){
        // 1.创建新增文档对象添加至list中
        List<Goods> goodsList = new ArrayList<>();
        for (long i = 2; i < 10; i++) {
            Goods goods = new Goods(i, "小米手机" + i, "手机", "小米", 19999.00 + i, "http://www.baidu.com");
            goodsList.add(goods);
        }
        // 2.调用GoodsRepository接口批量保存方法
        goodsRepository.saveAll(goodsList);
    }
}

通过Kibana查看

GET /goods/_search

查询文档数据

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

🐤根据id查询

  • 调用goodsRepository根据id查询
  • 判断返回的Optional对象中是否有值
  • 从Optional对象中获取查询结果
@SpringBootTest
class SpringbootEsApiApplicationTests {

    @Autowired
    private GoodsRepository goodsRepository;

    /**
     * 根据id查询
     */
    @Test
    public void findDocById(){
        // 1.调用goodsRepository根据id查询
        Optional<Goods> optional = goodsRepository.findById(1L);

        // 2.判断返回的Optional对象中是否有值
        if(optional.isPresent()){
            // 3.从Optional对象中获取查询结果
            Goods goods = optional.get();
            System.out.println("查询结果" + goods);
        }
    }
}

🐸查询所有

@SpringBootTest
class SpringbootEsApiApplicationTests {

    @Autowired
    private GoodsRepository goodsRepository;

    /**
     * 查询所有文档
     */
    @Test
    public void findAllDoc(){
        // 1.调用goodsRepository查询所有
        Iterable<Goods> all = goodsRepository.findAll();

        // 2.遍历打印输出查询结果
        for (Goods goods : all) {
            System.out.println("查询结果" + goods);
        }
    }
}

search查询

  • 构建QueryBuilder对象设置查询类型和查询条件
  • 调用goodsRepository search方法进行查询
  • 遍历打印输出查询结果
@SpringBootTest
class SpringbootEsApiApplicationTests {

    @Autowired
    private GoodsRepository goodsRepository;

    /**
     * search查询
     */
    @Test
    public void findAllDoc(){
        // 1.构建QueryBuilder对象设置查询类型和查询条件
        TermQueryBuilder queryBuilder = QueryBuilders.termQuery("title", "大米");

        // 2.调用goodsRepository search方法进行查询
        Iterable<Goods> iterable = goodsRepository.search(queryBuilder);

        // 3.遍历打印输出查询结果
        for (Goods goods : iterable) {
            System.out.println("查询结果" + goods);
        }
    }
}

search查询并分页排序

  • 构建Sort排序对象,指定排序字段和排序方式
  • 使用PageRequest构建Pageable分页对象,指定分页参数,并将排序对象设置到分页对象中
  • 调用goodsRepository search方法进行查询
  • 解析结果
@SpringBootTest
class SpringbootEsApiApplicationTests {

    @Autowired
    private GoodsRepository goodsRepository;

    /**
     * search查询并实现分页、排序
     */
    @Test
    public void findAllDoc(){
        // 1.构建排序对象,指定排序字段和排序方式
        Sort sort = new Sort(Sort.Direction.ASC, "id");

        // 2.构建分页对象,指定分页参数,并将排序对象设置到分页对象中
        PageRequest pageRequest = PageRequest.of(0, 2, sort);

        // 3.调用goodsRepository search方法进行查询
        Page<Goods> page = goodsRepository.search(QueryBuilders.matchQuery("title", "小米"), pageRequest);

        // 4.解析结果
        // 4.1.获取总记录数
        long totalElements = page.getTotalElements();

        // 4.2.获取总页数
        int totalPages = page.getTotalPages();

        // 4.3.遍历查询结果
        for (Goods goods : page) {
            System.out.println("查询结果" + goods);
        }

        System.out.println("总记录数" + totalElements);
        System.out.println("总页数" + totalPages);
    }
}

自定义方法查询

GoodsRepository提供的查询方法有限,但是它却提供了非常强大的自定义查询功能;只要遵循SpringData提供的语法,我们可以任意定义方法达到自定义查询

public interface GoodsRepository extends ElasticsearchRepository<Goods,Long> {

    /**
     * 根据价格范围查询
     * @param from  开始价格
     * @param to    结束价格
     * @return      查询的结果
     */
    List<Goods> findByPriceBetween(Double from, Double to);

}

无需写实现,SDE会自动帮我们实现该方法,我们只需要用即可

@SpringBootTest
class SpringbootEsApiApplicationTests {

    @Autowired
    private GoodsRepository goodsRepository;

    /**
     * 自定义GoodsRepository查询方法: 根据价格范围查找
     */
    @Test
    public void findGoodsByPriceRang(){
        // 1.调用goodsRepository自定义方法 findByPriceBetween
        List<Goods> list = goodsRepository.findByPriceBetween(19999.0, 20006.0);

        // 2.遍历输出结果
        for (Goods goods : list) {
            System.out.println(goods);
        }
    }
}

支持的一些语法示例

  • findGoods By Price Between
  • 语法:findBy + 字段名 + Keyword + 字段名 + …
Keyword Sample Elasticsearch Query String
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" : "?"}}}}
Between findByPriceBetween {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : ?,"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}}}}}
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" : "?"}}}}}}
OrderBy findByNameOrderByNameDesc {"sort" : [{ "name" : {"order" : "desc"} }],"bool" : {"must" : {"field" : {"name" : "?"}}}

ElasticsearchTemplate查询

  • SDE也支持使用ElasticsearchTemplate进行原生查询
  • 而查询条件的构建是通过一个名为NativeSearchQueryBuilder的类来完成的,不过这个类的底层还是使用的原生API中的QueryBuildersHighlightBuilders等工具

分页和排序

  • 可以通过NativeSearchQueryBuilder类来构建分页和排序、聚合等操作
  • queryBuilder.withQuery():设置查询类型和查询条件
  • queryBuilder.withPageable():设置分页
  • queryBuilder.withSort():设置排序
  1. 构建NativeSearchQueryBuilder查询对象
  2. 使用QueryBuilders指定查询类型和查询条件
  3. 使用SortBuilders指定排序字段和排序方式
  4. 使用PageRequest对象指定分页参数
  5. 调用NativeSearchQueryBuilder的build方法完成构建
  6. 使用ElasticsearchTemplate完成查询
  7. 解析结果
@SpringBootTest
class SpringbootEsApiApplicationTests {

    @Autowired
    private ElasticsearchTemplate template;

    /**
     * 使用ElasticsearchTemplate完成分页排序查询
     */
    @Test
    public void nativeSearchQuery(){
        // 1.构建NativeSearchQueryBuilder查询对象
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();

        // 2.使用QueryBuilders指定查询类型和查询条件
        nativeSearchQueryBuilder.withQuery(QueryBuilders.matchQuery("title", "小米"));

        // 3.使用SortBuilders指定排序字段和排序方式
        nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("id").order(SortOrder.DESC));

        // 4.使用PageRequest对象指定分页参数
        nativeSearchQueryBuilder.withPageable(PageRequest.of(0,2));

        // 5.调用NativeSearchQueryBuilder的build方法完成构建
        NativeSearchQuery build = nativeSearchQueryBuilder.build();

        // 6.使用ElasticsearchTemplate完成查询
        AggregatedPage<Goods> page = template.queryForPage(build, Goods.class);

        // 7.解析结果
        // 7.1获取总记录数
        long totalElements = page.getTotalElements();

        // 7.2获取总页数
        int totalPages = page.getTotalPages();

        // 7.3遍历查询结果
        for (Goods goods : page) {
            System.out.println("查询结果" + goods);
        }
        System.out.println("总记录数" + totalElements);
        System.out.println("总页数" + totalPages);
    }
}
posted @ 2020-08-24 11:30  BNTang  阅读(363)  评论(0编辑  收藏  举报