springboot集成Elasticsearch和简单应用

2 阅读16分钟

Spring Boot + Elasticsearch


一、项目结构

es-demo/
├── pom.xml
├── src/
│   ├── main/
│   │   ├── java/com/nd/es/
│   │   │   ├── EsApplication.java
│   │   │   ├── config/
│   │   │   │   ├── ElasticsearchConfig.java
│   │   │   │   └── ElasticsearchIndexInitializer.java
│   │   │   ├── controller/
│   │   │   │   └── BookController.java
│   │   │   ├── entity/
│   │   │   │   └── Book.java
│   │   │   ├── repository/
│   │   │   │   └── BookRepository.java
│   │   │   └── service/
│   │   │       └── BookService.java
│   │   └── resources/
│   │       └── application.yml

二、pom.xml

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <!-- ✅ 降级到 2.3.x,该版本默认集成 ES 7.6 -->
        <version>2.3.12.RELEASE</version>
        <relativePath/>
</parent>
    
   <dependencies>
        <!-- Spring Data Elasticsearch -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
​
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
​
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
​
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
​
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

三、application.yml

spring:
  elasticsearch:
    rest:
      uris: http://192.168.135.132:9200


# 自定义索引配置
elasticsearch:
  index:
    name: book
    shards: 5
    replicas: 1

四、entity/Book.java

@Data
@Document(indexName = "book", shards = 5, replicas = 1)
public class Book {
    @Id
    private String id;
​
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String name;
​
    @Field(type = FieldType.Keyword)
    private String auth;
​
    @Field(type = FieldType.Long)
    private Long count;
​
    @Field(type = FieldType.Date, format = DateFormat.date)
    private Date createtime;
​
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String desc;
}

五、repository/BookRepository.java

package com.nd.es.repository;
​
@Repository
public interface BookRepository extends ElasticsearchRepository<Book, String> {
    List<Book> findByName(String name);
    List<Book> findByNameIn(List<String> names);
    List<Book> findByNameLike(String name);
    List<Book> findByCountBetween(Long min, Long max);
    List<Book> findByNameStartingWith(String prefix);
    List<Book> findByNameAndAuth(String name, String auth);
    List<Book> findByNameOrAuth(String name, String auth);
}

六、service/BookService.java

