Spring-Boot使用rest方式整合Elasticsearch

写在前面:1. ES及ik分词插件的安装参考上篇文章,ES安装(Linux);2. Springboot(2.1.3.RELEASE)、ES(6.5.0)、spring-boot-starter-data-jest(3.2.5.RELEASE)三者版本的对应关系,因为版本问题调整了6个小时才无异常运行。

一、ES客户端

Elasticsearch(ES)提供了两种客户端连接方式:

  • transport :通过 TCP 方式访问 ES 。
  • rest :通过 HTTP API 方式访问 ES 。

在项目中,编写数据库操作的逻辑,使用 MyBatis 或者 JPA 为主,而不使用原生的 JDBC 。那么,我们在编写 Elasticsearch 操作的逻辑,也不直接使用上述的客户端,而是:

虽然这两者底层使用的不同客户端,但是都基于 Spring Data 体系,所以项目在使用时,编写的代码是相同的。也因此,如果想从 spring-data-elasticsearch 迁移到 spring-data-jest 时,基本无成本。

二、背景故事

虽然说,ES 提供了 2 种方式,官方目前建议使用 rest 方式,而不是 transport 方式。并且,transport 在未来的计划中,准备废弃。并且,阿里云提供的 Elasticsearch 更加干脆,直接只提供 rest 方式,而不提供 transport 方式。
在社区中,有个 Jest 开源项目,提供了的 Elasticsearch REST API 客户端。

重要结论:所以,为了兼容我们需要使用spring-data-jest

三、Spring Data Jest

本文重点是使用spring-data-jest方式跟ES打交道。我们会使用 spring-boot-starter-data-jest 自动化配置 Spring Data Jest 主要配置。同时,编写相应的 Elasticsearch 的 CRUD 操作。

3.1 引入依赖

在 [pom.xml]文件中,引入相关依赖。

<?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">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>es-spring-data-jest</artifactId>

    <dependencies>
        <!-- 自动化配置 Spring Data Jest -->
        <dependency>
            <groupId>com.github.vanroy</groupId>
            <artifactId>spring-boot-starter-data-jest</artifactId>
            <version>3.2.5.RELEASE</version>
        </dependency>

        <!-- 方便写单元测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-autoconfigure</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>

    </dependencies>

</project>

3.2 Application启动类

package cn.erbadagang.springboot.es.springdatajest;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration;
import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration;

/**
 * @description ES Jest客户端项目启动类。需要排除 ElasticsearchAutoConfiguration 和 ElasticsearchDataAutoConfiguration 自动配置类,否则会自动配置 Spring Data Elasticsearch 。
 * @ClassName: Application
 * @author: 郭秀志 jbcode@126.com
 * @date: 2020/7/5 10:27
 * @Copyright:
 */
@SpringBootApplication(exclude = {ElasticsearchAutoConfiguration.class, ElasticsearchDataAutoConfiguration.class})
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

需要排除 ElasticsearchAutoConfiguration 和 ElasticsearchDataAutoConfiguration 自动配置类,否则会自动配置 Spring Data Elasticsearch 。

3.3 配置文件

在 [application.yml]中,添加 Jest 配置,如下:

spring:
  data:
    # Jest 配置项
    jest:
      uri: http://127.0.0.1:9200
  • 我们使用本地的 ES 服务。默认情况下,ES rest 连接方式暴露的端口是 9200 。

3.4 ESProductDO

package cn.erbadagang.springboot.es.springdatajest.dataobject;

import cn.erbadagang.springboot.es.springdatajest.constant.FieldAnalyzer;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
/**
 * @description 
 * @ClassName: ESProductDO
 * @author: 郭秀志 jbcode@126.com
 * @date: 2020/7/5 15:16    
 * @Copyright:  
 */
@Document(indexName = "product", // 索引名
        type = "product", // 类型。未来的版本即将废弃
        shards = 1, // 默认索引分区数
        replicas = 0, // 每个分区的备份数
        refreshInterval = "-1" // 刷新间隔
)
public class ESProductDO {

    /**
     * ID 主键
     */
    @Id
    private Integer id;

    /**
     * SPU 名字
     */
    @Field(analyzer = FieldAnalyzer.IK_MAX_WORD, type = FieldType.Text)
    private String name;
    /**
     * 卖点
     */
    @Field(analyzer = FieldAnalyzer.IK_MAX_WORD, type = FieldType.Text)
    private String sellPoint;
    /**
     * 描述
     */
    @Field(analyzer = FieldAnalyzer.IK_MAX_WORD, type = FieldType.Text)
    private String description;
    /**
     * 分类编号
     */
    private Integer cid;
    /**
     * 分类名
     */
    @Field(analyzer = FieldAnalyzer.IK_MAX_WORD, type = FieldType.Text)
    private String categoryName;

