Elasticsearch-核心篇(10)-SpringBoot框架整合开发

1,793 阅读4分钟

一、SpringBoot集成ES

  • 添加maven依赖,加入核心包spring-boot-starter-data-elasticsearch,注意需要排除elasticsearch-rest-high-level-client并指定其核心版本和安装的ES版本一致
<?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 https://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.4.4</version>
        <relativePath/>
    </parent>
    <groupId>com.codecoord</groupId>
    <artifactId>springboot-elasticsearch</artifactId>
    <version>1.0</version>
    <name>springboot-elasticsearch</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!-- elasticsearch -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
            <!-- 如果SpringBoot版本不匹配,需要排除原生版本 -->
            <exclusions>
                <exclusion>
                    <groupId>org.elasticsearch.client</groupId>
                    <artifactId>elasticsearch-rest-high-level-client</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- 指定高级客户端版本 -->
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>7.8.0</version>
        </dependency>
      
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.74</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </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>
  • 配置文件增加配置信息
spring:
  elasticsearch:
    rest:
      uris: http://127.0.0.1:9200

二、ElasticsearchRestTemplate

  1. ElasticsearchRestTemplate提供操作ES的模板类
  2. 新建SpringBoot实体类,用于操作基础文档
    • @Document(indexName = "springboot", shards = 1, replicas = 1)
      • 配置索引名称、分片数、副本数,项目启动时将会自动创建索引
    • @Id
      • 指定id字段
    • @Field(type = FieldType.Keyword)
      • 配置字段为关键字
    • @Field(type = FieldType.Text)
      • 配置字段为普通文本字段
import com.alibaba.fastjson.JSONObject;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
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;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Document(indexName = "springboot", shards = 1, replicas = 1)
public class SpringBoot {
    /**
     * 必须有id,这里的id是全局唯一的标识,等同于es中的"_id"
     */
    @Id
    private Long id;
    /**
     * type : 字段数据类型
     * analyzer : 分词器类型
     * index : 是否索引(默认:true)
     * Keyword : 短语,不进行分词
     */
    @Field(type = FieldType.Keyword)
    private String name;
    @Field(type = FieldType.Text)
    private String createTime;

    @Override
    public String toString() {
        return JSONObject.toJSONString(this);
    }
}
  • FieldType枚举值
public enum FieldType {
	Auto,
	Text,
	Keyword,
	Long,
	Integer,
	Short,
	Byte,
	Double,
	Float,
	Half_Float,
	Scaled_Float,
	Date,
	Date_Nanos,
	Boolean,
	Binary,
	Integer_Range,
	Float_Range,
	Long_Range,
	Double_Range,
	Date_Range,
	Ip_Range,
	Object,
	Nested,
	Ip,
	TokenCount,
	Percolator,
	Flattened,
	Search_As_You_Type,
	Rank_Feature,
	Rank_Features
}
  1. 创建打印工具类,用于美化json输出
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class PrintUtil {

    public static void prettyPrint(Object resource) {
        System.out.println(JSONObject.toJSONString(resource, SerializerFeature.PrettyFormat));
    }
}
  1. 模板类使用示例
import com.codecoord.springboot.elasticsearch.domain.SpringBoot;
import com.codecoord.springboot.elasticsearch.util.PrintUtil;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Resource;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchAllQueryBuilder;
import org.elasticsearch.index.query.MatchPhraseQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.index.query.TermsQueryBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.metrics.StatsAggregationBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.HighlightQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;

@SpringBootTest(classes = SpringbootElasticsearchApplication.class)
class ElasticsearchRestTemplateApplicationTests {

    private static final IndexCoordinates INDEX = IndexCoordinates.of("springboot");

    @Resource
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    /**
     * 创建索引
     */
    @Test
    void creteIndex() {
        boolean success = elasticsearchRestTemplate.indexOps(INDEX).create();
        System.out.println("success = " + success);
    }

    /**
     * 删除索引
     */
    @Test
    void deleteIndex() {
        boolean success = elasticsearchRestTemplate.indexOps(INDEX).delete();
        System.out.println("success = " + success);
    }

    /**
     * 创建文档
     */
    @Test
    void createDocument() {
        SpringBoot springBoot = new SpringBoot(1L, "SpringBoot001", LocalDateTime.now().toString());
        SpringBoot springboot = elasticsearchRestTemplate.save(springBoot, INDEX);
        System.out.println("springboot = " + springboot);
    }