package com.nd.es.service;
​
import com.nd.es.entity.Book;
import com.nd.es.repository.BookRepository;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.range.Range;
import org.elasticsearch.search.aggregations.metrics.Cardinality;
import org.elasticsearch.search.aggregations.metrics.ExtendedStats;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.*;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
​
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
​
@Slf4j
@Service
public class BookService {
​
    @Autowired
    private BookRepository bookRepository;
​
    @Autowired
    private ElasticsearchRestTemplate template;
​
    // ==================== 索引管理 ====================
    public void createIndex() {
        if (!template.indexOps(Book.class).exists()) {
            template.indexOps(Book.class).create();
            log.info("索引创建成功");
        }
    }
​
    public void deleteIndex() {
        if (template.indexOps(Book.class).exists()) {
            template.indexOps(Book.class).delete();
            log.info("索引删除成功");
        }
    }
​
    // ==================== 文档操作 ====================
    public Book save(Book book) {
        return bookRepository.save(book);
    }
​
    public void saveAll(List<Book> books) {
        bookRepository.saveAll(books);
    }
​
    public Optional<Book> findById(String id) {
        return bookRepository.findById(id);
    }
​
    public void deleteById(String id) {
        bookRepository.deleteById(id);
    }
​
    public List<Book> findAll() {
        Iterable<Book> iterable = bookRepository.findAll();
        return StreamSupport.stream(iterable.spliterator(), false)
                .collect(Collectors.toList());
    }
​
    // ==================== 批量操作 ====================
    public List<Book> bulkCreate(List<Book> books) {
        return (List<Book>) bookRepository.saveAll(books);
    }
​
    public void bulkDelete(List<String> ids) {
        List<Book> books = ids.stream().map(id -> {
            Book book = new Book();
            book.setId(id);
            return book;
        }).collect(Collectors.toList());
        bookRepository.deleteAll(books);
    }
​
    // ==================== Term查询 ====================
    public List<Book> findByName(String name) {
        return bookRepository.findByName(name);
    }
​
    public List<Book> findByNameIn(List<String> names) {
        return bookRepository.findByNameIn(names);
    }
​
    // ==================== Match查询 ====================
    public List<Book> searchByName(String name) {
        QueryBuilder queryBuilder = QueryBuilders.matchQuery("name", name);
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .build();
        SearchHits<Book> searchHits = template.search(query, Book.class);
        return searchHits.get().map(hit -> hit.getContent()).collect(Collectors.toList());
    }
​
    public List<Book> searchByNameLike(String name) {
        return bookRepository.findByNameLike(name);
    }
​
    public List<Book> multiMatchSearch(String keyword) {
        QueryBuilder queryBuilder = QueryBuilders.multiMatchQuery(keyword, "name", "desc");
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .build();
        SearchHits<Book> searchHits = template.search(query, Book.class);
        return searchHits.get().map(hit -> hit.getContent()).collect(Collectors.toList());
    }
​
    // ==================== 布尔查询 ====================
    public List<Book> boolSearch(String name, String auth) {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
                .must(QueryBuilders.matchQuery("name", name))
                .must(QueryBuilders.termQuery("auth", auth));
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(boolQuery)
                .build();
        SearchHits<Book> searchHits = template.search(query, Book.class);
        return searchHits.get().map(hit -> hit.getContent()).collect(Collectors.toList());
    }
​
    public List<Book> boolShouldSearch(String name, String auth) {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
                .should(QueryBuilders.matchQuery("name", name))
                .should(QueryBuilders.matchQuery("auth", auth))
                .minimumShouldMatch(1);
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(boolQuery)
                .build();
        SearchHits<Book> searchHits = template.search(query, Book.class);
        return searchHits.get().map(hit -> hit.getContent()).collect(Collectors.toList());
    }
​
    // ==================== 范围查询 ====================
    public List<Book> rangeSearch(Long min, Long max) {
        QueryBuilder queryBuilder = QueryBuilders.rangeQuery("count").from(min).to(max);
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .build();
        SearchHits<Book> searchHits = template.search(query, Book.class);
        return searchHits.get().map(hit -> hit.getContent()).collect(Collectors.toList());
    }
​
    public List<Book> dateRangeSearch(String startDate, String endDate) {
        QueryBuilder queryBuilder = QueryBuilders.rangeQuery("createtime")
                .from(startDate).to(endDate)
                .format("yyyy-MM-dd");
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .build();
        SearchHits<Book> searchHits = template.search(query, Book.class);
        return searchHits.get().map(hit -> hit.getContent()).collect(Collectors.toList());
    }
​
    // ==================== 前缀查询 ====================
    public List<Book> prefixSearch(String prefix) {
        QueryBuilder queryBuilder = QueryBuilders.prefixQuery("name", prefix);
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .build();
        SearchHits<Book> searchHits = template.search(query, Book.class);
        return searchHits.get().map(hit -> hit.getContent()).collect(Collectors.toList());
    }
​
    // ==================== 通配符查询 ====================
    public List<Book> wildcardSearch(String pattern) {
        QueryBuilder queryBuilder = QueryBuilders.wildcardQuery("name", pattern);
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .build();
        SearchHits<Book> searchHits = template.search(query, Book.class);
        return searchHits.get().map(hit -> hit.getContent()).collect(Collectors.toList());
    }
​
    // ==================== 模糊查询 ====================
    public List<Book> fuzzySearch(String value, int prefixLength) {
        QueryBuilder queryBuilder = QueryBuilders.fuzzyQuery("name", value).prefixLength(prefixLength);
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .build();
        SearchHits<Book> searchHits = template.search(query, Book.class);
        return searchHits.get().map(hit -> hit.getContent()).collect(Collectors.toList());
    }
​
    // ==================== 正则查询 ====================
    public List<Book> regexpSearch(String regexp) {
        QueryBuilder queryBuilder = QueryBuilders.regexpQuery("name", regexp);
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .build();
        SearchHits<Book> searchHits = template.search(query, Book.class);
        return searchHits.get().map(hit -> hit.getContent()).collect(Collectors.toList());
    }
​
    // ==================== ID查询 ====================
    public Book findByIdQuery(String id) {
        return bookRepository.findById(id).orElse(null);
    }
​
    public List<Book> findByIds(List<String> ids) {
        QueryBuilder queryBuilder = QueryBuilders.idsQuery().addIds(ids.toArray(new String[0]));
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .build();
        SearchHits<Book> searchHits = template.search(query, Book.class);
        return searchHits.get().map(hit -> hit.getContent()).collect(Collectors.toList());
    }
​
    // ==================== 高亮查询 ====================
    public List<Book> highlightSearch(String keyword) {
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        HighlightBuilder.Field nameField = new HighlightBuilder.Field("name")
                .preTags("<font color='red'>")
                .postTags("</font>");
        HighlightBuilder.Field descField = new HighlightBuilder.Field("desc")
                .preTags("<font color='red'>")
                .postTags("</font>");
​
        highlightBuilder.field(nameField);
        highlightBuilder.field(descField);
​
        QueryBuilder queryBuilder = QueryBuilders.multiMatchQuery(keyword, "name", "desc");
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .withHighlightBuilder(highlightBuilder)
                .build();
​
        SearchHits<Book> searchHits = template.search(query, Book.class);
​
        return searchHits.getSearchHits().stream()
                .map(hit -> {
                    Book book = hit.getContent();
                    Map<String, List<String>> highlightFields = hit.getHighlightFields();
​
                    if (highlightFields.containsKey("name")) {
                        book.setName(highlightFields.get("name").get(0));
                    }
                    if (highlightFields.containsKey("desc")) {
                        book.setDesc(highlightFields.get("desc").get(0));
                    }
                    return book;
                })
                .collect(Collectors.toList());
    }
​
    // ==================== 聚合查询 ====================
    public Map<String, Object> statsSearch() {
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .addAggregation(AggregationBuilders.extendedStats("count_stats").field("count"))
                .build();
​
        SearchHits<Book> searchHits = template.search(query, Book.class);
        ExtendedStats extendedStats = searchHits.getAggregations().get("count_stats");
​
        Map<String, Object> stats = new HashMap<>();
        stats.put("max", extendedStats.getMax());
        stats.put("min", extendedStats.getMin());
        stats.put("avg", extendedStats.getAvg());
        stats.put("sum", extendedStats.getSum());
        stats.put("count", extendedStats.getCount());
        return stats;
    }
​
    public long cardinalitySearch(String field) {
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .addAggregation(AggregationBuilders.cardinality("cardinality_agg").field(field))
                .build();
​
        SearchHits<Book> searchHits = template.search(query, Book.class);
        Cardinality cardinality = searchHits.getAggregations().get("cardinality_agg");
        return cardinality.getValue();
    }
​
    // ==================== 以下是补充的缺失方法 ====================
​
    public Map<String, Object> rangeStats(Long min, Long max) {
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .addAggregation(AggregationBuilders.range("count_ranges")
                        .field("count")
                        .addUnboundedTo(min)        // < min
                        .addRange(min, max)         // min - max
                        .addUnboundedFrom(max))     // > max
                .build();
​
        SearchHits<Book> searchHits = template.search(query, Book.class);
        Range rangeAgg = searchHits.getAggregations().get("count_ranges");
​
        Map<String, Object> result = new HashMap<>();
        for (Range.Bucket bucket : rangeAgg.getBuckets()) {
            result.put(bucket.getKeyAsString(), bucket.getDocCount());
        }
        return result;
    }
​
    public long deleteByQuery(String field, String value) {
        // 构建查询条件
        QueryBuilder queryBuilder = QueryBuilders.matchQuery(field, value);
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .build();
​
        // 执行搜索获取结果
        SearchHits<Book> searchHits = template.search(query, Book.class);
​
        // 收集所有匹配文档的ID
        List<String> idsToDelete = searchHits.get().map(hit -> hit.getContent().getId())
                .collect(Collectors.toList());
​
        // 根据ID列表删除文档
        if (!idsToDelete.isEmpty()) {
            Iterable<Book> booksToDelete = idsToDelete.stream().map(id -> {
                Book book = new Book();
                book.setId(id);
                return book;
            }).collect(Collectors.toList());
​
            bookRepository.deleteAll(booksToDelete);
        }
​
        return idsToDelete.size(); // 返回删除的文档数量
    }
​
​
    public List<Book> boostingSearch(String positiveField, String positiveValue,
                                     String negativeField, String negativeValue,
                                     float negativeBoost) {
        BoostingQueryBuilder boostingQuery = QueryBuilders.boostingQuery(
                QueryBuilders.matchQuery(positiveField, positiveValue),
                QueryBuilders.matchQuery(negativeField, negativeValue)
        ).negativeBoost(negativeBoost);
​
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(boostingQuery)
                .build();
​
        SearchHits<Book> searchHits = template.search(query, Book.class);
        return searchHits.get().map(hit -> hit.getContent()).collect(Collectors.toList());
    }
​
    public List<Book> filterSearch(String mustField, String mustValue,
                                   String filterField, String filterValue,
                                   Long minCount, Long maxCount) {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
                .must(QueryBuilders.matchQuery(mustField, mustValue))
                .filter(QueryBuilders.termQuery(filterField, filterValue))
                .filter(QueryBuilders.rangeQuery("count").from(minCount).to(maxCount));
​
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(boolQuery)
                .build();
​
        SearchHits<Book> searchHits = template.search(query, Book.class);
        return searchHits.get().map(hit -> hit.getContent()).collect(Collectors.toList());
    }
​
    // ==================== 深分页 Scroll ====================
    public Map<String, Object> scrollSearchFirst(int size) {
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(QueryBuilders.matchAllQuery())
                .withPageable(PageRequest.of(0, size))
                .build();
​
        SearchScrollHits<Book> scrollHits = template.searchScrollStart(60000, query, Book.class, IndexCoordinates.of("book"));
        String scrollId = scrollHits.getScrollId();
        List<Book> books = scrollHits.getSearchHits().stream()
                .map(hit -> hit.getContent())
                .collect(Collectors.toList());
​
        Map<String, Object> result = new HashMap<>();
        result.put("scrollId", scrollId);
        result.put("data", books);
        result.put("totalHits", scrollHits.getTotalHits());
        return result;
    }
​
    // 替换原有的 scrollSearchNext 方法
    public List<Book> scrollSearchNext(String scrollId) {
        SearchScrollHits<Book> scrollHits = template.searchScrollContinue(scrollId, 60000, Book.class, IndexCoordinates.of("book"));
​
        return scrollHits.getSearchHits().stream()
                .map(SearchHit::getContent)
                .collect(Collectors.toList());
    }
​
​
    public void scrollClear(String scrollId) {
        template.searchScrollClear(Collections.singletonList(scrollId));
    }
​
​
    // ==================== 地图查询 ====================
    // ==================== 地图查询 ====================
    // ==================== 地图查询 - 返回Book实体 ====================
    public List<Book> geoDistanceSearch(double lat, double lon, String distance) {
        QueryBuilder queryBuilder = QueryBuilders.geoDistanceQuery("location")
                .point(lat, lon)
                .distance(distance, DistanceUnit.KILOMETERS);
​
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .build();
​
        SearchHits<Book> searchHits = template.search(query, Book.class);
        return searchHits.get().map(SearchHit::getContent).collect(Collectors.toList());
    }
​
    public List<Book> geoBoundingBoxSearch(GeoPoint topLeft, GeoPoint bottomRight) {
        QueryBuilder queryBuilder = QueryBuilders.geoBoundingBoxQuery("location")
                .setCorners(topLeft, bottomRight);
​
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .build();
​
        SearchHits<Book> searchHits = template.search(query, Book.class);
        return searchHits.get().map(SearchHit::getContent).collect(Collectors.toList());
    }
​
    public List<Book> geoPolygonSearch(List<GeoPoint> points) {
        QueryBuilder queryBuilder = QueryBuilders.geoPolygonQuery("location", points);
​
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .build();
​
        SearchHits<Book> searchHits = template.search(query, Book.class);
        return searchHits.get().map(SearchHit::getContent).collect(Collectors.toList());
    }
}