    public Integer getId() {
        return id;
    }

    public ESProductDO setId(Integer id) {
        this.id = id;
        return this;
    }

    public String getName() {
        return name;
    }

    public ESProductDO setName(String name) {
        this.name = name;
        return this;
    }

    public String getSellPoint() {
        return sellPoint;
    }

    public ESProductDO setSellPoint(String sellPoint) {
        this.sellPoint = sellPoint;
        return this;
    }

    public String getDescription() {
        return description;
    }

    public ESProductDO setDescription(String description) {
        this.description = description;
        return this;
    }

    public Integer getCid() {
        return cid;
    }

    public ESProductDO setCid(Integer cid) {
        this.cid = cid;
        return this;
    }

    public String getCategoryName() {
        return categoryName;
    }

    public ESProductDO setCategoryName(String categoryName) {
        this.categoryName = categoryName;
        return this;
    }

    @Override
    public String toString() {
        return "ProductDO{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", sellPoint='" + sellPoint + '\'' +
                ", description='" + description + '\'' +
                ", cid=" + cid +
                ", categoryName='" + categoryName + '\'' +
                '}';
    }

}
  • 为了区别关系数据库的实体对象,以 ES 前缀开头。
  • 字段上的 @Field 注解的 [FieldAnalyzer],是定义的常量类。代码如下:
package cn.erbadagang.springboot.es.springdatajest.constant;

/**
 * ES 字段分析器的常量类
 *
 * 关于 IK 分词,文章 https://blog.csdn.net/xsdxs/article/details/72853288 不错。
 * 目前项目使用的 ES 版本是 6.5.0 ,可以在 https://www.elastic.co/cn/downloads/past-releases/elasticsearch-6-5-0 下载。
 * 如果不知道怎么安装 ES ,可以看 https://www.jianshu.com/p/941c9797923e。
 */
public class FieldAnalyzer {

    /**
     * IK 最大化分词
     *
     * 会将文本做最细粒度的拆分
     */
    public static final String IK_MAX_WORD = "ik_max_word";

    /**
     * IK 智能分词
     *
     * 会做最粗粒度的拆分
     */
    public static final String IK_SMART = "ik_smart";

}

一定要记得给 Elasticsearch 安装 IK 插件,不然等会示例会报错。报错信息类似:“unknown index.”

3.5 ProductRepository

创建 [ProductRepository] 接口。代码如下:

package cn.erbadagang.springboot.es.springdatajest.repository;

import cn.erbadagang.springboot.es.springdatajest.dataobject.ESProductDO;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

/**
 * @description JPA操作ES
 * @ClassName: ProductRepository
 * @author: 郭秀志 jbcode@126.com
 * @date: 2020/7/5 15:20
 * @Copyright:
 */
public interface ProductRepository extends ElasticsearchRepository<ESProductDO, Integer> {

}

  • 继承 org.springframework.data.elasticsearch.repository.ElasticsearchRepository 接口,第一个泛型设置对应的实体是 ESProductDO ,第二个泛型设置对应的主键类型是 Integer 。
  • 因为实现了 ElasticsearchRepository 接口,Spring Data Jest 会自动生成对应的 CRUD 等等的代码。
  • ElasticsearchRepository 类图如下:
    ElasticsearchRepository 类图

    上图会发现和 Spring Data JPA 操作MySQL数据库的使用方式基本一致。这就是 Spring Data 带给我们的好处,使用相同的 API ,统一访问不同的数据源。

3.6 简单JPA操作ES测试

