搜索引擎组件(Search)
:::success 版本提示
该功能自4.5.0版本开始可用 :::
概述
Rydeen Search Component 是一个现代化的搜索引擎组件,采用模块化架构设计,支持多种搜索引擎。该组件遵循 Spring Data 最新版本的最佳实践,为每种搜索引擎提供独立的、优化的接口实现。
主要特性
- 🏗️ 模块化架构:每种搜索引擎独立实现,互不干扰
- 🚀 现代化设计:基于最新版本的 Spring Data 技术栈
- 🔍 多种查询模式:支持 CriteriaQuery、StringQuery、NativeQuery
- 📝 注解驱动:支持
@Query注解,类似 JPA 的查询方式 - 🎯 类型安全:提供 Lambda 表达式支持,编译时类型检查
- ⚡ 性能优化:针对每种搜索引擎的特性进行优化
- 🔧 灵活配置:支持自定义查询条件对象和链式查询构建
Elasticsearch 模块
概述
Elasticsearch 模块是当前最成熟的实现,基于 Spring Data Elasticsearch 5.5.2,提供完整的搜索功能支持。
快速开始
依赖配置
<dependency>
<groupId>cn.com.rydeen.component</groupId>
<artifactId>rydeen-component-search</artifactId>
</dependency>
基础配置
# 基础配置
platform:
component:
search:
# 服务地址(支持单节点和多节点)
hosts: http://localhost:9200
# 多节点示例:http://es1:9200,http://es2:9200,http://es3:9200
# 认证信息(如果启用安全认证)
username: elastic
password: your_password
# 连接超时配置
connect-timeout: 5000 # 连接超时:5秒
socket-timeout: 60000 # 读取超时:60秒
# 连接池配置
pool:
max-total: 100 # 最大总连接数
max-per-route: 100 # 单节点最大连接数
connect-timeout: 5000 # 连接超时
socket-timeout: 60000 # 读取超时
connection-request-timeout: 5000 # 获取连接超时
keep-alive-time: 20000 # 连接保活时间
idle-connection-timeout: 30000 # 空闲连接超时
validate-after-inactivity: true # 连接有效性验证
enable-metrics: false # 连接池统计
# SSL安全配置
ssl:
enabled: false # 是否启用HTTPS
protocol: TLSv1.2 # SSL协议版本
keystore-path: /path/to/keystore.p12
keystore-password: keystore_password
keystore-type: PKCS12 # 证书类型:JKS, PKCS12, PEM
truststore-path: /path/to/truststore.p12
truststore-password: truststore_password
trust-all: false # 是否信任所有证书
# Elasticsearch特有配置
elasticsearch:
shards: 1 # 默认分片数
replicas: 1 # 默认副本数
refresh-interval: 1s # 刷新间隔
cluster:
health-check: true # 启用健康检查
health-check-timeout: 30000 # 健康检查超时
name: elasticsearch # 集群名称
minimum-master-nodes: 1 # 最小主节点数
# 生产环境配置示例
platform:
component:
search:
provider: ELASTICSEARCH
hosts: https://es-cluster.example.com:9200
username: ${ES_USERNAME:elastic}
password: ${ES_PASSWORD:}
connect-timeout: 3000
socket-timeout: 30000
pool:
max-total: 200
max-per-route: 100
connect-timeout: 3000
socket-timeout: 30000
connection-request-timeout: 3000
keep-alive-time: 60000
idle-connection-timeout: 60000
validate-after-inactivity: true
enable-metrics: true
ssl:
enabled: true
protocol: TLSv1.3
keystore-path: ${ES_KEYSTORE_PATH:/etc/elasticsearch/certs/client.p12}
keystore-password: ${ES_KEYSTORE_PASSWORD:}
keystore-type: PKCS12
truststore-path: ${ES_TRUSTSTORE_PATH:/etc/elasticsearch/certs/ca.p12}
truststore-password: ${ES_TRUSTSTORE_PASSWORD:}
trust-all: false
elasticsearch:
shards: 3
replicas: 2
refresh-interval: 30s
cluster:
health-check: true
health-check-timeout: 60000
name: production-cluster
minimum-master-nodes: 2
配置说明
配置优先级
- 环境变量 > 配置文件 > 默认值
- 敏感信息(密码、证书)建议使用环境变量
连接池调优建议
- 低并发应用:max-total: 50-100, max-per-route: 50-100
- 中等并发应用:max-total: 100-150, max-per-route: 100-150
- 高并发应用:max-total: 150-200, max-per-route: 100-150
超时时间配置
- 内网环境:connect-timeout: 3000-5000ms, socket-timeout: 30000-60000ms
- 公网环境:connect-timeout: 5000-8000ms, socket-timeout: 60000-120000ms
- 网络较差:connect-timeout: 8000-10000ms, socket-timeout: 120000ms以上
SSL安全配置
- 生产环境:必须启用SSL,使用TLSv1.2或TLSv1.3
- 开发环境:可以禁用SSL或使用自签名证书
- 证书管理:建议使用PKCS12格式,便于部署和管理
基本使用
@Service
@RequiredArgsConstructor
public class OrderService {
private final ElasticsearchService elasticsearchService;
public PageResult<Order> searchOrders(String keyword, int page, int size) {
SearchQueryWrapper<Order> wrapper = SearchQueryWrapper
.create(Order.class, "orders")
.page(page, size);
if (StringUtils.hasText(keyword)) {
wrapper.like(Order::getOrderName, keyword);
}
return elasticsearchService.search(wrapper);
}
}
核心组件
SearchQueryWrapper - 通用查询包装器
SearchQueryWrapper 是主要的查询构建器,支持链式调用和多种查询操作。
基础查询示例
// 简单条件查询
SearchQueryWrapper<Order> wrapper = SearchQueryWrapper
.create(Order.class, "orders")
.eq(Order::getStatus, "ACTIVE")
.ge(Order::getAmount, 100.0)
.orderByDesc(Order::getCreateTime)
.page(0, 10);
PageResult<Order> result = elasticsearchService.search(wrapper);
复杂条件查询
// 多条件组合查询
SearchQueryWrapper<Order> wrapper = SearchQueryWrapper
.create(Order.class, "orders")
.eq(Order::getStatus, "ACTIVE")
.between(Order::getCreateTime, startDate, endDate)
.in(Order::getCategory, Arrays.asList("ELECTRONICS", "CLOTHING"))
.like(Order::getDescription, "重要")
.orderByDesc(Order::getCreateTime)
.page(0, 20);
PageResult<Order> result = elasticsearchService.search(wrapper);
嵌套字段查询
// 查询嵌套数组中的字段
SearchQueryWrapper<Order> wrapper = SearchQueryWrapper
.create(Order.class, "orders")
.nestedLike("orderItems", "productName", "iPhone")
.eq(Order::getStatus, "ACTIVE")
.page(0, 10);
PageResult<Order> result = elasticsearchService.search(wrapper);
StringQueryWrapper - JSON 字符串查询
StringQueryWrapper 用于直接使用 Elasticsearch JSON 查询字符串,适合复杂查询场景。
基础使用
StringQueryWrapper<Order> wrapper = StringQueryWrapper
.create(Order.class, "orders")
.page(page, size)
.stringQuery("""
{
"nested": {
"path": "orderItems",
"query": {
"match": {
"orderItems.productName": "iPhone"
}
}
}
}
""");
PageResult<Order> result = elasticsearchService.search(wrapper);
复杂聚合查询
StringQueryWrapper<Order> wrapper = StringQueryWrapper
.create(Order.class, "orders")
.page(0, 0) // 聚合查询不需要分页
.stringQuery("""
{
"query": {
"bool": {
"must": [
{"term": {"status": "ACTIVE"}},
{"range": {"amount": {"gte": 100}}}
]
}
},
"aggs": {
"category_stats": {
"terms": {"field": "category"},
"aggs": {
"total_amount": {"sum": {"field": "amount"}}
}
}
}
}
""");
PageResult<Order> result = elasticsearchService.search(wrapper);
NativeQueryWrapper - 原生查询包装器
NativeQueryWrapper 使用 Spring Data Elasticsearch 的 NativeQuery,提供类型安全的查询构建。
基础使用
NativeQueryWrapper<Order> wrapper = NativeQueryWrapper
.create(Order.class, "orders")
.page(page, size)
.nativeQuery(q -> q
.bool(b -> b
.must(m -> m.term(t -> t.field("status").value("ACTIVE")))
.must(m -> m.range(r -> r.field("amount").gte(gte -> gte.value(100.0))))
);
PageResult<Order> result = elasticsearchService.search(wrapper);
嵌套查询
NativeQueryWrapper<Order> wrapper = NativeQueryWrapper
.create(Order.class, "orders")
.page(page, size)
.nativeQuery(q -> q
.nested(n -> n
.path("orderItems")
.query(nq -> nq
.match(m -> m
.field("orderItems.productName")
.query("iPhone")
)
)
)
);
PageResult<Order> result = elasticsearchService.search(wrapper);
查询操作符
基础比较操作符
| 操作符 | 方法 | 描述 | 示例 |
|---|---|---|---|
| 等于 | eq(column, value) | 精确匹配 | .eq(Order::getStatus, "ACTIVE") |
| 不等于 | ne(column, value) | 不匹配 | .ne(Order::getStatus, "DELETED") |
| 大于 | gt(column, value) | 大于指定值 | .gt(Order::getAmount, 100.0) |
| 大于等于 | ge(column, value) | 大于等于指定值 | .ge(Order::getAmount, 100.0) |
| 小于 | lt(column, value) | 小于指定值 | .lt(Order::getAmount, 1000.0) |
| 小于等于 | le(column, value) | 小于等于指定值 | .le(Order::getAmount, 1000.0) |
| 区间 | between(column, from, to) | 在指定范围内 | .between(Order::getCreateTime, start, end) |
集合操作符
| 操作符 | 方法 | 描述 | 示例 |
|---|---|---|---|
| 包含 | in(column, values) | 在指定集合中 | .in(Order::getCategory, Arrays.asList("A", "B")) |
| 不包含 | notIn(column, values) | 不在指定集合中 | .notIn(Order::getStatus, Arrays.asList("DELETED")) |
文本操作符
| 操作符 | 方法 | 描述 | 示例 |
|---|---|---|---|
| 模糊匹配 | like(column, value) | 包含指定字符串 | .like(Order::getDescription, "重要") |
| 存在 | exists(column) | 字段存在 | .exists(Order::getTags) |
| 不存在 | notExists(column) | 字段不存在 | .notExists(Order::getDeletedAt) |
高级功能
高亮查询
SearchQueryWrapper<Order> wrapper = SearchQueryWrapper
.create(Order.class, "orders")
.like(Order::getDescription, "重要")
.highlight(Order::getDescription, Order::getOrderName) // 设置高亮字段
.page(0, 10);
PageResult<Order> result = elasticsearchService.search(wrapper);
// 获取高亮结果
Map<String, Map<String, List<String>>> highlights = result.getHighlights();
搜索建议
SearchQueryWrapper<Order> wrapper = SearchQueryWrapper
.create(Order.class, "orders")
.suggest(Order::getOrderName, "iPh", 5) // 字段名,前缀,建议数量
.page(0, 10);
PageResult<Order> result = elasticsearchService.search(wrapper);
// 获取建议结果
Map<String, List<String>> suggestions = result.getSuggestions();
聚合查询
// 使用 dslSearch 进行聚合查询(推荐)
StringQueryWrapper<Order> wrapper = StringQueryWrapper
.create(Order.class, "orders")
.page(0, 0) // 聚合查询不需要分页
.stringQuery("""
{
"query": {
"term": {"status": "ACTIVE"}
},
"aggs": {
"category_stats": {
"terms": {"field": "category"},
"aggs": {
"total_amount": {"sum": {"field": "amount"}},
"avg_amount": {"avg": {"field": "amount"}}
}
},
"amount_stats": {
"stats": {"field": "amount"}
},
"status_distribution": {
"terms": {"field": "status"}
}
}
}
""");
PageResult<Order> result = elasticsearchService.dslSearch(wrapper);
// 获取聚合结果
Map<String, Object> aggregations = result.getAggregations();
复杂聚合查询示例
// 多层级聚合查询
StringQueryWrapper<Order> wrapper = StringQueryWrapper
.create(Order.class, "orders")
.page(0, 0)
.stringQuery("""
{
"query": {
"bool": {
"must": [
{"range": {"createTime": {"gte": "2024-01-01"}}},
{"range": {"amount": {"gte": 100}}}
]
}
},
"aggs": {
"monthly_stats": {
"date_histogram": {
"field": "createTime",
"calendar_interval": "month",
"format": "yyyy-MM"
},
"aggs": {
"total_amount": {"sum": {"field": "amount"}},
"order_count": {"value_count": {"field": "id"}},
"category_breakdown": {
"terms": {"field": "category"},
"aggs": {
"category_amount": {"sum": {"field": "amount"}}
}
}
}
},
"top_products": {
"nested": {"path": "orderItems"},
"aggs": {
"product_stats": {
"terms": {"field": "orderItems.productName", "size": 10},
"aggs": {
"total_quantity": {"sum": {"field": "orderItems.quantity"}},
"total_revenue": {"sum": {"field": "orderItems.subtotal"}}
}
}
}
}
}
}
""");
PageResult<Order> result = elasticsearchService.dslSearch(wrapper);
使用 NativeQueryWrapper 进行聚合查询
// 使用 NativeQueryWrapper 进行类型安全的聚合查询
NativeQueryWrapper<Order> wrapper = NativeQueryWrapper
.create(Order.class, "orders")
.page(0, 0)
.nativeQuery(q -> q
.query(qb -> qb
.term(t -> t.field("status").value("ACTIVE"))
)
.aggregations("category_stats", a -> a
.terms(t -> t.field("category"))
.aggregations("total_amount", sa -> sa
.sum(s -> s.field("amount"))
)
)
.aggregations("amount_stats", a -> a
.stats(s -> s.field("amount"))
)
);
PageResult<Order> result = elasticsearchService.nativeSearch(wrapper);
注解驱动查询
根据Spring Data Elasticsearch Repository官方文档,Spring Data Elasticsearch提供了强大的Repository抽象,支持多种查询方式。
1. Repository接口定义
基础Repository接口
// 基础Repository接口
public interface OrderRepository extends Repository<Order, String> {
// 基础方法
}
// CRUD Repository接口
public interface OrderRepository extends CrudRepository<Order, String> {
// 继承所有CRUD方法
}
// Elasticsearch专用Repository接口
public interface OrderRepository extends ElasticsearchRepository<Order, String> {
// 继承所有CRUD方法 + Elasticsearch特有方法
}
// 分页和排序Repository接口
public interface OrderRepository extends PagingAndSortingRepository<Order, String> {
// 继承分页和排序方法
}
// List版本Repository接口(推荐)
public interface OrderRepository extends ListCrudRepository<Order, String> {
// 返回List而不是Iterable
}
Repository配置
@Configuration
@EnableElasticsearchRepositories(
basePackages = "cn.com.rydeen.repository",
basePackageClasses = OrderRepository.class,
namedQueriesLocation = "classpath:elasticsearch-queries.properties"
)
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration {
@Override
public RestHighLevelClient elasticsearchClient() {
// 客户端配置
return new RestHighLevelClient(/* 配置 */);
}
}
2. @Query 注解使用
@Query 注解允许直接编写 Elasticsearch JSON 查询语句,支持多种查询场景。
基础查询示例
@Repository
public interface OrderRepository extends ElasticsearchRepository<Order, String> {
// 简单查询
@Query("{\"term\": {\"status\": \"?0\"}}")
List<Order> findByStatus(String status);
// 嵌套查询
@Query("""
{
"nested": {
"path": "orderItems",
"query": {
"match": {
"orderItems.productName": "?0"
}
}
}
}
""")
List<Order> findByProductName(String productName);
// 复杂查询
@Query("""
{
"bool": {
"must": [
{"term": {"status": "?0"}},
{"range": {"amount": {"gte": "?1"}}}
]
}
}
""")
List<Order> findByStatusAndMinAmount(String status, Double minAmount);
// 使用 SpEL 表达式
@Query("""
{
"bool": {
"must": [
{"term": {"status": "#{#status}"}},
{"range": {"amount": {"gte": "#{#minAmount}"}}}
]
}
}
""")
List<Order> findByStatusAndMinAmountSpEL(@Param("status") String status,
@Param("minAmount") Double minAmount);
// 聚合查询
@Query("""
{
"query": {"term": {"status": "?0"}},
"aggs": {
"category_stats": {
"terms": {"field": "category"},
"aggs": {
"total_amount": {"sum": {"field": "amount"}}
}
}
}
}
""")
SearchHits<Order> findByStatusWithAggregations(String status);
// 高亮查询
@Query("""
{
"query": {"match": {"description": "?0"}},
"highlight": {
"fields": {
"description": {},
"orderName": {}
}
}
}
""")
SearchHits<Order> findByDescriptionWithHighlight(String description);
}
3. 查询方法派生
根据Spring Data Elasticsearch查询方法官方文档,支持通过方法名自动派生查询。
基础查询方法
@Repository
public interface OrderRepository extends ElasticsearchRepository<Order, String> {
// 精确匹配
List<Order> findByStatus(String status);
List<Order> findByStatusAndAmount(String status, Double amount);
List<Order> findByStatusOrAmount(String status, Double amount);
// 范围查询
List<Order> findByAmountBetween(Double minAmount, Double maxAmount);
List<Order> findByAmountGreaterThan(Double amount);
List<Order> findByAmountLessThan(Double amount);
List<Order> findByAmountGreaterThanEqual(Double amount);
List<Order> findByAmountLessThanEqual(Double amount);
// 存在性查询
List<Order> findByDescriptionExists();
List<Order> findByDescriptionNotExists();
// 空值查询
List<Order> findByDescriptionIsNull();
List<Order> findByDescriptionIsNotNull();
// 集合查询
List<Order> findByCategoryIn(Collection<String> categories);
List<Order> findByCategoryNotIn(Collection<String> categories);
// 文本查询
List<Order> findByDescriptionLike(String description);
List<Order> findByDescriptionNotLike(String description);
List<Order> findByDescriptionStartingWith(String prefix);
List<Order> findByDescriptionEndingWith(String suffix);
List<Order> findByDescriptionContaining(String text);
// 日期查询
List<Order> findByCreateTimeAfter(LocalDateTime time);
List<Order> findByCreateTimeBefore(LocalDateTime time);
List<Order> findByCreateTimeBetween(LocalDateTime start, LocalDateTime end);
}
嵌套字段查询
@Repository
public interface OrderRepository extends ElasticsearchRepository<Order, String> {
// 嵌套字段查询
List<Order> findByOrderItemsProductName(String productName);
List<Order> findByOrderItemsPriceBetween(Double minPrice, Double maxPrice);
List<Order> findByOrderItemsProductIdIn(Collection<String> productIds);
// 嵌套字段存在性查询
List<Order> findByOrderItemsExists();
List<Order> findByOrderItemsNotExists();
}
地理位置查询
@Repository
public interface OrderRepository extends ElasticsearchRepository<Order, String> {
// 地理位置查询
List<Order> findByDeliveryLocationNear(Point point, Distance distance);
List<Order> findByDeliveryLocationWithin(Point point, Distance distance);
List<Order> findByDeliveryLocationWithin(GeoJsonPolygon polygon);
}
4. 查询关键字参考
根据Spring Data查询关键字官方文档,支持以下查询关键字:
逻辑操作符
| 关键字 | 示例 | Elasticsearch查询 |
|---|---|---|
And | findByStatusAndAmount | bool.must |
Or | findByStatusOrAmount | bool.should |
Not | findByStatusNot | bool.must_not |
比较操作符
| 关键字 | 示例 | Elasticsearch查询 |
|---|---|---|
Is | findByStatusIs | term |
Equals | findByStatusEquals | term |
Between | findByAmountBetween | range |
LessThan | findByAmountLessThan | range.lt |
LessThanEqual | findByAmountLessThanEqual | range.lte |
GreaterThan | findByAmountGreaterThan | range.gt |
GreaterThanEqual | findByAmountGreaterThanEqual | range.gte |
字符串操作符
| 关键字 | 示例 | Elasticsearch查询 |
|---|---|---|
Like | findByDescriptionLike | wildcard |
NotLike | findByDescriptionNotLike | bool.must_not.wildcard |
StartingWith | findByDescriptionStartingWith | prefix |
EndingWith | findByDescriptionEndingWith | wildcard |
Containing | findByDescriptionContaining | match |
集合操作符
| 关键字 | 示例 | Elasticsearch查询 |
|---|---|---|
In | findByCategoryIn | terms |
NotIn | findByCategoryNotIn | bool.must_not.terms |
Exists | findByDescriptionExists | exists |
NotExists | findByDescriptionNotExists | bool.must_not.exists |
5. 分页和排序
@Repository
public interface OrderRepository extends ElasticsearchRepository<Order, String> {
// 分页查询
Page<Order> findByStatus(String status, Pageable pageable);
// 排序查询
List<Order> findByStatusOrderByCreateTimeDesc(String status);
List<Order> findByStatusOrderByAmountAscCreateTimeDesc(String status);
// 分页+排序
Page<Order> findByStatus(String status, Pageable pageable);
// 使用示例
Pageable pageable = PageRequest.of(0, 20, Sort.by("createTime").descending());
Page<Order> page = repository.findByStatus("ACTIVE", pageable);
}
6. 投影查询
根据Spring Data投影官方文档,支持投影查询来优化数据传输。
接口投影
// 定义投影接口
public interface OrderSummary {
String getOrderId();
String getStatus();
Double getAmount();
LocalDateTime getCreateTime();
// 计算属性
default String getDisplayName() {
return getOrderId() + " - " + getStatus();
}
}
// 使用投影
@Repository
public interface OrderRepository extends ElasticsearchRepository<Order, String> {
List<OrderSummary> findByStatus(String status);
Page<OrderSummary> findByStatus(String status, Pageable pageable);
}
类投影
// 定义投影类
public class OrderProjection {
private final String orderId;
private final String status;
private final Double amount;
public OrderProjection(String orderId, String status, Double amount) {
this.orderId = orderId;
this.status = status;
this.amount = amount;
}
// getter方法
public String getOrderId() { return orderId; }
public String getStatus() { return status; }
public Double getAmount() { return amount; }
}
// 使用投影
@Repository
public interface OrderRepository extends ElasticsearchRepository<Order, String> {
List<OrderProjection> findByStatus(String status);
}
动态投影
@Repository
public interface OrderRepository extends ElasticsearchRepository<Order, String> {
<T> List<T> findByStatus(String status, Class<T> type);
}
// 使用示例
List<OrderSummary> summaries = repository.findByStatus("ACTIVE", OrderSummary.class);
List<OrderProjection> projections = repository.findByStatus("ACTIVE", OrderProjection.class);
7. 自定义Repository实现
根据Spring Data自定义实现官方文档,支持自定义Repository实现。
自定义Repository接口
public interface OrderRepositoryCustom {
List<Order> findComplexOrders(OrderSearchCriteria criteria);
Map<String, Object> getOrderStatistics(String status);
}
自定义Repository实现
public class OrderRepositoryImpl implements OrderRepositoryCustom {
@Autowired
private ElasticsearchTemplate template;
@Override
public List<Order> findComplexOrders(OrderSearchCriteria criteria) {
// 自定义查询逻辑
NativeQuery query = NativeQuery.builder()
.withQuery(q -> q
.bool(b -> b
.must(m -> m.term(t -> t.field("status").value(criteria.getStatus())))
.must(m -> m.range(r -> r.field("amount").gte(gte -> gte.value(criteria.getMinAmount()))))
)
.build();
return template.search(query, Order.class).getSearchHits().stream()
.map(SearchHit::getContent)
.collect(Collectors.toList());
}
@Override
public Map<String, Object> getOrderStatistics(String status) {
// 自定义聚合查询
NativeQuery query = NativeQuery.builder()
.withQuery(q -> q.term(t -> t.field("status").value(status)))
.withAggregations("category_stats", a -> a.terms(t -> t.field("category")))
.withAggregations("amount_stats", a -> a.stats(s -> s.field("amount")))
.build();
SearchHits<Order> searchHits = template.search(query, Order.class);
// 处理聚合结果...
return new HashMap<>();
}
}
组合Repository接口
@Repository
public interface OrderRepository extends
ElasticsearchRepository<Order, String>,
OrderRepositoryCustom {
// 继承所有方法
}
8. 领域事件发布
根据Spring Data领域事件官方文档,支持发布领域事件。
实体事件
@Document(indexName = "orders")
public class Order extends AbstractAggregateRoot<Order> {
@Id
private String id;
private String orderId;
private String status;
public void activate() {
this.status = "ACTIVE";
// 发布领域事件
registerEvent(new OrderActivatedEvent(this.orderId));
}
public void cancel() {
this.status = "CANCELLED";
// 发布领域事件
registerEvent(new OrderCancelledEvent(this.orderId));
}
}
事件监听器
@Component
public class OrderEventListener {
@EventListener
public void handleOrderActivated(OrderActivatedEvent event) {
log.info("订单 {} 已激活", event.getOrderId());
// 处理订单激活逻辑
}
@EventListener
public void handleOrderCancelled(OrderCancelledEvent event) {
log.info("订单 {} 已取消", event.getOrderId());
// 处理订单取消逻辑
}
}
9. 空值处理
根据Spring Data空值处理官方文档,支持多种空值处理策略。
空值处理配置
@Repository
public interface OrderRepository extends ElasticsearchRepository<Order, String> {
// 默认行为:忽略空参数
List<Order> findByStatusAndAmount(String status, Double amount);
// 显式处理空值
@Query("""
{
"bool": {
"must": [
{"term": {"status": "?0"}},
{"range": {"amount": {"gte": "?1"}}}
]
}
}
""")
List<Order> findByStatusAndAmountExplicit(String status, Double amount);
}
空值安全查询
@Repository
public interface OrderRepository extends ElasticsearchRepository<Order, String> {
// 使用Optional处理可能为空的值
Optional<Order> findByOrderId(String orderId);
// 使用@Nullable注解
List<Order> findByStatusAndAmount(@Nullable String status, @Nullable Double amount);
}
10. 查询返回类型
根据Spring Data查询返回类型官方文档,支持多种返回类型。
支持的返回类型
@Repository
public interface OrderRepository extends ElasticsearchRepository<Order, String> {
// 单个实体
Order findByOrderId(String orderId);
Optional<Order> findByOrderId(String orderId);
// 集合
List<Order> findByStatus(String status);
Set<Order> findByStatus(String status);
Iterable<Order> findByStatus(String status);
// 分页
Page<Order> findByStatus(String status, Pageable pageable);
Slice<Order> findByStatus(String status, Pageable pageable);
// 流式查询
Stream<Order> findByStatus(String status);
// 计数
long countByStatus(String status);
boolean existsByOrderId(String orderId);
// 删除
long deleteByStatus(String status);
void removeByStatus(String status);
}
异步查询
@Repository
public interface OrderRepository extends ElasticsearchRepository<Order, String> {
// 异步查询
@Async
CompletableFuture<Order> findByOrderId(String orderId);
@Async
CompletableFuture<List<Order>> findByStatus(String status);
@Async
CompletableFuture<Page<Order>> findByStatus(String status, Pageable pageable);
}
11. 查询方法最佳实践
@Repository
public interface OrderRepository extends ElasticsearchRepository<Order, String> {
// 1. 使用有意义的方法名
List<Order> findByStatusAndCreateTimeBetweenOrderByAmountDesc(
String status, LocalDateTime start, LocalDateTime end);
// 2. 使用@Query注解处理复杂查询
@Query("""
{
"bool": {
"must": [
{"term": {"status": "?0"}},
{"range": {"createTime": {"gte": "?1", "lte": "?2"}}}
]
}
}
""")
List<Order> findActiveOrdersInDateRange(LocalDateTime start, LocalDateTime end);
// 3. 使用投影优化数据传输
List<OrderSummary> findOrderSummariesByStatus(String status);
// 4. 使用分页避免大量数据查询
Page<Order> findByStatus(String status, Pageable pageable);
// 5. 使用流式查询处理大量数据
Stream<Order> streamByStatus(String status);
}
实体映射与配置
1. 基础映射注解
根据Spring Data Elasticsearch官方文档,Spring Data Elasticsearch提供了丰富的映射注解来配置实体类:
@Document(indexName = "orders", createIndex = true)
public class Order {
@Id
private String id;
@Field(type = FieldType.Keyword)
private String orderId;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String description;
@Field(type = FieldType.Date, format = {DateFormat.basic_date, DateFormat.basic_time})
private LocalDateTime createTime;
@Field(type = FieldType.Nested)
private List<OrderItem> orderItems;
@Field(type = FieldType.GeoPoint)
private Point deliveryLocation;
@Field(type = FieldType.Object)
private Map<String, Object> metadata;
}
常用字段类型
| 类型 | 说明 | 适用场景 |
|---|---|---|
FieldType.Text | 全文搜索字段 | 商品描述、评论内容 |
FieldType.Keyword | 精确匹配字段 | 状态、分类、ID |
FieldType.Date | 日期时间字段 | 创建时间、更新时间 |
FieldType.Nested | 嵌套对象字段 | 订单项、标签列表 |
FieldType.GeoPoint | 地理位置字段 | 配送地址、门店位置 |
FieldType.Object | 动态对象字段 | 扩展属性、元数据 |
2. 日期格式映射
@Field(type = FieldType.Date,
format = {DateFormat.basic_date, DateFormat.basic_time},
pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
// 或者使用自定义格式
@Field(type = FieldType.Date,
format = {},
pattern = "dd.MM.yyyy")
private LocalDate orderDate;
3. 地理位置类型
// 使用 Point 类型
@Field(type = FieldType.GeoPoint)
private Point deliveryLocation;
// 使用 GeoJson 类型
@Field(type = FieldType.GeoShape)
private GeoJsonPoint location;
// 在代码中使用
Point point = new Point(-118.3026284, 34.118347);
GeoJsonPoint geoPoint = new GeoJsonPoint(-118.3026284, 34.118347);
4. 集合和映射类型
// 集合类型
@Field(type = FieldType.Nested)
private List<OrderItem> orderItems;
@Field(type = FieldType.Keyword)
private Set<String> tags;
// 映射类型
@Field(type = FieldType.Object)
private Map<String, Address> knownLocations;
@Field(type = FieldType.Object)
private Map<String, Object> metadata;
实体生命周期管理
1. 实体回调
根据Spring Data Elasticsearch实体回调文档,Spring Data Elasticsearch支持多种实体生命周期回调:
@Document(indexName = "orders")
public class Order {
@Id
private String id;
private String orderId;
private LocalDateTime createTime;
private LocalDateTime updateTime;
// 保存前回调
@PrePersist
public void prePersist() {
if (createTime == null) {
createTime = LocalDateTime.now();
}
updateTime = LocalDateTime.now();
}
// 更新前回调
@PreUpdate
public void preUpdate() {
updateTime = LocalDateTime.now();
}
// 保存后回调
@PostPersist
public void postPersist() {
log.info("订单 {} 已保存到ES", orderId);
}
// 加载后回调
@PostLoad
public void postLoad() {
log.debug("订单 {} 已从ES加载", orderId);
}
}
2. 自定义回调实现
@Component
public class OrderAuditCallback implements BeforeConvertCallback<Order> {
@Override
public Order onBeforeConvert(Order order, IndexCoordinates indexCoordinates) {
if (order.getCreateTime() == null) {
order.setCreateTime(LocalDateTime.now());
}
order.setUpdateTime(LocalDateTime.now());
return order;
}
}
@Component
public class OrderAfterLoadCallback implements AfterLoadCallback<Order> {
@Override
public Order onAfterLoad(Order order, IndexCoordinates indexCoordinates) {
// 处理加载后的逻辑,如计算字段、格式化等
if (order.getOrderItems() != null) {
order.setTotalItems(order.getOrderItems().size());
}
return order;
}
}
审计功能
1. 启用审计
根据Spring Data Elasticsearch审计文档,可以通过配置启用审计功能:
@Configuration
@EnableElasticsearchAuditing
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration {
@Override
public RestHighLevelClient elasticsearchClient() {
// 客户端配置
return new RestHighLevelClient(/* 配置 */);
}
@Bean
public AuditorAware<String> auditorProvider() {
return () -> Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.map(Authentication::getName);
}
}
2. 审计注解使用
@Document(indexName = "orders")
@EntityListeners(AuditingEntityListener.class)
public class Order {
@Id
private String id;
@CreatedDate
@Field(type = FieldType.Date)
private LocalDateTime createTime;
@LastModifiedDate
@Field(type = FieldType.Date)
private LocalDateTime updateTime;
@CreatedBy
@Field(type = FieldType.Keyword)
private String createdBy;
@LastModifiedBy
@Field(type = FieldType.Keyword)
private String lastModifiedBy;
@Version
private Long version;
}
高级映射特性
1. Join类型支持
根据Spring Data Elasticsearch Join类型文档,支持父子文档关系:
@Document(indexName = "orders")
public class Order {
@Id
private String id;
@Field(type = FieldType.Keyword)
private String orderId;
@JoinTypeRelations(
relations = {
@JoinTypeRelation(parent = "order", children = {"orderItem", "orderLog"})
}
)
private JoinField<String> relation;
}
@Document(indexName = "orders")
public class OrderItem {
@Id
private String id;
@Field(type = FieldType.Keyword)
private String productId;
@JoinTypeRelations(
relations = {
@JoinTypeRelation(parent = "order", children = {"orderItem", "orderLog"})
}
)
private JoinField<String> relation;
}
2. 路由值配置
根据Spring Data Elasticsearch路由文档,支持自定义路由策略:
@Document(indexName = "orders")
public class Order {
@Id
private String id;
@Field(type = FieldType.Keyword)
private String orderId;
@Routing
private String tenantId; // 使用租户ID作为路由值
@Field(type = FieldType.Keyword)
private String status;
}
// 在查询时指定路由
SearchQueryWrapper<Order> wrapper = SearchQueryWrapper
.create(Order.class, "orders")
.routing("tenant_001") // 指定路由值
.eq(Order::getStatus, "ACTIVE");
3. 类型别名
@Document(indexName = "orders")
@TypeAlias("order") // 自定义类型别名
public class Order {
@Id
private String id;
@Field(type = FieldType.Keyword)
private String orderId;
}
// 配置类型别名
@Configuration
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration {
@Override
protected Set<Class<?>> getInitialEntitySet() {
return Set.of(Order.class);
}
}
自定义转换器
1. 类型转换器
@Configuration
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration {
@Bean
@Override
public ElasticsearchCustomConversions elasticsearchCustomConversions() {
return new ElasticsearchCustomConversions(
Arrays.asList(new AddressToMap(), new MapToAddress())
);
}
@WritingConverter
static class AddressToMap implements Converter<Address, Map<String, Object>> {
@Override
public Map<String, Object> convert(Address source) {
Map<String, Object> target = new LinkedHashMap<>();
target.put("city", source.getCity());
target.put("street", source.getStreet());
target.put("location", Arrays.asList(source.getLatitude(), source.getLongitude()));
return target;
}
}
@ReadingConverter
static class MapToAddress implements Converter<Map<String, Object>, Address> {
@Override
public Address convert(Map<String, Object> source) {
Address address = new Address();
address.setCity((String) source.get("city"));
address.setStreet((String) source.get("street"));
@SuppressWarnings("unchecked")
List<Double> coords = (List<Double>) source.get("location");
if (coords != null && coords.size() == 2) {
address.setLatitude(coords.get(0));
address.setLongitude(coords.get(1));
}
return address;
}
}
}
2. 字段值转换器
@Document(indexName = "orders")
public class Order {
@Id
private String id;
@Field(type = FieldType.Keyword)
private String orderId;
@ValueConverter(OrderStatusConverter.class)
private OrderStatus status;
}
// 自定义字段值转换器
public class OrderStatusConverter implements ValueConverter<OrderStatus, String> {
@Override
public String write(OrderStatus value) {
return value != null ? value.getCode() : null;
}
@Override
public OrderStatus read(String value) {
return OrderStatus.fromCode(value);
}
}
索引管理
1. 索引创建与配置
@Service
public class IndexManagementService {
@Autowired
private ElasticsearchTemplate template;
public void createOrderIndex() {
IndexOperations indexOps = template.indexOps(Order.class);
if (!indexOps.exists()) {
// 创建索引
indexOps.create();
// 创建映射
Document document = Document.create();
document.put("mappings", createOrderMappings());
indexOps.putMapping(document);
}
}
private Map<String, Object> createOrderMappings() {
Map<String, Object> mappings = new HashMap<>();
// 详细的映射配置...
return mappings;
}
}
2. 索引别名管理
@Service
public class IndexAliasService {
@Autowired
private ElasticsearchTemplate template;
public void createAlias(String indexName, String aliasName) {
IndexOperations indexOps = template.indexOps(IndexCoordinates.of(indexName));
AliasQuery aliasQuery = new AliasQuery();
aliasQuery.setAliasName(aliasName);
indexOps.addAlias(aliasQuery);
}
public void switchAlias(String oldIndex, String newIndex, String aliasName) {
// 原子性切换别名
AliasActions aliasActions = new AliasActions();
aliasActions.add(new AliasAction.Add(IndexCoordinates.of(newIndex), aliasName));
aliasActions.add(new AliasAction.Remove(IndexCoordinates.of(oldIndex), aliasName));
template.execute(aliasActions);
}
}
API 接口概览
| 分类 | 方法名 | 描述 | 返回类型 | 参数 |
|---|---|---|---|---|
| 索引管理 | createIndex | 创建索引(Elasticsearch)或集合(SolrCloud) | boolean | (String indexName, String mappingJson) |
deleteIndex | 删除索引(Elasticsearch)或集合(SolrCloud) | boolean | (String indexName) | |
indexExists | 判断索引/集合是否存在 | boolean | (String indexName) | |
| 文档操作 | save | 保存单条文档 | <T> T | (String indexName, T document) |
deleteById | 根据ID删除单条文档 | boolean | (String indexName, String id) | |
findByDocId | 根据ID查询单条文档 | <T> Optional<T> | (String indexName, String docId, Class<T> clazz) | |
findOne | 根据查询条件查找单个文档 | <T> T | (SearchQueryWrapper<T> wrapper) | |
| 批量操作 | saveBatch | 批量保存文档 | <T> List<T> | (String indexName, List<T> documents) |
deleteBatchByIds | 批量根据ID删除文档 | boolean | (String indexName, List<String> ids) | |
| 基础查询 | count | 查询文档总数 | long | (SearchQueryWrapper<T> wrapper) |
deleteByCondition | 根据查询条件删除文档 | long | (SearchQueryWrapper<T> wrapper) | |
| 高级查询 | search | 高级查询(支持常规查询、布尔查询、嵌套查询、脚本查询等) | <T> PageResult<T> | (SearchQueryWrapper<T> wrapper) |
| 原生查询 | dslSearch | 原生查询(使用搜索引擎原生DSL语法) | <T> PageResult<T> | (StringQueryWrapper<T> wrapper) |
nativeSearch | 原生查询(使用搜索引擎原生API语法) | <T> PageResult<T> | (NativeQueryWrapper<T> wrapper) |
API 接口列表
索引管理接口
创建索引
| 属性 | 值 |
|---|---|
| 方法名 | createIndex |
| 描述 | 创建索引(Elasticsearch)或集合(SolrCloud) |
| 返回类型 | boolean |
参数表
| 参数名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| indexName | String | 是 | 索引/集合名称 |
| mappingJson | String | 否 | mapping或schema定义(ES为JSON,Solr可忽略) |
返回值
true: 创建成功false: 已存在或不支持
使用示例
boolean created = elasticsearchService.createIndex("users", mappingJson);
删除索引
| 属性 | 值 |
|---|---|
| 方法名 | deleteIndex |
| 描述 | 删除索引(Elasticsearch)或集合(SolrCloud) |
| 返回类型 | boolean |
参数表
| 参数名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| indexName | String | 是 | 索引/集合名称 |
返回值
true: 删除成功false: 不存在或不支持
使用示例
boolean deleted = elasticsearchService.deleteIndex("users");
检查索引是否存在
| 属性 | 值 |
|---|---|
| 方法名 | indexExists |
| 描述 | 判断索引/集合是否存在 |
| 返回类型 | boolean |
参数表
| 参数名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| indexName | String | 是 | 索引/集合名称 |
返回值
true: 存在false: 不存在
使用示例
boolean exists = elasticsearchService.indexExists("users");
文档操作接口
保存单条文档
| 属性 | 值 |
|---|---|
| 方法名 | save |
| 描述 | 将文档保存到指定的索引或集合中,如果文档已存在则更新 |
| 返回类型 | <T> T |
参数表
| 参数名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| indexName | String | 是 | 索引/集合名称 |
| document | T | 是 | 文档对象 |
返回值
- 保存后的文档对象(带ID)
使用示例
User user = new User("张三", 25);
User saved = elasticsearchService.save("users", user);
根据ID删除单条文档
| 属性 | 值 |
|---|---|
| 方法名 | deleteById |
| 描述 | 根据文档ID删除指定的文档 |
| 返回类型 | boolean |
参数表
| 参数名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| indexName | String | 是 | 索引/集合名称 |
| id | String | 是 | 文档ID |
返回值
true: 删除成功false: 删除失败
使用示例
boolean deleted = elasticsearchService.deleteById("users", "user_001");
根据ID查询单条文档
| 属性 | 值 |
|---|---|
| 方法名 | findByDocId |
| 描述 | 根据文档ID查询指定的文档 |
| 返回类型 | <T> Optional<T> |
参数表
| 参数名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| indexName | String | 是 | 索引/集合名称 |
| docId | String | 是 | 文档ID |
| clazz | Class<T> | 是 | 文档类型Class |
返回值
Optional<T>: 包装的文档对象
使用示例
Optional<User> user = elasticsearchService.findByDocId("users", "user_001", User.class);
根据查询条件查找单个文档
| 属性 | 值 |
|---|---|
| 方法名 | findOne |
| 描述 | 根据查询条件查找单个文档 |
| 返回类型 | <T> T |
参数表
| 参数名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| wrapper | SearchQueryWrapper<T> | 是 | 查询参数构建器 |
返回值
- 匹配的文档对象,如果未找到返回null
使用示例
User user = elasticsearchService.findOne(
SearchQueryWrapper.create(User.class, "users")
.eq(User::getName, "张三")
);
批量操作接口
批量保存文档
| 属性 | 值 |
|---|---|
| 方法名 | saveBatch |
| 描述 | 批量保存多个文档到指定的索引或集合中 |
| 返回类型 | <T> List<T> |
参数表
| 参数名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| indexName | String | 是 | 索引/集合名称 |
| documents | List<T> | 是 | 文档列表 |
返回值
- 保存后的文档列表
使用示例
List<User> users = Arrays.asList(
new User("张三", 25),
new User("李四", 30)
);
List<User> savedUsers = elasticsearchService.saveBatch("users", users);
批量根据ID删除文档
| 属性 | 值 |
|---|---|
| 方法名 | deleteBatchByIds |
| 描述 | 根据文档ID列表批量删除文档 |
| 返回类型 | boolean |
参数表
| 参数名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| indexName | String | 是 | 索引/集合名称 |
| ids | List<String> | 是 | 文档ID列表 |
返回值
true: 删除成功false: 删除失败
使用示例
List<String> ids = Arrays.asList("1", "2", "3");
boolean deleted = elasticsearchService.deleteBatchByIds("users", ids);
基础查询接口
查询文档总数
| 属性 | 值 |
|---|---|
| 方法名 | count |
| 描述 | 根据查询条件统计匹配的文档总数 |
| 返回类型 | long |
参数表
| 参数名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| wrapper | SearchQueryWrapper<T> | 是 | 查询参数构建器 |
返回值
- 匹配文档总数
使用示例
long count = elasticsearchService.count(
SearchQueryWrapper.create(User.class, "users")
.eq(User::getStatus, "active")
);
根据查询条件删除文档
| 属性 | 值 |
|---|---|
| 方法名 | deleteByCondition |
| 描述 | 根据指定的查询条件删除匹配的所有文档 |
| 返回类型 | long |
参数表
| 参数名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| wrapper | SearchQueryWrapper<T> | 是 | 查询参数构建器 |
返回值
- 删除的文档数量
使用示例
long deletedCount = elasticsearchService.deleteByCondition(
SearchQueryWrapper.create(User.class, "users")
.eq(User::getStatus, "inactive")
);
高级查询接口
高级查询
| 属性 | 值 |
|---|---|
| 方法名 | search |
| 描述 | 高级查询,支持常规查询、布尔查询、嵌套查询、脚本查询等 |
| 返回类型 | <T> PageResult<T> |
参数表
| 参数名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| wrapper | SearchQueryWrapper<T> | 是 | 查询参数构建器 |
返回值
- 分页结果PageResult
使用示例
PageResult<User> result = elasticsearchService.search(
SearchQueryWrapper.create(User.class, "users")
.eq(User::getStatus, "active")
.ge(User::getAge, 18)
.like(User::getName, "张")
.page(0, 10)
.orderByDesc(User::getCreateTime)
);
原生查询接口
DSL查询
| 属性 | 值 |
|---|---|
| 方法名 | dslSearch |
| 描述 | 原生查询,使用搜索引擎原生DSL语法 |
| 返回类型 | <T> PageResult<T> |
参数表
| 参数名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| wrapper | StringQueryWrapper<T> | 是 | 查询参数构建器 |
返回值
- 查询结果PageResult
使用示例
PageResult<Order> result = elasticsearchService.dslSearch(
StringQueryWrapper.create(Order.class, "orders")
.page(0, 10)
.stringQuery("""
{
"nested": {
"path": "orderItems",
"query": {
"match": {
"orderItems.productName": "iPhone"
}
}
}
}
""")
);
Native查询
| 属性 | 值 |
|---|---|
| 方法名 | nativeSearch |
| 描述 | 原生查询,使用搜索引擎原生API语法 |
| 返回类型 | <T> PageResult<T> |
参数表
| 参数名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| wrapper | NativeQueryWrapper<T> | 是 | 查询参数构建器 |
返回值
- 查询结果PageResult
使用示例
PageResult<Order> result = elasticsearchService.nativeSearch(
NativeQueryWrapper.create(Order.class, "orders")
.page(0, 10)
.nativeQuery(q -> q
.bool(b -> b
.must(m -> m.term(t -> t.field("status").value("ACTIVE")))
.must(m -> m.range(r -> r.field("amount").gte(gte -> gte.value(100.0))))
)
);
数据结构说明
SearchQueryWrapper
查询参数构建器,用于构建复杂的搜索条件。推荐使用此方式构建查询,内部会自动处理build操作。
基础查询方法
| 方法名 | 描述 | 示例 |
|---|---|---|
eq(column, value) | 等于 | .eq(User::getName, "张三") |
ne(column, value) | 不等于 | .ne(User::getStatus, "inactive") |
in(column, values) | 包含在集合中 | .in(User::getCategory, Arrays.asList("A", "B")) |
notIn(column, values) | 不包含在集合中 | .notIn(User::getStatus, Arrays.asList("deleted")) |
gt(column, value) | 大于 | .gt(User::getAge, 18) |
ge(column, value) | 大于等于 | .ge(User::getAge, 18) |
lt(column, value) | 小于 | .lt(User::getAge, 65) |
le(column, value) | 小于等于 | .le(User::getAge, 65) |
between(column, from, to) | 区间查询 | .between(User::getCreateTime, startDate, endDate) |
like(column, value) | 模糊匹配 | .like(User::getName, "张") |
exists(column) | 字段存在 | .exists(User::getEmail) |
notExists(column) | 字段不存在 | .notExists(User::getDeletedAt) |
排序和分页方法
| 方法名 | 描述 | 示例 |
|---|---|---|
orderByAsc(column) | 升序排序 | .orderByAsc(User::getCreateTime) |
orderByDesc(column) | 降序排序 | .orderByDesc(User::getUpdateTime) |
page(page, size) | 分页设置 | .page(0, 10) |
高亮和建议方法
| 方法名 | 描述 | 示例 |
|---|---|---|
highlight(columns...) | 设置高亮字段 | .highlight(User::getName, User::getDescription) |
suggest(column, prefix) | 设置建议查询 | .suggest(User::getName, "张") |
suggest(column, prefix, size) | 设置建议查询(指定数量) | .suggest(User::getName, "张", 5) |
StringQueryWrapper
JSON字符串查询包装器,用于直接使用Elasticsearch JSON查询字符串。
| 字段名 | 类型 | 描述 |
|---|---|---|
stringQuery | String | 原生查询JSON字符串 |
使用示例
StringQueryWrapper<Order> wrapper = StringQueryWrapper
.create(Order.class, "orders")
.page(0, 10)
.stringQuery("""
{
"bool": {
"must": [
{"term": {"status": "ACTIVE"}},
{"range": {"amount": {"gte": 100}}}
]
}
}
""");
NativeQueryWrapper
类型安全的原生查询包装器,使用Spring Data Elasticsearch的NativeQuery。
| 字段名 | 类型 | 描述 |
|---|---|---|
nativeQuery | Query | Elasticsearch原生查询对象 |
使用示例
NativeQueryWrapper<Order> wrapper = NativeQueryWrapper
.create(Order.class, "orders")
.page(0, 10)
.nativeQuery(q -> q
.bool(b -> b
.must(m -> m.term(t -> t.field("status").value("ACTIVE")))
.must(m -> m.range(r -> r.field("amount").gte(gte -> gte.value(100.0))))
);
PageResult
统一分页结果对象。
| 字段名 | 类型 | 描述 |
|---|---|---|
content | List<T> | 当前页数据列表 |
total | long | 总条数 |
page | int | 当前页码(从0开始) |
size | int | 每页大小 |
aggregations | Map<String, Object> | 聚合结果 |
highlights | Map<String, Map<String, List<String>>> | 高亮内容 |
suggestions | Map<String, List<String>> | 建议/自动补全结果 |
使用示例
基本查询示例
@Service
@RequiredArgsConstructor
public class UserService {
private final ElasticsearchService elasticsearchService;
public PageResult<User> searchUsers(String keyword, int page, int size) {
SearchQueryWrapper<User> wrapper = SearchQueryWrapper
.create(User.class, "users")
.page(page, size);
if (StringUtils.hasText(keyword)) {
wrapper.like(User::getName, keyword);
}
return elasticsearchService.search(wrapper);
}
public User findUserByName(String name) {
return elasticsearchService.findOne(
SearchQueryWrapper.create(User.class, "users")
.eq(User::getName, name)
);
}
public long countActiveUsers() {
return elasticsearchService.count(
SearchQueryWrapper.create(User.class, "users")
.eq(User::getStatus, "active")
);
}
}
复杂查询示例
// 多条件组合查询
SearchQueryWrapper<Order> wrapper = SearchQueryWrapper
.create(Order.class, "orders")
.eq(Order::getStatus, "ACTIVE")
.between(Order::getCreateTime, startDate, endDate)
.in(Order::getCategory, Arrays.asList("ELECTRONICS", "CLOTHING"))
.like(Order::getDescription, "重要")
.orderByDesc(Order::getCreateTime)
.page(0, 20)
.highlight(Order::getDescription, Order::getOrderName);
PageResult<Order> result = elasticsearchService.search(wrapper);
聚合查询示例
// 使用 dslSearch 进行聚合查询
StringQueryWrapper<Order> wrapper = StringQueryWrapper
.create(Order.class, "orders")
.page(0, 0) // 聚合查询不需要分页
.stringQuery("""
{
"query": {
"term": {"status": "ACTIVE"}
},
"aggs": {
"category_stats": {
"terms": {"field": "category"},
"aggs": {
"total_amount": {"sum": {"field": "amount"}},
"avg_amount": {"avg": {"field": "amount"}}
}
},
"amount_stats": {
"stats": {"field": "amount"}
}
}
}
""");
PageResult<Order> result = elasticsearchService.dslSearch(wrapper);
Map<String, Object> aggregations = result.getAggregations();
批量操作示例
// 批量保存
List<User> users = Arrays.asList(
new User("张三", 25),
new User("李四", 30)
);
List<User> savedUsers = elasticsearchService.saveBatch("users", users);
// 批量删除
List<String> ids = Arrays.asList("1", "2", "3");
boolean deleted = elasticsearchService.deleteBatchByIds("users", ids);