十二、controller/BookController.java

package com.nd.es.controller;
​
import com.nd.es.entity.Book;
import com.nd.es.service.BookService;
import org.elasticsearch.common.geo.GeoPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
​
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
​
@RestController
@RequestMapping("/api/books")
@CrossOrigin(origins = "*")
/**
 * BookController类 - 处理图书相关的HTTP请求
 * 该控制器提供了图书的索引管理、文档操作、批量操作、各种查询方式等功能
 */
public class BookController {
    /**
     * 自动注入BookService服务
     * 用于处理图书相关的业务逻辑
     */
    @Autowired
    private BookService bookService;
​
    // ==================== 索引管理 ====================
    /**
     * 创建索引
     * @return 返回创建成功的消息
     */
    @PostMapping("/index")
    public String createIndex() {
        bookService.createIndex();
        return "索引创建成功";
    }
​
    /**
     * 删除索引
     * @return 返回删除成功的消息
     */
    @DeleteMapping("/index")
    public String deleteIndex() {
        bookService.deleteIndex();
        return "索引删除成功";
    }
​
    // ==================== 文档操作 ====================
    /**
     * 保存单个图书
     * @param book 要保存的图书对象
     * @return 返回保存后的图书对象
     */
    @PostMapping
    public Book save(@RequestBody Book book) {
        return bookService.save(book);
    }
​
    /**
     * 批量保存图书
     * @param books 图书列表
     * @return 返回批量添加成功的消息
     */
    @PostMapping("/batch")
    public String saveAll(@RequestBody List<Book> books) {
        bookService.saveAll(books);
        return "批量添加成功";
    }
​
    /**
     * 根据ID查找图书
     * @param id 图书ID
     * @return 返回找到的图书对象,如果不存在则返回null
     */
    @GetMapping("/{id}")
    public Book findById(@PathVariable String id) {
        Optional<Book> book = bookService.findById(id);
        return book.orElse(null);
    }
​
    /**
     * 根据ID删除图书
     * @param id 图书ID
     * @return 返回删除成功的消息
     */
    @DeleteMapping("/{id}")
    public String deleteById(@PathVariable String id) {
        bookService.deleteById(id);
        return "删除成功";
    }
​
    /**
     * 查找所有图书
     * @return 返回所有图书的列表
     */
    @GetMapping
    public List<Book> findAll() {
        return bookService.findAll();
    }
​
    // ==================== 批量操作 ====================
    /**
     * 批量创建图书
     * @param books 图书列表
     * @return 返回创建成功的图书列表
     */
    @PostMapping("/batch/create")
    public List<Book> bulkCreate(@RequestBody List<Book> books) {
        return bookService.bulkCreate(books);
    }
​
    /**
     * 批量删除图书
     * @param ids 要删除的图书ID列表
     * @return 返回批量删除成功的消息
     */
    @PostMapping("/batch/delete")
    public String bulkDelete(@RequestBody List<String> ids) {
        bookService.bulkDelete(ids);
        return "批量删除成功";
    }
​
    // ==================== Term查询 ====================
    /**
     * 根据名称精确查询图书
     * @param name 图书名称
     * @return 返回匹配名称的图书列表
     */
    @GetMapping("/search/name")
    public List<Book> findByName(@RequestParam String name) {
        return bookService.findByName(name);
    }
​
    /**
     * 根据名称列表查询图书
     * @param names 图书名称列表
     * @return 返回匹配名称列表的图书列表
     */
    @PostMapping("/search/names")
    public List<Book> findByNameIn(@RequestBody List<String> names) {
        return bookService.findByNameIn(names);
    }
​
    // ==================== Match查询 ====================
    /**
     * 根据名称进行全文搜索
     * @param name 搜索关键词
     * @return 返回匹配的图书列表
     */
    @GetMapping("/search/match")
    public List<Book> searchByName(@RequestParam String name) {
        return bookService.searchByName(name);
    }
​
    /**
     * 根据名称进行模糊查询
     * @param name 搜索关键词
     * @return 返回匹配的图书列表
     */
    @GetMapping("/search/like")
    public List<Book> searchByNameLike(@RequestParam String name) {
        return bookService.searchByNameLike(name);
    }
​
    /**
     * 多字段全文搜索
     * @param keyword 搜索关键词
     * @return 返回匹配的图书列表
     */
    @GetMapping("/search/multi-match")
    public List<Book> multiMatchSearch(@RequestParam String keyword) {
        return bookService.multiMatchSearch(keyword);
    }
​
    // ==================== 布尔查询 ====================
    /**
     * 布尔查询
     * @param name 图书名称
     * @param auth 作者
     * @return 返回匹配的图书列表
     */
    @GetMapping("/search/bool")
    public List<Book> boolSearch(@RequestParam String name, @RequestParam String auth) {
        return bookService.boolSearch(name, auth);
    }
​
    /**
     * 布尔Should查询
     * @param name 图书名称
     * @param auth 作者
     * @return 返回匹配的图书列表
     */
    @GetMapping("/search/bool/should")
    public List<Book> boolShouldSearch(@RequestParam String name, @RequestParam String auth) {
        return bookService.boolShouldSearch(name, auth);
    }
​
    // ==================== 范围查询 ====================
    /**
     * 数值范围查询
     * @param min 最小值
     * @param max 最大值
     * @return 返回在指定范围内的图书列表
     */
    @GetMapping("/search/range")
    public List<Book> rangeSearch(@RequestParam Long min, @RequestParam Long max) {
        return bookService.rangeSearch(min, max);
    }
​
    /**
     * 日期范围查询
     * @param startDate 开始日期
     * @param endDate 结束日期
     * @return 返回在指定日期范围内的图书列表
     */
    @GetMapping("/search/date-range")
    public List<Book> dateRangeSearch(@RequestParam String startDate, @RequestParam String endDate) {
        return bookService.dateRangeSearch(startDate, endDate);
    }
​
    // ==================== 前缀查询 ====================
    /**
     * 前缀查询
     * @param prefix 前缀字符串
     * @return 返回以前缀开头的图书列表
     */
    @GetMapping("/search/prefix")
    public List<Book> prefixSearch(@RequestParam String prefix) {
        return bookService.prefixSearch(prefix);
    }
​
    // ==================== 通配符查询 ====================
    /**
     * 通配符查询
     * @param pattern 通配符模式
     * @return 返回匹配通配符模式的图书列表
     */
    @GetMapping("/search/wildcard")
    public List<Book> wildcardSearch(@RequestParam String pattern) {
        return bookService.wildcardSearch(pattern);
    }
​
    // ==================== 模糊查询 ====================
    /**
     * 模糊查询
     * @param value 搜索值
     * @param prefixLength 前缀长度,默认为2
     * @return 返回模糊匹配的图书列表
     */
    @GetMapping("/search/fuzzy")
    public List<Book> fuzzySearch(@RequestParam String value, @RequestParam(defaultValue = "2") int prefixLength) {
        return bookService.fuzzySearch(value, prefixLength);
    }
​
    // ==================== 正则查询 ====================
    /**
     * 正则表达式查询
     * @param regexp 正则表达式
     * @return 返回匹配正则表达式的图书列表
     */
    @GetMapping("/search/regexp")
    public List<Book> regexpSearch(@RequestParam String regexp) {
        return bookService.regexpSearch(regexp);
    }
​
    // ==================== ID查询 ====================
    /**
     * 根据ID查询图书
     * @param id 图书ID
     * @return 返回匹配ID的图书
     */
    @GetMapping("/search/id/{id}")
    public Book findByIdQuery(@PathVariable String id) {
        return bookService.findByIdQuery(id);
    }
​
    /**
     * 根据ID列表查询图书
     * @param ids 图书ID列表
     * @return 返回匹配ID列表的图书列表
     */
    @PostMapping("/search/ids")
    public List<Book> findByIds(@RequestBody List<String> ids) {
        return bookService.findByIds(ids);
    }
​
    // ==================== 高亮查询 ====================
    /**
     * 高亮搜索
     * @param keyword 搜索关键词
     * @return 返回高亮显示的图书列表
     */
    @GetMapping("/search/highlight")
    public List<Book> highlightSearch(@RequestParam String keyword) {
        return bookService.highlightSearch(keyword);
    }
​
    // ==================== 聚合查询 ====================
    /**
     * 统计查询
     * @return 返回包含各种统计信息的Map
     */
    @GetMapping("/stats")
    public Map<String, Object> statsSearch() {
        return bookService.statsSearch();
    }
​
    /**
     * 基数查询
     * @param field 要查询的字段
     * @return 返回包含基数统计信息的Map
     */
    @GetMapping("/stats/cardinality")
    public Map<String, Object> cardinalitySearch(@RequestParam String field) {
        long count = bookService.cardinalitySearch(field);
        Map<String, Object> map = new HashMap<>();
        map.put("count", count);
        return map;
    }
​
    /**
     * 范围统计查询
     * @param min 最小值
     * @param max 最大值
     * @return 返回包含范围统计信息的Map
     */
    @GetMapping("/stats/range")
    public Map<String, Object> rangeStats(@RequestParam Long min, @RequestParam Long max) {
        return bookService.rangeStats(min, max);
    }
​
    // ==================== delete-by-query ====================
    /**
     * 根据条件删除文档
     * @param field 字段名
     * @param value 字段值
     * @return 返回删除的文档数量
     */
    @DeleteMapping("/delete/by-query")
    public long deleteByQuery(@RequestParam String field, @RequestParam String value) {
        return bookService.deleteByQuery(field, value);
    }
​
    // ==================== boosting 查询 ====================
    /**
     * Boosting查询
     * @param positiveField 正向字段名
     * @param positiveValue 正向字段值
     * @param negativeField 负向字段名
     * @param negativeValue 负向字段值
     * @param negativeBoost 负向权重,默认为0.2
     * @return 返回Boosting查询结果的图书列表
     */
    @GetMapping("/search/boosting")
    public List<Book> boostingSearch(@RequestParam String positiveField,
                                     @RequestParam String positiveValue,
                                     @RequestParam String negativeField,
                                     @RequestParam String negativeValue,
                                     @RequestParam(defaultValue = "0.2") float negativeBoost) {
        return bookService.boostingSearch(positiveField, positiveValue,
                                        negativeField, negativeValue, negativeBoost);
    }
​
    // ==================== filter 查询 ====================
    /**
     * Filter查询
     * @param mustField 必须匹配的字段名
     * @param mustValue 必须匹配的字段值
     * @param filterField 过滤字段名
     * @param filterValue 过滤字段值
     * @param minCount 最小数量
     * @param maxCount 最大数量
     * @return 返回Filter查询结果的图书列表
     */
    @GetMapping("/search/filter")
    public List<Book> filterSearch(@RequestParam String mustField,
                                   @RequestParam String mustValue,
                                   @RequestParam String filterField,
                                   @RequestParam String filterValue,
                                   @RequestParam Long minCount,
                                   @RequestParam Long maxCount) {
        return bookService.filterSearch(mustField, mustValue, filterField, filterValue, minCount, maxCount);
    }
​
    // ==================== 深分页 Scroll ====================
    /**
     * 首次Scroll查询
     * @param size 每页大小,默认为10
     * @return 返回包含结果和scrollId的Map
     */
    @GetMapping("/search/scroll/first")
    public Map<String, Object> scrollSearchFirst(@RequestParam(defaultValue = "10") int size) {
        return bookService.scrollSearchFirst(size);
    }
​
    /**
     * 后续Scroll查询
     * @param scrollId 上次查询返回的scrollId
     * @return 返回下一页的图书列表
     */
    @GetMapping("/search/scroll/next")
    public List<Book> scrollSearchNext(@RequestParam String scrollId) {
        return bookService.scrollSearchNext(scrollId);
    }
​
    /**
     * 清除Scroll上下文
     * @param scrollId 要清除的scrollId
     * @return 返回清除成功的消息
     */
    @DeleteMapping("/search/scroll/clear")
    public String scrollClear(@RequestParam String scrollId) {
        bookService.scrollClear(scrollId);
        return "清除成功";
    }
​
    // ==================== 地图查询 ====================
//
    @GetMapping("/search/geo/distance")
    public List<Book> geoDistanceSearch(@RequestParam double lat,
                                        @RequestParam double lon,
                                        @RequestParam String distance) {
        return bookService.geoDistanceSearch(lat, lon, distance);
    }
​
    @GetMapping("/search/geo/box")
    public List<Book> geoBoundingBoxSearch(@RequestParam double topLeftLat,
                                           @RequestParam double topLeftLon,
                                           @RequestParam double bottomRightLat,
                                           @RequestParam double bottomRightLon) {
        GeoPoint topLeft = new GeoPoint(topLeftLat, topLeftLon);
        GeoPoint bottomRight = new GeoPoint(bottomRightLat, bottomRightLon);
        return bookService.geoBoundingBoxSearch(topLeft, bottomRight);
    }
​
    @PostMapping("/search/geo/polygon")
    public List<Book> geoPolygonSearch(@RequestBody List<double[]> points) {
        List<GeoPoint> geoPoints = points.stream()
                .map(p -> new GeoPoint(p[0], p[1]))
                .collect(Collectors.toList());
        return bookService.geoPolygonSearch(geoPoints);
    }
}