创建 [ProductRepositoryTest](测试类,我们来测试一下简单的 ProductRepository 的每个操作。代码如下:

package cn.erbadagang.springboot.es.springdatajest.repository;

import cn.erbadagang.springboot.es.springdatajest.Application;
import cn.erbadagang.springboot.es.springdatajest.dataobject.ESProductDO;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Optional;

/**
 * @description
 * @ClassName: ProductRepositoryTest
 * @author: 郭秀志 jbcode@126.com
 * @date: 2020/7/6 9:52
 * @Copyright:
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class ProductRepositoryTest {

    @Autowired
    private ProductRepository productRepository;

    @Test // 插入一条记录
    public void testInsert() {
        ESProductDO product = new ESProductDO();
        product.setId(1); // 一般 ES 的 ID 编号使用 DB 数据对应的编号。这里先写死
        product.setName("简单JPA操作的ES");
        product.setSellPoint("简单,跟普通JPA data的语法一致进行ES操作");
        product.setDescription("描述:自带CRUD");
        product.setCid(1);
        product.setCategoryName("技术分类");
        productRepository.save(product);

        product.setId(5); // 一般 ES 的 ID 编号使用 DB 数据对应的编号。这里先写死
        product.setName("ES5");
        product.setSellPoint("跟普通JPA data的语法一致进行ES操作");
        product.setDescription("描述5:自带CRUD");
        product.setCid(5);
        product.setCategoryName("技术分类");
        productRepository.save(product);
    }

    // 这里要注意,如果使用 save 方法来更新的话,必须是全量字段,否则其它字段会被覆盖。
    // 所以,这里仅仅是作为一个示例。
    @Test // 更新一条记录
    public void testUpdate() {
        ESProductDO product = new ESProductDO();
        product.setId(1);
        product.setCid(2);
        product.setCategoryName("tech-Java");
        productRepository.save(product);
    }

    @Test // 根据 ID 编号,删除一条记录
    public void testDelete() {
        productRepository.deleteById(1);
    }

    @Test // 根据 ID 编号,查询一条记录
    public void testSelectById() {
        Optional<ESProductDO> userDO = productRepository.findById(1);
        System.out.println("testSelectById():" + userDO);
    }

    @Test // 查询所有记录
    public void testAll() {
        Iterable<ESProductDO> users = productRepository.findAll();
        users.forEach(System.out::println);
    }

}

部分运行结果:

junit运行结果

id=1的name变成空,是因为后面调用了save 方法来更新,必须是全量字段,否则其它字段会被覆盖。

四、JPA——基于方法名查询

在 Spring Data 中,支持根据方法名作生成对应的查询(WHERE)条件,进一步进化我们使用 JPA ,具体是方法名以 findBy、existsBy、countBy、deleteBy 开头,后面跟具体的条件。具体的规则:

关键字 方法示例 JPQL snippet
And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
Is, Equals findByFirstname, findByFirstnameIs, findByFirstnameEquals … where x.firstname = ?1
Between findByStartDateBetween … where x.startDate between ?1 and ?2
LessThan findByAgeLessThan … where x.age < ?1
LessThanEqual findByAgeLessThanEqual … where x.age <= ?1
GreaterThan findByAgeGreaterThan … where x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1
After findByStartDateAfter … where x.startDate > ?1
Before findByStartDateBefore … where x.startDate < ?1
IsNull, Null findByAge(Is)Null … where x.age is null
IsNotNull, NotNull findByAge(Is)NotNull … where x.age not null
Like findByFirstnameLike … where x.firstname like ?1
NotLike findByFirstnameNotLike … where x.firstname not like ?1
StartingWith findByFirstnameStartingWith … where x.firstname like ?1 (parameter bound with appended %)
EndingWith findByFirstnameEndingWith … where x.firstname like ?1 (parameter bound with prepended %)
Containing findByFirstnameContaining … where x.firstname like ?1 (parameter bound wrapped in %)
OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc
Not findByLastnameNot … where x.lastname <> ?1
In findByAgeIn(Collection ages) … where x.age in ?1
NotIn findByAgeNotIn(Collection ages) … where x.age not in ?1
True findByActiveTrue() … where x.active = true
False findByActiveFalse() … where x.active = false
IgnoreCase findByFirstnameIgnoreCase … where UPPER(x.firstame) = UPPER(?1)

注意,如果我们有排序需求,可以使用 OrderBy 关键字。

因为 Spring Data Elasticsearch 和 Spring Data Jest 也是 Spring Data 体系中的一员,所以也能享受到基于方法名查询的福利。下面,我们来编写一个简单的示例。

4.1 ProductRepositoryWithMethodName

package cn.erbadagang.springboot.es.springdatajest.repository;


import cn.erbadagang.springboot.es.springdatajest.dataobject.ESProductDO;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

/**
 * @description 使用JPA的基于方法名的操作,如: findBy、existsBy、countBy、deleteBy 开头。
 * @ClassName: ProductRepositoryWithMethodName
 * @author: 郭秀志 jbcode@126.com
 * @date: 2020/7/6 15:04
 * @Copyright:
 */
public interface ProductRepositoryWithMethodName extends ElasticsearchRepository<ESProductDO, Integer> {

    ESProductDO findByName(String name);

    Page<ESProductDO> findByNameLike(String name, Pageable pageable);

}

对于分页操作,需要使用到Pageable参数,需要作为方法的最后一个参数。

4.2 简单测试

创建 [ProductRepositoryWithMethodNameTest]测试类,我们来测试一下简单的 ProductRepositoryWithMethodName 的每个操作。代码如下:

package cn.erbadagang.springboot.es.springdatajest.repository;


import cn.erbadagang.springboot.es.springdatajest.Application;
import cn.erbadagang.springboot.es.springdatajest.dataobject.ESProductDO;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * @description 使用JPA命名规范自定义的查询测试
 * @ClassName: ProductRepositoryWithMethodNameTest
 * @author: 郭秀志 jbcode@126.com
 * @date: 2020/7/6 21:21
 * @Copyright:
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class ProductRepositoryWithMethodNameTest {

    @Autowired
    private ProductRepositoryWithMethodName productRepository;

    @Test // 根据名字获得一条记录
    public void testFindByName() {
        ESProductDO product = productRepository.findByName("郭秀志源码");
        System.out.println(product);
    }

    @Test // 使用 name 模糊查询,分页返回结果
    public void testFindByNameLike() {
        // 根据情况,是否要制造测试数据
        if (true) {
            testInsert();
        }

        // 创建排序条件
        // Sort sort = new Sort(Sort.Direction.DESC, "id");//废弃的写法
        Sort sort = Sort.by(Sort.Direction.DESC, "id");// ID 倒序
        // 创建分页条件。
        Pageable pageable = PageRequest.of(0, 10, sort);
        // 执行分页操作
        Page<ESProductDO> page = productRepository.findByNameLike("郭秀志", pageable);
        // 打印
        System.out.println(page.getTotalElements());
        System.out.println(page.getTotalPages());
        //遍历所有取出ESProductDO数据
        page.get().forEach(System.out::println);
    }

    /**
     * 为了给分页制造一点数据
     */
    private void testInsert() {
        for (int i = 1; i <= 100; i++) {
            ESProductDO product = new ESProductDO();
            product.setId(i); // 一般 ES 的 ID 编号使用 DB 数据对应的编号。先写死成数据循环变化
            product.setName("郭秀志源码:" + i);
            product.setSellPoint("免费开源,不要钱了");
            product.setDescription("描述,description");
            product.setCid(1);
            product.setCategoryName("技术");
            productRepository.save(product);
        }
    }

}

运行结果:
测试结果

五、自定义复杂查询

在一些业务场景下,我们需要编写相对复杂的查询,例如说类似京东 https://search.jd.com/Search?keyword=华为手机 搜索功能,需要支持关键字、分类、品牌等等,并且可以按照综合、销量等等升降序排序,那么我们就无法在使用上面的 Spring Data Repository 提供的简单的查询方法,而需要使用到 ElasticsearchRepository search `方法,代码如下:

// ElasticsearchRepository.java
// 省略非 search 方法

Page<T> search(QueryBuilder query, Pageable pageable);

Page<T> search(SearchQuery searchQuery);

Page<T> searchSimilar(T entity, String[] fields, Pageable pageable);

此时,我们就需要使用QueryBuilderSearchQuery构建相对复杂的搜索和排序条件。

接下来,实现一个简单的商品搜索功能。

5.1 ProductRepositoryNativeSearchQuery

package cn.erbadagang.springboot.es.springdatajest.repository;


import cn.erbadagang.springboot.es.springdatajest.dataobject.ESProductDO;
import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.util.StringUtils;

import static org.elasticsearch.index.query.QueryBuilders.matchQuery;

/**
 * @description 自定义复杂查询。
 * @ClassName: ProductRepositoryNativeSearchQuery
 * @author: 郭秀志 jbcode@126.com
 * @date: 2020/7/7 11:50
 * @Copyright:
 */
public interface ProductRepositoryNativeSearchQuery extends ElasticsearchRepository<ESProductDO, Integer> {

    default Page<ESProductDO> search(Integer cid, String keyword, Pageable pageable) {
        // <1> 创建 NativeSearchQueryBuilder 对象
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        // <2.1> 筛选条件 cid
        if (cid != null) {
            nativeSearchQueryBuilder.withFilter(QueryBuilders.termQuery("cid", cid));
        }
        // <2.2> 筛选
        if (StringUtils.hasText(keyword)) {
            FunctionScoreQueryBuilder.FilterFunctionBuilder[] functions = { // TODO 分值随便打的
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder(matchQuery("name", keyword),
                            ScoreFunctionBuilders.weightFactorFunction(10)),
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder(matchQuery("sellPoint", keyword),
                            ScoreFunctionBuilders.weightFactorFunction(2)),
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder(matchQuery("categoryName", keyword),
                            ScoreFunctionBuilders.weightFactorFunction(3)),
//                    new FunctionScoreQueryBuilder.FilterFunctionBuilder(matchQuery("description", keyword),
//                            ScoreFunctionBuilders.weightFactorFunction(2)), // TODO 目前这么做,如果商品描述很长,在按照价格降序,会命中超级多的关键字。
            };
            FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(functions)
                    .scoreMode(FunctionScoreQuery.ScoreMode.SUM) // 求和
                    .setMinScore(2F); // TODO 需要考虑下 score
            nativeSearchQueryBuilder.withQuery(functionScoreQueryBuilder);
        }
        // 排序
        if (StringUtils.hasText(keyword)) { // <3.1> 关键字,使用打分
            nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC));
        } else if (pageable.getSort().isSorted()) { // <3.2> 有排序,则进行拼接
            pageable.getSort().get().forEach(sortField -> nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortField.getProperty())
                    .order(sortField.getDirection().isAscending() ? SortOrder.ASC : SortOrder.DESC)));
        } else { // <3.3> 无排序,则按照 ID 倒序
            nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("id").order(SortOrder.DESC));
        }
        // <4> 分页
        nativeSearchQueryBuilder.withPageable(PageRequest.of(pageable.getPageNumber(), pageable.getPageSize())); // 避免
        // <5> 执行查询
        return search(nativeSearchQueryBuilder.build());
    }

}

