package com.example.demo.model;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.time.LocalDateTime;
import java.util.List;
@Document(indexName = "products")
public class Product {
@Id
private String id;
@Field(type = FieldType.Text, analyzer = "standard")
private String name;
@Field(type = FieldType.Text, analyzer = "standard")
private String description;
@Field(type = FieldType.Keyword)
private String category;
@Field(type = FieldType.Double)
private Double price;
@Field(type = FieldType.Integer)
private Integer stock;
@Field(type = FieldType.Keyword)
private List<String> tags;
@Field(type = FieldType.Date)
private LocalDateTime createdAt;
@Field(type = FieldType.Boolean)
private Boolean available;
// Getters and setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getCategory() { return category; }
public void setCategory(String category) { this.category = category; }
public Double getPrice() { return price; }
public void setPrice(Double price) { this.price = price; }
public Integer getStock() { return stock; }
public void setStock(Integer stock) { this.stock = stock; }
public List<String> getTags() { return tags; }
public void setTags(List<String> tags) { this.tags = tags; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public Boolean getAvailable() { return available; }
public void setAvailable(Boolean available) { this.available = available; }
}
package com.example.demo.repository;
import com.example.demo.model.Product;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import java.util.List;
public interface ProductRepository extends ElasticsearchRepository<Product, String> {
// Method name query
List<Product> findByName(String name);
// Full-text search on multiple fields
List<Product> findByNameOrDescription(String name, String description);
// Range query
List<Product> findByPriceBetween(Double minPrice, Double maxPrice);
// Custom query with fuzzy search
@Query("{"fuzzy": {"name": {"value": "?0", "fuzziness": "AUTO"}}}")
List<Product> fuzzySearch(String term);
// Multi-match query
@Query("{"multi_match": {"query": "?0", "fields": ["name^2", "description", "tags"]}}")
Page<Product> multiMatchSearch(String query, Pageable pageable);
// Filter by category and price
Page<Product> findByCategoryAndPriceLessThan(String category, Double maxPrice, Pageable pageable);
// Find available products
List<Product> findByAvailableTrue();
// Find by tags
List<Product> findByTagsContaining(String tag);
}
package com.example.demo.service;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import com.example.demo.model.Product;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class ProductSearchService {
private final ElasticsearchOperations elasticsearchOperations;
public ProductSearchService(ElasticsearchOperations elasticsearchOperations) {
this.elasticsearchOperations = elasticsearchOperations;
}
public List<Product> searchProducts(String query) {
NativeQuery searchQuery = NativeQuery.builder()
.withQuery(q -> q
.multiMatch(m -> m
.query(query)
.fields("name^3", "description^2", "tags")
.fuzziness("AUTO")
)
)
.build();
SearchHits<Product> searchHits = elasticsearchOperations.search(
searchQuery,
Product.class
);
return searchHits.getSearchHits().stream()
.map(SearchHit::getContent)
.collect(Collectors.toList());
}
public List<Product> advancedSearch(String query, String category, Double minPrice, Double maxPrice) {
NativeQuery searchQuery = NativeQuery.builder()
.withQuery(q -> q
.bool(b -> b
.must(m -> m.multiMatch(mm -> mm.query(query).fields("name", "description")))
.filter(f -> f.term(t -> t.field("category").value(category)))
.filter(f -> f.range(r -> r.field("price").gte(minPrice).lte(maxPrice)))
)
)
.build();
SearchHits<Product> searchHits = elasticsearchOperations.search(
searchQuery,
Product.class
);
return searchHits.getSearchHits().stream()
.map(SearchHit::getContent)
.collect(Collectors.toList());
}
}
Elasticsearch provides powerful full-text search capabilities. Spring Data Elasticsearch offers repository abstraction similar to JPA. @Document annotates entity classes with index mapping. @Field customizes field types and analyzers. Queries use method names, @Query annotation, or query builders. Full-text search supports stemming, synonyms, fuzzy matching. Aggregations enable faceted search and analytics. Highlighting shows match context. Geospatial queries find nearby locations. Auto-complete uses completion suggesters. Bulk operations optimize indexing. Elasticsearch scales horizontally for large datasets. Integration enables sophisticated search features—filtering, sorting, pagination, relevance tuning. Proper mapping design is crucial for performance and relevance. Elasticsearch complements relational databases for search-heavy applications.