十三、配置类

config/ElasticsearchIndexInitializer.java

在 Spring Boot 应用启动时,自动检查并创建 Elasticsearch 索引

package com.nd.es.config;
​
​
@Component
public class ElasticsearchIndexInitializer implements CommandLineRunner {
    @Autowired
    private ElasticsearchOperations operations;
    
    @Override
    public void run(String... args) throws Exception {
        if (!operations.indexOps(Book.class).exists()) {
            operations.indexOps(Book.class).create();
        }
    }
}

十四、EsApplication.java

package com.nd.es;
​
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
​
@SpringBootApplication
public class EsApplication {
    public static void main(String[] args) {
        SpringApplication.run(EsApplication.class, args);
    }
}

Elasticsearch Query


一、全文查询(Full-Text Queries)

特点:会对查询文本进行分词,计算相关性得分(_score),用于 text 字段。

查询类型用途DSL 示例
match单字段全文搜索(最常用){"match": {"title": "Elasticsearch实战"}}
multi_match多字段全文搜索{"multi_match": {"query": "ES", "fields": ["title", "desc"]}}
match_phrase短语精确匹配(保持词序){"match_phrase": {"desc": "分布式搜索引擎"}}
match_phrase_prefix短语前缀匹配(自动补全){"match_phrase_prefix": {"title": "Elastic"}}
query_string支持Lucene语法的高级查询{"query_string": {"query": "title:ES AND auth:张三"}}
simple_query_string简化版 query_string(容错性更好){"simple_query_string": {"query": "title:ES auth:张三"}}
fuzzy模糊匹配(允许拼写错误){"fuzzy": {"title": {"value": "Elastcsearch", "fuzziness": 2}}}