    /**
     * 批量创建文档
     */
    @Test
    void createDocumentBatch() {
        List<SpringBoot> springBoots = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            SpringBoot springBoot = new SpringBoot(i + 1L, "SpringBoot00" + (i + 1), LocalDateTime.now().toString());
            springBoots.add(springBoot);
        }
        Iterable<SpringBoot> save = elasticsearchRestTemplate.save(springBoots, INDEX);
        System.out.println(save);
    }

    /**
     * 查询文档
     */
    @Test
    void queryDocument() {
        // 根据id查询数据
        SpringBoot springboot = elasticsearchRestTemplate.get("1", SpringBoot.class, INDEX);
        System.out.println("springboot = " + springboot);
    }

    /**
     * 删除文档
     */
    @Test
    void deleteDocument() {
        String springboot = elasticsearchRestTemplate.delete("1", INDEX);
        System.out.println("springboot = " + springboot);
    }

    /**
     * 高级查询-查询全部
     */
    @Test
    void advanceQueryDocumentMatchAll() {
        MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
        NativeSearchQuery searchQuery = new NativeSearchQuery(matchAllQueryBuilder);
        SearchHits<SpringBoot> searchHits = elasticsearchRestTemplate.search(searchQuery, SpringBoot.class, INDEX);
        PrintUtil.prettyPrint(searchHits);
    }

    /**
     * 高级查询-匹配查询
     */
    @Test
    void advanceQueryDocumentMatch() {
        MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("name", "SpringBoot001");
        NativeSearchQuery searchQuery = new NativeSearchQuery(queryBuilder);
        SearchHits<SpringBoot> searchHits = elasticsearchRestTemplate.search(searchQuery, SpringBoot.class, INDEX);
        PrintUtil.prettyPrint(searchHits);
    }

    /**
     * 高级查询-短语匹配查询
     */
    @Test
    void advanceQueryDocumentMatchPhrase() {
        MatchPhraseQueryBuilder queryBuilder = QueryBuilders.matchPhraseQuery("name", "SpringBoot001");
        NativeSearchQuery searchQuery = new NativeSearchQuery(queryBuilder);
        SearchHits<SpringBoot> searchHits = elasticsearchRestTemplate.search(searchQuery, SpringBoot.class, INDEX);
        PrintUtil.prettyPrint(searchHits);
    }

    /**
     * 高级查询-多词匹配查询
     */
    @Test
    void advanceQueryDocumentMultiMatch() {
        MultiMatchQueryBuilder queryBuilder = QueryBuilders.multiMatchQuery("SpringBoot001", "name");
        NativeSearchQuery searchQuery = new NativeSearchQuery(queryBuilder);
        SearchHits<SpringBoot> searchHits = elasticsearchRestTemplate.search(searchQuery, SpringBoot.class, INDEX);
        PrintUtil.prettyPrint(searchHits);
    }

    /**
     * 高级查询-精准查询
     */
    @Test
    void advanceQueryDocumentTerm() {
        TermQueryBuilder queryBuilder = QueryBuilders.termQuery("id", "1");
        NativeSearchQuery searchQuery = new NativeSearchQuery(queryBuilder);
        SearchHits<SpringBoot> searchHits = elasticsearchRestTemplate.search(searchQuery, SpringBoot.class, INDEX);
        PrintUtil.prettyPrint(searchHits);
    }


    /**
     * 高级查询-多词精准查询
     */
    @Test
    void advanceQueryDocumentTerms() {
        TermsQueryBuilder queryBuilder = QueryBuilders.termsQuery("id", "1", "2");
        NativeSearchQuery searchQuery = new NativeSearchQuery(queryBuilder);
        SearchHits<SpringBoot> searchHits = elasticsearchRestTemplate.search(searchQuery, SpringBoot.class, INDEX);
        PrintUtil.prettyPrint(searchHits);
    }

    /**
     * 高级查询-分页查询
     */
    @Test
    void advanceQueryDocumentPage() {
        MatchAllQueryBuilder queryBuilder = QueryBuilders.matchAllQuery();

        NativeSearchQuery searchQuery = new NativeSearchQuery(queryBuilder);
        // 设置分页,按照id进行排序,注意排序字段需要是数值类型
        searchQuery.setPageable(PageRequest.of(0, 2, Sort.by(Order.asc("id"))));
        SearchHits<SpringBoot> searchHits = elasticsearchRestTemplate.search(searchQuery, SpringBoot.class, INDEX);
        PrintUtil.prettyPrint(searchHits);
    }

    /**
     * 高级查询-组合查询
     */
    @Test
    void advanceQueryDocumentBool() {
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        queryBuilder.must(QueryBuilders.matchQuery("id", 1L));
        queryBuilder.mustNot(QueryBuilders.matchQuery("name", "SpringBoot002"));
        queryBuilder.should(QueryBuilders.matchQuery("name", "SpringBoot006"));

        NativeSearchQuery searchQuery = new NativeSearchQuery(queryBuilder);
        SearchHits<SpringBoot> searchHits = elasticsearchRestTemplate.search(searchQuery, SpringBoot.class, INDEX);
        PrintUtil.prettyPrint(searchHits);
    }

    /**
     * 高级查询-聚合查询
     */
    @Test
    void advanceQueryDocumentAggregation() {
        MatchAllQueryBuilder queryBuilder = QueryBuilders.matchAllQuery();
        NativeSearchQuery searchQuery = new NativeSearchQuery(queryBuilder);

        // 注意聚合字段需要数值类型
        StatsAggregationBuilder builder = AggregationBuilders.stats("aggs_group");
        builder.field("id");

        searchQuery.addAggregation(builder);
        SearchHits<SpringBoot> searchHits = elasticsearchRestTemplate.search(searchQuery, SpringBoot.class, INDEX);
        PrintUtil.prettyPrint(searchHits);
    }

    /**
     * 高级查询-高亮查询
     */
    @Test
    void advanceQueryDocumentHighlight() {
        MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("name", "SpringBoot001");
        NativeSearchQuery searchQuery = new NativeSearchQuery(queryBuilder);

        HighlightBuilder highlightQueryBuilder = new HighlightBuilder();
        highlightQueryBuilder.preTags("<em>");
        highlightQueryBuilder.postTags("</em>");
        highlightQueryBuilder.field("name");
        searchQuery.setHighlightQuery(new HighlightQuery(highlightQueryBuilder));

        SearchHits<SpringBoot> searchHits = elasticsearchRestTemplate.search(searchQuery, SpringBoot.class, INDEX);
        PrintUtil.prettyPrint(searchHits);
    }
}

