Elasticsearch
Elasticsearch
Lucene,Solr,ElasticSearch 比较
Lucene, Solr, 和 Elasticsearch 是三个与搜索相关的开源项目,它们之间存在紧密的联系,但又有一些区别。以下是它们的比较:
- Lucene:
- Lucene 是一个 Java 编写的全文检索引擎库,提供了强大的文本搜索和索引功能。
- 它是一个基础库,提供了创建搜索应用所需的核心功能,包括索引构建、搜索和分析等。
- Lucene 可以被其他项目作为底层搜索引擎来使用,但它本身并不提供完整的搜索应用解决方案。
- Solr:
- Solr 是一个构建在 Lucene 之上的搜索平台,使用 Java 编写,提供了基于 HTTP 的 RESTful API。
- Solr 提供了一系列的功能,包括分布式搜索、复杂查询、实时索引、缓存、扩展性等。
- 它提供了方便的配置和管理工具,使得构建搜索应用更加容易。
- Solr 适用于需要快速构建搜索应用的情况,尤其是对于企业级应用或者需要自定义搜索逻辑的场景。
- Elasticsearch:
- Elasticsearch 也是基于 Lucene 构建的搜索引擎,但它不仅仅是一个搜索引擎,还是一个分布式文档存储和分析引擎。
- Elasticsearch 提供了简单的 RESTful API,并且具有更广泛的用途,包括日志和事件分析、数据可视化、实时搜索等。
- 它具有强大的分布式能力,支持水平扩展,可以处理大规模数据和高并发请求。
- Elasticsearch 还提供了丰富的插件和生态系统,使得它更容易与其他工具和系统集成。
总的来说,Lucene 提供了搜索引擎的核心功能,Solr 是构建在 Lucene 之上的搜索平台,提供了更多的功能和方便的管理工具,而 Elasticsearch 则是一个更加广泛用途的分布式搜索和分析引擎,具有强大的分布式能力和丰富的生态系统。选择其中一个取决于你的需求和项目的规模。
本次演示以Elasticsearch为例
服务端
Docker Desktop 安装最新的Elasticsearch镜像并启动容器,注册端口:9200
http://localhost:9200/ 访问不了,学习演示的话,需要修改es配置文件elasticsearch.yml,关闭ssl安全访问权限控制,重启容器即可。
客户端
web界面安装
修改完成重启容器浏览器访问:http://localhost:9200/ 返回Elasticsearch 实例的详细信息如下:
{
"name" : "00d7fda22075",
"cluster_name" : "docker-cluster",
"cluster_uuid" : "WeZtDCeNS0-Spxw_Uc79VQ",
"version" : {
"number" : "8.12.2",
"build_flavor" : "default",
"build_type" : "docker",
"build_hash" : "48a287ab9497e852de30327444b0809e55d46466",
"build_date" : "2024-02-19T10:04:32.774273190Z",
"build_snapshot" : false,
"lucene_version" : "9.9.2",
"minimum_wire_compatibility_version" : "7.17.0",
"minimum_index_compatibility_version" : "7.0.0"
},
"tagline" : "You Know, for Search"
}
如果需要可视化界面需要安装chrome浏览器插件(简单易用,没有界面样式):Multi Elasticsearch Head
点击插件即可访问界面如下图所示:
也可以安装Kibana组件(功能强大)。
spring-boot项目使用示例
以maven spring-boot-starter-data-elasticsearch插件使用为例,添加spring-boot-starter-data-elasticsearch和elasticsearch-rest-high-level-client包(Rest API)。
maven配置添加如下包
<!--spring整合elasticsearch包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.10.0</version>
</dependency>
项目启动,构建初始化数据,初始化示例代码如下所示:
@PostConstruct
private void init() {
// 创建文档
Book book1 = new Book("1", "Java Programming Smith", "John Doe");
Book book2 = new Book("2", "Spring Boot in Action", "Jane Smith");
Book book3 = new Book("3", "Spring Boot es", "cao Smith");
Book book4 = new Book("4", "Spring Boot mongodb Smith", "lili Smith");
bookRepository.save(book1);
bookRepository.save(book2);
bookRepository.save(book3);
bookRepository.save(book4);
// 创建 Brand 对象
Brand brand = new Brand();
brand.setId("1");
brand.setName("华为");
brand.setCountry("中国");
// 创建 Product 对象并设置嵌套的 Brand 对象
Product product = new Product();
product.setId("1");
product.setName("手机");
product.setPrice(1001.0);
product.setBrand(brand);
// 保存 Product 对象到 Elasticsearch 中
elasticsearchOperations.save(product);
}
其中Brand为Product的内嵌对象,JPA构建es索引代码如下:
package guru.springframework.domain;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.*;
@Data
@Document(indexName = "products")
public class Product {
@Id
private String id;
private String name;
private double price;
@Field(type = FieldType.Nested)
private Brand brand;
}
部分查询代码示例:
package guru.springframework.services;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Service
public class BookService {
@Autowired
private RestHighLevelClient elasticsearchClient;
@Autowired
private BookRepository bookRepository;
private final String index = "library";
public Page<Book> searchBooksByKeyword(String keyword, int page, int size) {
SearchRequest searchRequest = new SearchRequest(index);
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
.should(QueryBuilders.matchQuery("title", keyword))
.should(QueryBuilders.matchQuery("author", keyword));
sourceBuilder.query(boolQuery);
sourceBuilder.from(page * size);
sourceBuilder.size(size);
searchRequest.source(sourceBuilder);
try {
SearchResponse response = elasticsearchClient.search(searchRequest, RequestOptions.DEFAULT);
List<Book> books = extractBooksFromSearchResponse(response);
long totalHits = response.getHits().getTotalHits().value;
return new PageImpl<>(books, PageRequest.of(page, size), totalHits);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private List<Book> extractBooksFromSearchResponse(SearchResponse response) {
List<Book> books = new ArrayList<>();
for (SearchHit hit : response.getHits().getHits()) {
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
String title = (String) sourceAsMap.get("title");
String author = (String) sourceAsMap.get("author");
// 构造Book对象
Book book = new Book();
book.setTitle(title);
book.setAuthor(author);
books.add(book);
}
return books;
}
public Page<Book> searchBooksByKeyword2(String keyword, int page, int size) {
// return bookRepository.findByTitleContainingOrAuthorContaining(keyword,keyword,PageRequest.of(page, size));
return bookRepository.findByTitleOrAuthorCustomQuery(keyword,PageRequest.of(page, size));
}
}
package guru.springframework.services;;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
@Autowired
private RestHighLevelClient elasticsearchClient;
private final String index = "products";
@Autowired
private ObjectMapper objectMapper;
public Page<Product> getAllProducts(int pageNumber, int pageSize) {
Pageable pageable = PageRequest.of(pageNumber, pageSize);
return productRepository.findAll(pageable);
}
public Page<Product> searchProductsByBrandName(String brandName, int pageNumber, int pageSize) throws IOException {
SearchRequest searchRequest = new SearchRequest(index);
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.matchQuery("brand.name", brandName));
sourceBuilder.from(pageNumber * pageSize);
sourceBuilder.size(pageSize);
searchRequest.source(sourceBuilder);
SearchResponse response = elasticsearchClient.search(searchRequest, RequestOptions.DEFAULT);
List<Product> products = extractProductsFromSearchResponse(response);
return new PageImpl<>(products, PageRequest.of(pageNumber, pageSize), response.getHits().getTotalHits().value);
}
private List<Product> extractProductsFromSearchResponse(SearchResponse response) {
return Arrays.stream(response.getHits().getHits())
.map(hit -> {
try {
return objectMapper.readValue(hit.getSourceAsString(), Product.class);
} catch (IOException e) {
throw new RuntimeException("Failed to parse search response", e);
}
})
.collect(Collectors.toList());
}
}
- RestHighLevelClient 提供一些接口,可以通过抽象方式构建查询等一些操作。面向对象编程,但是写法反而更麻烦,代码更冗余。
- JPA方式,有了ChatGPT后,其实JPA写法更快,就一行代码,之前这种手写查询语句的方法不好写或者不好维护,现在不用维护,直接扔给ChatGPT,生成查询语句。在BookRepository查询方法上添加@Query注解查询语句。BookRepository代码如下所示:
package guru.springframework.repositories;
import guru.springframework.domain.Book;
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;
public interface BookRepository extends ElasticsearchRepository<Book, String> {
Page<Book> findByTitleContainingOrAuthorContaining(String title, String author, Pageable pageable);
@Query("{\"bool\": {\"should\": [{\"match\": {\"title\": \"?0\"}}, {\"match\": {\"author\": \"?0\"}}]}}")
Page<Book> findByTitleOrAuthorCustomQuery(String keyword, Pageable pageable);
}
数据查询结果格式如下图所示:
项目源码及调试
Github下载地址:https://github.com/caohuajin/Elasticsearch
ApiPost在线接口:https://console-docs.apipost.cn/preview/d900735c0fcb6588/d644ecf613c746f4