二、精确查询(Term-Level Queries)

特点:不对查询条件分词,用于精确值匹配(keyword、数字、日期等)。

查询类型用途DSL 示例
term单值精确匹配{"term": {"status": 1}}
terms多值精确匹配(OR关系){"terms": {"tag": ["java", "python"]}}
range范围查询(gte/lte/gt/lt){"range": {"price": {"gte": 50, "lte": 100}}}
prefix前缀匹配{"prefix": {"name.keyword": "El"}}
wildcard通配符匹配(* ?{"wildcard": {"name.keyword": "El*ch"}}
regexp正则表达式匹配{"regexp": {"code.keyword": "A\d{3}"}}
exists字段是否存在{"exists": {"field": "desc"}}
ids根据ID列表查询{"ids": {"values": ["1", "2", "3"]}}

三、复合查询(Compound Queries)

特点:组合多个子查询,实现复杂逻辑。

算分(Scoring) :Elasticsearch 为每个匹配的文档计算一个相关性得分(_score) ,用于排序。得分越高,文档越相关

1. bool 查询(最常用)

{
  "query": {
    "bool": {
      "must": [],        // AND - 必须匹配,参与算分
      "filter": [],      // AND - 必须匹配,不参与算分(可缓存,高性能)
      "should": [],      // OR  - 应该匹配,参与算分
      "must_not": []     // NOT - 必须不匹配,不参与算分
    }
  }
}

示例

{
  "query": {
    "bool": {
      "must": [
        {"match": {"title": "ES"}}
      ],
      "filter": [
        {"range": {"price": {"gte": 50}}},
        {"term": {"status": 1}}
      ],
      "should": [
        {"term": {"tag.keyword": "hot"}}
      ],
      "must_not": [
        {"term": {"stock": 0}}
      ]
    }
  }
}

2. 其他复合查询

查询类型用途
boosting提升/降低相关性得分
constant_score固定得分查询(filter结果设固定分)
dis_max返回多个查询中得分最高的结果
function_score使用函数自定义评分逻辑

四、关联查询(Join Queries)

用于处理文档间关系(嵌套、父子)

查询类型用途
nested查询嵌套对象数组
has_child查询有特定子文档的父文档
has_parent查询有特定父文档的子文档

五、特殊化查询(Specialized Queries)

查询类型用途
more_like_this查找相似文档(内容推荐)
script使用脚本执行查询
percolate反向查询(文档匹配查询)
wrapper包装其他查询(如Base64编码)

六、地理查询(Geo Queries)

查询类型用途
geo_distance距离某点指定范围内的文档
geo_bounding_box矩形区域内的文档
geo_polygon多边形区域内的文档
geo_shape复杂地理形状查询

七、特殊查询

1. 匹配所有文档

{"query": {"match_all": {}}}

2. 不匹配任何文档

{"query": {"match_none": {}}}

Query 与 Filter 的区别

特性QueryFilter
是否算分是(影响排序)否(固定分为0)
是否缓存是(性能更高)
使用场景全文搜索、相关性匹配精确过滤、范围筛选

最佳实践:过滤条件放在 bool.filter 中,搜索条件放在 bool.must/should 中。

对应的 Elasticsearch DSL 语句


一、索引管理

1. 创建索引 createIndex()

PUT /book
{
  "settings": {
    "number_of_shards": 5,
    "number_of_replicas": 1
  },
  "mappings": {
    "properties": {
      "id": {"type": "keyword"},
      "name": {"type": "text", "analyzer": "ik_max_word"},
      "auth": {"type": "keyword"},
      "count": {"type": "long"},
      "createtime": {"type": "date"},
      "desc": {"type": "text", "analyzer": "ik_max_word"}
    }
  }
}

2. 删除索引 deleteIndex()

DELETE /book

二、文档操作

3. 保存单个文档 save(@RequestBody Book book)

POST /book/_doc
{
  "name": "Elasticsearch实战",
  "auth": "张三",
  "count": 100,
  "createtime": "2024-01-01",
  "desc": "Elasticsearch入门到精通"
}
# 或指定ID更新
PUT /book/_doc/{id}

4. 批量保存 saveAll(@RequestBody List<Book> books)

POST /book/_bulk
{"index":{"_id":"1"}}
{"name":"Book1","auth":"Author1","count":10,"createtime":"2024-01-01","desc":"desc1"}
{"index":{"_id":"2"}}
{"name":"Book2","auth":"Author2","count":20,"createtime":"2024-01-02","desc":"desc2"}

5. 根据ID查找 findById(@PathVariable String id)

GET /book/_doc/{id}

6. 根据ID删除 deleteById(@PathVariable String id)

DELETE /book/_doc/{id}

7. 查找所有 findAll()

GET /book/_search
{
  "query": {"match_all": {}},
  "size": 100
}

三、批量操作

8. 批量创建 bulkCreate(@RequestBody List<Book> books)

POST /book/_bulk
{"index":{"_id":"1"}}
{"name":"Book1","auth":"Author1","count":10,"createtime":"2024-01-01","desc":"desc1"}
{"index":{"_id":"2"}}
{"name":"Book2","auth":"Author2","count":20,"createtime":"2024-01-02","desc":"desc2"}

9. 批量删除 bulkDelete(@RequestBody List<String> ids)

POST /book/_bulk
{"delete":{"_id":"1"}}
{"delete":{"_id":"2"}}
{"delete":{"_id":"3"}}

四、Term查询(精确值)

10. 根据名称精确查询 findByName(@RequestParam String name)

GET /book/_search
{
  "query": {
    "term": {
      "name.keyword": "Elasticsearch实战"
    }
  }
}

11. 根据名称列表查询 findByNameIn(@RequestBody List<String> names)

GET /book/_search
{
  "query": {
    "terms": {
      "name.keyword": ["Book1", "Book2", "Book3"]
    }
  }
}

五、Match查询(全文)

12. 全文搜索 searchByName(@RequestParam String name)

GET /book/_search
{
  "query": {
    "match": {
      "name": "Elasticsearch"
    }
  }
}

13. 模糊查询 searchByNameLike(@RequestParam String name)

GET /book/_search
{
  "query": {
    "match": {
      "name": {
        "query": "Elastcsearch",
        "fuzziness": "AUTO",
        "prefix_length": 2
      }
    }
  }
}

14. 多字段全文搜索 multiMatchSearch(@RequestParam String keyword)

GET /book/_search
{
  "query": {
    "multi_match": {
      "query": "Elasticsearch 实战",
      "fields": ["name", "desc"]
    }
  }
}

六、布尔查询

15. 布尔must查询 boolSearch(@RequestParam String name, @RequestParam String auth)

GET /book/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {"name": "Elasticsearch"}},
        {"term": {"auth.keyword": "张三"}}
      ]
    }
  }
}