三、ElasticsearchRepository

  1. ElasticsearchRepository封装了常用的增删改查操作,但是不支持复杂查询、删除没有返回值,无法确定是否成功删除
  2. ElasticsearchRepository需要创建接口作为实现接口
import com.codecoord.springboot.elasticsearch.domain.Product;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ProductDao extends ElasticsearchRepository<Product, Long> {

}
  1. 创建Product,用于操作文档
import com.alibaba.fastjson.JSONObject;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
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;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Document(indexName = "product", shards = 1, replicas = 1)
public class Product {
    @Id
    private Long id;
    /**
     * 商品名称
     */
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String title;
    /**
     * 分类名称
     */
    @Field(type = FieldType.Keyword)
    private String category;
    /**
     * 商品价格
     */
    @Field(type = FieldType.Double)
    private Double price;
    /**
     * 图片地址
     */
    @Field(type = FieldType.Keyword, index = false)
    private String images;

    @Override
    public String toString() {
        return JSONObject.toJSONString(this);
    }
}
  1. 仓库类使用示例
import com.alibaba.fastjson.JSONObject;
import com.codecoord.springboot.elasticsearch.dao.ProductDao;
import com.codecoord.springboot.elasticsearch.domain.Product;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import javax.annotation.Resource;
import org.junit.jupiter.api.Test;
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.data.domain.Sort.Order;

@SpringBootTest(classes = SpringbootElasticsearchApplication.class)
class ElasticsearchRepositoryApplicationTests {

    @Resource
    private ProductDao productDao;

    /**
     * 创建文档
     */
    @Test
    void createDocument() {
        Product product = new Product(1L, "HUAWEI Mate30", "华为", 4999D, "http://www.codecoord.com/favicon.ico");
        Product save = productDao.save(product);
        System.out.println(save);
    }

    /**
     * 批量创建文档
     */
    @Test
    void createDocumentBatch() {
        List<Product> productList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            productList.add(new Product(i + 1L, "HUAWEI Mate30", "华为", 3999D, String.valueOf(i)));
        }
        Iterable<Product> products = productDao.saveAll(productList);
        System.out.println(products);
    }

    /**
     * 查询文档
     */
    @Test
    void queryDocument() {
        // 通过id单个查询
        Optional<Product> product = productDao.findById(1L);
        System.out.println(product);

        printLine();

        // 通过id批量查询
        List<Long> ids = Arrays.asList(1L, 2L);
        Iterable<Product> products = productDao.findAllById(ids);
        System.out.println(products);

        printLine();

        // 查询所有(排序)
        Iterable<Product> sortProduct = productDao.findAll(Sort.by(Order.desc("id")));
        System.out.println(sortProduct);

        printLine();

        // 分页查询,按照id进行排序
        Page<Product> productPage = productDao.findAll(PageRequest.of(0, 3, Sort.by(Order.asc("id"))));
        System.out.println(JSONObject.toJSONString(productPage));
    }

    /**
     * 统计文档
     */
    @Test
    void countDocument() {
        // 计数
        long count = productDao.count();
        System.out.println("count = " + count);

        printLine();

        // 判断id是否存在
        boolean exists = productDao.existsById(1L);
        System.out.println("exists = " + exists);
    }

    /**
     * 更新文档
     */
    @Test
    void updateDocument() {
        Product product = new Product(1L, "HUAWEI Mate30", "华为", 4999D, "更新");
        Product save = productDao.save(product);
        System.out.println(save);
    }


    /**
     * 删除文档(注意此处删除没有返回值,无法确定删除结果)
     */
    @Test
    void deleteDocument() {
        // 删除所有
        // productDao.deleteAll();
        // 根据id删除
        productDao.deleteById(1L);
    }

    private void printLine() {
        System.out.println("-------------------------------");
    }
}