使用 QueryBuilder 和 SearchQuery 构建相对复杂的搜索和排序条件,我们可以放在 Service 层,也可以放在 Repository 层。个人的偏好放在 Repository 层。
主要原因是,尽量避免数据层的操作暴露在 Service 层。
缺点呢,就像我们这里看到的,有点业务逻辑就到了 Repository 层。
有舍有得,看个人喜好。翻了一些开源项目,放在 Service 或 Repository 层的都有。
简单来说下这个方法的整体逻辑,根据商品分类编号 + 关键字,检索相应的商品,分页返回结果。
<1> 处,创建 NativeSearchQueryBuilder 对象。
筛选条件
<2.1> 处,如果有分类编号 cid ,则进行筛选。
<2.2> 处,如果有关键字 keyword ,则按照 name 10 分、sellPoint 2 分、categoryName 3 分,计算求和,筛选至少满足 2 分。
排序条件
<3.1> 处,如果有关键字,则按照打分结果降序。
<3.2> 处,如果有排序条件,则按照该排序即可。
<3.3> 处,如果无排序条件,则按照 ID 编号降序。
分页条件
<4> 处,创建新的 PageRequest 对象,避免 pageable 里原有的排序条件。
执行搜索
<5> 处,调用 #search(SearchQuery searchQuery) 方法,执行 Elasticsearch 搜索。