16. 布尔should查询 boolShouldSearch(@RequestParam String name, @RequestParam String auth)

GET /book/_search
{
  "query": {
    "bool": {
      "should": [
        {"match": {"name": "Elasticsearch"}},
        {"term": {"auth.keyword": "张三"}}
      ],
      "minimum_should_match": 1
    }
  }
}

七、范围查询

17. 数值范围查询 rangeSearch(@RequestParam Long min, @RequestParam Long max)

GET /book/_search
{
  "query": {
    "range": {
      "count": {
        "gte": 10,
        "lte": 100
      }
    }
  }
}

18. 日期范围查询 dateRangeSearch(@RequestParam String startDate, @RequestParam String endDate)

GET /book/_search
{
  "query": {
    "range": {
      "createtime": {
        "gte": "2024-01-01",
        "lte": "2024-12-31",
        "format": "yyyy-MM-dd"
      }
    }
  }
}

八、其他查询

19. 前缀查询 prefixSearch(@RequestParam String prefix)

GET /book/_search
{
  "query": {
    "prefix": {
      "name.keyword": "Es"
    }
  }
}

20. 通配符查询 wildcardSearch(@RequestParam String pattern)

GET /book/_search
{
  "query": {
    "wildcard": {
      "name.keyword": "El*ic*"
    }
  }
}

