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 的区别
| 特性 | Query | Filter |
|---|---|---|
| 是否算分 | 是(影响排序) | 否(固定分为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用于后续请求