Spring Boot集成Elasticsearch指南
Spring Boot集成Elasticsearch指南
1. 环境准备
1.1 依赖配置
在pom.xml
中添加Elasticsearch相关依赖:
<dependencies>
<!-- Spring Data Elasticsearch -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- 如果需要使用RestHighLevelClient -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.17.4</version>
</dependency>
</dependencies>
1.2 配置文件
在application.yml
中添加Elasticsearch配置:
spring:
elasticsearch:
rest:
uris: http://localhost:9200
username: elastic
password: your-password
data:
elasticsearch:
repositories:
enabled: true
2. 基础配置
2.1 配置Elasticsearch客户端
@Configuration
public class ElasticsearchConfig {
@Value("${spring.elasticsearch.rest.uris}")
private String elasticsearchUrl;
@Value("${spring.elasticsearch.rest.username}")
private String username;
@Value("${spring.elasticsearch.rest.password}")
private String password;
@Bean
public RestHighLevelClient restHighLevelClient() {
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(username, password));
RestClientBuilder builder = RestClient.builder(
new HttpHost("localhost", 9200, "http"))
.setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder
.setDefaultCredentialsProvider(credentialsProvider));
return new RestHighLevelClient(builder);
}
}
2.2 配置ElasticsearchTemplate
@Configuration
public class ElasticsearchConfig {
@Bean
public ElasticsearchOperations elasticsearchTemplate(RestHighLevelClient client) {
return new ElasticsearchRestTemplate(client);
}
}
3. 实体类映射
3.1 创建实体类
@Document(indexName = "products")
public class Product {
@Id
private String id;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String name;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String description;
@Field(type = FieldType.Double)
private Double price;
@Field(type = FieldType.Keyword)
private String category;
@Field(type = FieldType.Date)
private Date createTime;
// getters and setters
}
3.2 创建Repository接口
@Repository
public interface ProductRepository extends ElasticsearchRepository<Product, String> {
// 自定义查询方法
List<Product> findByName(String name);
List<Product> findByPriceBetween(Double minPrice, Double maxPrice);
List<Product> findByCategory(String category);
// 使用@Query注解自定义查询
@Query("{\"bool\": {\"must\": [{\"match\": {\"name\": \"?0\"}}]}}")
List<Product> searchByName(String name);
}
4. 服务层实现
4.1 创建Service接口
public interface ProductService {
Product save(Product product);
void delete(String id);
Product findById(String id);
List<Product> search(String keyword);
List<Product> findByCategory(String category);
List<Product> findByPriceRange(Double minPrice, Double maxPrice);
}
4.2 实现Service
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductRepository productRepository;
@Autowired
private ElasticsearchOperations elasticsearchOperations;
@Override
public Product save(Product product) {
return productRepository.save(product);
}
@Override
public void delete(String id) {
productRepository.deleteById(id);
}
@Override
public Product findById(String id) {
return productRepository.findById(id).orElse(null);
}
@Override
public List<Product> search(String keyword) {
// 使用QueryBuilder构建查询
QueryBuilder queryBuilder = QueryBuilders.multiMatchQuery(keyword, "name", "description")
.analyzer("ik_max_word");
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(queryBuilder)
.build();
return elasticsearchOperations.search(searchQuery, Product.class)
.stream()
.map(SearchHit::getContent)
.collect(Collectors.toList());
}
@Override
public List<Product> findByCategory(String category) {
return productRepository.findByCategory(category);
}
@Override
public List<Product> findByPriceRange(Double minPrice, Double maxPrice) {
return productRepository.findByPriceBetween(minPrice, maxPrice);
}
}
5. 控制器实现
5.1 创建Controller
@RestController
@RequestMapping("/api/products")
public class ProductController {
@Autowired
private ProductService productService;
@PostMapping
public ResponseEntity<Product> createProduct(@RequestBody Product product) {
return ResponseEntity.ok(productService.save(product));
}
@GetMapping("/{id}")
public ResponseEntity<Product> getProduct(@PathVariable String id) {
Product product = productService.findById(id);
return product != null ? ResponseEntity.ok(product) : ResponseEntity.notFound().build();
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteProduct(@PathVariable String id) {
productService.delete(id);
return ResponseEntity.ok().build();
}
@GetMapping("/search")
public ResponseEntity<List<Product>> searchProducts(@RequestParam String keyword) {
return ResponseEntity.ok(productService.search(keyword));
}
@GetMapping("/category/{category}")
public ResponseEntity<List<Product>> getProductsByCategory(@PathVariable String category) {
return ResponseEntity.ok(productService.findByCategory(category));
}
@GetMapping("/price-range")
public ResponseEntity<List<Product>> getProductsByPriceRange(
@RequestParam Double minPrice,
@RequestParam Double maxPrice) {
return ResponseEntity.ok(productService.findByPriceRange(minPrice, maxPrice));
}
}
6. 高级功能实现
6.1 聚合查询
@Service
public class ProductServiceImpl implements ProductService {
// ... 其他方法 ...
public Map<String, Long> getProductCountByCategory() {
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.addAggregation(AggregationBuilders.terms("category_count")
.field("category.keyword")
.size(10))
.build();
SearchHits<Product> searchHits = elasticsearchOperations.search(searchQuery, Product.class);
TermsAggregation termsAggregation = searchHits.getAggregations().get("category_count");
return termsAggregation.getBuckets().stream()
.collect(Collectors.toMap(
TermsAggregation.Bucket::getKeyAsString,
TermsAggregation.Bucket::getDocCount
));
}
}
6.2 高亮显示
@Service
public class ProductServiceImpl implements ProductService {
// ... 其他方法 ...
public List<Map<String, Object>> searchWithHighlight(String keyword) {
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.multiMatchQuery(keyword, "name", "description"))
.withHighlightFields(
new HighlightBuilder.Field("name"),
new HighlightBuilder.Field("description")
)
.build();
SearchHits<Product> searchHits = elasticsearchOperations.search(searchQuery, Product.class);
return searchHits.stream()
.map(hit -> {
Map<String, Object> result = new HashMap<>();
result.put("product", hit.getContent());
result.put("highlight", hit.getHighlightFields());
return result;
})
.collect(Collectors.toList());
}
}
7. 性能优化
7.1 批量操作
@Service
public class ProductServiceImpl implements ProductService {
// ... 其他方法 ...
public void bulkSave(List<Product> products) {
List<IndexQuery> queries = products.stream()
.map(product -> new IndexQueryBuilder()
.withId(product.getId())
.withObject(product)
.build())
.collect(Collectors.toList());
elasticsearchOperations.bulkIndex(queries, Product.class);
}
}
7.2 索引优化
@Configuration
public class ElasticsearchConfig {
// ... 其他配置 ...
@Bean
public ElasticsearchOperations elasticsearchTemplate(RestHighLevelClient client) {
ElasticsearchRestTemplate template = new ElasticsearchRestTemplate(client);
// 配置索引设置
Settings settings = Settings.builder()
.put("index.number_of_shards", 3)
.put("index.number_of_replicas", 1)
.put("index.refresh_interval", "30s")
.build();
// 创建索引
if (!template.indexOps(Product.class).exists()) {
template.indexOps(Product.class).create(settings);
}
return template;
}
}
8. 异常处理
8.1 全局异常处理
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ElasticsearchException.class)
public ResponseEntity<ErrorResponse> handleElasticsearchException(ElasticsearchException ex) {
ErrorResponse error = new ErrorResponse(
"Elasticsearch操作失败",
ex.getMessage(),
HttpStatus.INTERNAL_SERVER_ERROR.value()
);
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@Data
@AllArgsConstructor
class ErrorResponse {
private String message;
private String details;
private int status;
}
9. 测试
9.1 单元测试
@SpringBootTest
class ProductServiceTest {
@Autowired
private ProductService productService;
@Test
void testSaveAndSearch() {
// 创建测试数据
Product product = new Product();
product.setName("测试商品");
product.setDescription("这是一个测试商品");
product.setPrice(99.99);
product.setCategory("测试分类");
// 保存商品
Product savedProduct = productService.save(product);
assertNotNull(savedProduct.getId());
// 搜索商品
List<Product> results = productService.search("测试");
assertFalse(results.isEmpty());
assertEquals(savedProduct.getId(), results.get(0).getId());
}
}
10. 部署注意事项
10.1 生产环境配置
spring:
elasticsearch:
rest:
uris: ${ELASTICSEARCH_URL:http://localhost:9200}
username: ${ELASTICSEARCH_USERNAME:elastic}
password: ${ELASTICSEARCH_PASSWORD:your-password}
data:
elasticsearch:
repositories:
enabled: true
client:
reactive:
use-ssl: true
10.2 健康检查
@Component
public class ElasticsearchHealthIndicator implements HealthIndicator {
@Autowired
private RestHighLevelClient client;
@Override
public Health health() {
try {
ClusterHealthResponse health = client.cluster().health(
new ClusterHealthRequest(), RequestOptions.DEFAULT);
if (health.getStatus() == ClusterHealthStatus.GREEN) {
return Health.up().build();
} else if (health.getStatus() == ClusterHealthStatus.YELLOW) {
return Health.up().withDetail("status", "warning").build();
} else {
return Health.down().withDetail("status", "critical").build();
}
} catch (IOException e) {
return Health.down(e).build();
}
}
}
11. 参考资料
- Spring Data Elasticsearch官方文档
- Elasticsearch官方文档
- Spring Boot官方文档