21. 模糊查询 fuzzySearch(@RequestParam String value, @RequestParam(defaultValue = "2") int prefixLength)

GET /book/_search
{
  "query": {
    "fuzzy": {
      "name": {
        "value": "elastcsearch",
        "fuzziness": 2,
        "prefix_length": 2
      }
    }
  }
}

22. 正则查询 regexpSearch(@RequestParam String regexp)

GET /book/_search
{
  "query": {
    "regexp": {
      "name.keyword": "Elasticsearch.*"
    }
  }
}

九、ID查询

23. 根据ID查询 findByIdQuery(@PathVariable String id)

GET /book/_doc/{id}

24. 根据ID列表查询 findByIds(@RequestBody List<String> ids)

GET /book/_search
{
  "query": {
    "ids": {
      "values": ["1", "2", "3"]
    }
  }
}

十、高亮查询

25. 高亮搜索 highlightSearch(@RequestParam String keyword)

GET /book/_search
{
  "query": {
    "match": {"name": "Elasticsearch"}
  },
  "highlight": {
    "pre_tags": ["<em>"],
    "post_tags": ["</em>"],
    "fields": {
      "name": {},
      "desc": {}
    }
  }
}

十一、聚合查询

26. 统计查询 statsSearch()

GET /book/_search
{
  "size": 0,
  "aggs": {
    "count_stats": {
      "stats": {"field": "count"}
    }
  }
}