5.2 测试

创建测试类:

package cn.erbadagang.springboot.es.springdatajest.repository;


import cn.erbadagang.springboot.es.springdatajest.Application;
import cn.erbadagang.springboot.es.springdatajest.dataobject.ESProductDO;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * @description 自定义NativeSearchQuery测试类
 * @ClassName: ProductRepositoryNativeSearchQueryTest
 * @author: 郭秀志 jbcode@126.com
 * @date: 2020/7/7 11:53
 * @Copyright:
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class ProductRepositoryNativeSearchQueryTest {

    @Autowired
    private ProductRepositoryNativeSearchQuery productRepository;

    @Test
    public void testSearch() {
        // 查找分类为 1 + 指定关键字,并且按照 id 升序
        Page<ESProductDO> page = productRepository.search(1, "技术",
                PageRequest.of(0, 5, Sort.Direction.ASC, "id"));
        System.out.println(page.getTotalPages());
        //输出查询结果。
        page.forEach(System.out::println);

        // 查找分类为 1 ,并且按照 id 升序
        page = productRepository.search(1, null,
                PageRequest.of(0, 5, Sort.Direction.ASC, "id"));
        System.out.println(page.getTotalPages());
        //输出查询结果。
        page.forEach(System.out::println);
    }

}

运行结果:
自定义SearchQuery运行结果

底线


本文源代码使用 Apache License 2.0开源许可协议,可从Gitee代码地址通过git clone命令下载到本地或者通过浏览器方式查看源代码。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335