27. 基数统计 cardinalitySearch(@RequestParam String field)

GET /book/_search
{
  "size": 0,
  "aggs": {
    "auth_count": {
      "cardinality": {"field": "auth.keyword"}
    }
  }
}

28. 范围统计 rangeStats(@RequestParam Long min, @RequestParam Long max)

GET /book/_search
{
  "query": {
    "range": {
      "count": {"gte": 10, "lte": 100}
    }
  },
  "size": 0,
  "aggs": {
    "count_stats": {
      "stats": {"field": "count"}
    }
  }
}

十二、删除查询

29. 根据条件删除 deleteByQuery(@RequestParam String field, @RequestParam String value)

POST /book/_delete_by_query
{
  "query": {
    "term": {"auth.keyword": "张三"}
  }
}

十三、Boosting查询

30. Boosting查询 boostingSearch(...)

GET /book/_search
{
  "query": {
    "boosting": {
      "positive": {"match": {"name": "Elasticsearch"}},
      "negative": {"term": {"auth.keyword": "李四"}},
      "negative_boost": 0.2
    }
  }
}

十四、Filter查询

31. Filter查询 filterSearch(...)

GET /book/_search
{
  "query": {
    "bool": {
      "must": [{"match": {"name": "Elasticsearch"}}],
      "filter": [
        {"range": {"count": {"gte": 10, "lte": 100}}}
      ]
    }
  }
}

十五、深分页 Scroll

32. 首次Scroll查询 scrollSearchFirst(@RequestParam(defaultValue = "10") int size)

POST /book/_search?scroll=1m
{
  "size": 10,
  "query": {"match_all": {}}
}
​
# 返回结果包含 _scroll_id

33. 后续Scroll查询 scrollSearchNext(@RequestParam String scrollId)

POST /_search/scroll
{
  "scroll": "1m",
  "scroll_id": "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=="
}

34. 清除Scroll scrollClear(@RequestParam String scrollId)

DELETE /_search/scroll
{
  "scroll_id": ["DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=="]
}

十六、地图查询

35. 地理距离查询 geoDistanceSearch(@RequestParam double lat, @RequestParam double lon, @RequestParam String distance)

添加字段

步骤 1 把 location 加入 mapping(假设原 mapping 里没有该字段)

PUT /book/_mapping
{
  "properties": {
    "location": {
      "type": "geo_point"
    }
  }
}

步骤 2 给已有文档写入坐标(示例更新 id=1 的文档)

POST /book/_update/1
{
  "doc": {
    "location": {
      "lat": 40.12,
      "lon": -71.34
    }
  }
}
GET /book/_search
{
  "query": {
    "geo_distance": {
      "distance": "10km",
      "location": {
        "lat": 40.12,
        "lon": -71.34
      }
    }
  }
}

36. 地理边界框查询 geoBoundingBoxSearch(...)

GET /book/_search
{
  "query": {
    "geo_bounding_box": {
      "location": {
        "top_left": {"lat": 40.73, "lon": -74.1},
        "bottom_right": {"lat": 40.01, "lon": -71.12}
      }
    }
  }
}

37. 地理多边形查询 geoPolygonSearch(@RequestBody List<double[]> points)

GET /book/_search
{
  "query": {
    "geo_polygon": {
      "location": {
        "points": [
          {"lat": 40, "lon": -70},
          {"lat": 42, "lon": -72},
          {"lat": 41, "lon": -74}
        ]
      }
    }
  }
}

使用说明:

  • {id}{field}{value} 替换为实际参数
  • 地理查询需要 location 字段类型为 geo_point
  • Scroll查询需要保存返回的 _scroll_id 用于后续请求