Spring Boot集成ElasticSearch實現(xiàn)搜索引擎的示例
Elastic Search是一個開源的,分布式,實時搜索和分析引擎。Spring Boot為Elasticsearch及Spring Data Elasticsearch提供的基于它的抽象提供了基本的配置。Spring Boot提供了一個用于聚集依賴的spring-boot-starter-data-elasticsearch 'StarterPOM'。
ElasticSearch作為搜索引擎,我們需要解決2大問題:
1, 如何將被搜索的數(shù)據(jù)在ES上創(chuàng)建反向索引
2, Java代碼如何與ES交互
其中第一個大問題又分為兩個小問題
1.1,如何初始化已有的數(shù)據(jù)
1.2,如何同步增量數(shù)據(jù)
第二個大問題也有兩種集成方式
2.1 Spring Data 9300端口集成
2.2 Restful API 9200端口集成
本篇先解決第二大問題。
第一種方式,利用RestAPI方式,也叫Jest方式:
示例代碼:https://github.com/yejingtao/forblog/tree/master/demo-jest-elasticsearch
Pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>yejingtao.demo.springcloud</groupId>
<artifactId>demo-jest-elasticsearch</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo-jest-elasticsearch</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>io.searchbox</groupId>
<artifactId>jest</artifactId>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
</dependency>
</dependencies>
</project>
Application.yml:
server: port: 7081 spring: elasticsearch: jest: uris: - http://192.168.226.133:9200 read-timeout: 5000
注意這里是9200端口
主程序:最簡單的Spring boot啟動程序:
@SpringBootApplication
public class ESApplication {
public static void main(String[] args) {
SpringApplication.run(ESApplication.class);
}
}
定義好ES中的實體類和對ES操作的接口:
public class Entity implements Serializable{
private static final long serialVersionUID = -763638353551774166L;
public static final String INDEX_NAME = "index_entity";
public static final String TYPE = "tstype";
private Long id;
private String name;
public Entity() {
super();
}
public Entity(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public interface CityESService {
void saveEntity(Entity entity);
void saveEntity(List<Entity> entityList);
List<Entity> searchEntity(String searchContent);
}
接口實現(xiàn):
@Service
public class CityESServiceImpl implements CityESService{
private static final Logger LOGGER = LoggerFactory.getLogger(CityESServiceImpl.class);
@Autowired
private JestClient jestClient;
@Override
public void saveEntity(Entity entity) {
Index index = new Index.Builder(entity).index(Entity.INDEX_NAME).type(Entity.TYPE).build();
try {
jestClient.execute(index);
LOGGER.info("ES 插入完成");
} catch (IOException e) {
e.printStackTrace();
LOGGER.error(e.getMessage());
}
}
/**
* 批量保存內(nèi)容到ES
*/
@Override
public void saveEntity(List<Entity> entityList) {
Bulk.Builder bulk = new Bulk.Builder();
for(Entity entity : entityList) {
Index index = new Index.Builder(entity).index(Entity.INDEX_NAME).type(Entity.TYPE).build();
bulk.addAction(index);
}
try {
jestClient.execute(bulk.build());
LOGGER.info("ES 插入完成");
} catch (IOException e) {
e.printStackTrace();
LOGGER.error(e.getMessage());
}
}
/**
* 在ES中搜索內(nèi)容
*/
@Override
public List<Entity> searchEntity(String searchContent){
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//searchSourceBuilder.query(QueryBuilders.queryStringQuery(searchContent));
//searchSourceBuilder.field("name");
searchSourceBuilder.query(QueryBuilders.matchQuery("name",searchContent));
Search search = new Search.Builder(searchSourceBuilder.toString())
.addIndex(Entity.INDEX_NAME).addType(Entity.TYPE).build();
try {
JestResult result = jestClient.execute(search);
return result.getSourceAsObjectList(Entity.class);
} catch (IOException e) {
LOGGER.error(e.getMessage());
e.printStackTrace();
}
return null;
}
}
這里插入數(shù)據(jù)的方式給了兩種,一種是單次API直接插入,一種是利用ES的bulk批量插入。
做一個controller方面我們測試:
啟動后在瀏覽器中請求http://localhost:7081/entityController/search?name=%E4%BA%BA%E6%89%8B%E4%BA%95
得到結(jié)果:

這里只返回了9條記錄,而理論上ES默認的size是10,應(yīng)該不是分頁的問題,而是只能檢索出9條匹配記錄,用Kibana連上相同的搜索確認下:

這里用的是standard分詞方式,將每個中文都作為了一個term,凡是包含“人”“手”“井”的都被搜索了出來,只是評分不同,如果想支持只能中文索引需要依賴ik插件
OK,RestFul方式對ElasticSearch的檢索已經(jīng)搞定了,更多的擴展可以慢慢研究下QueryBuilders里的源碼和批注。
第二種方式,利用Spring Data客戶端方式:
事先說明此方式有個弊端,讓我掉了坑里好久才爬上來,Spring Data ElasticSearch必須與ElasticSearch版本相匹配,否則在對接時ES端會報版本不匹配錯誤,例如我ES是5.6.1版本,Spring boot是1.5.6版本,錯誤如下:
為解決這個問題我查找了一些資料,Spring Data與elasticsearch版本對應(yīng)關(guān)系如下:
|
spring data elasticsearch |
elasticsearch |
|
3.0.0.RC2 |
5.5.0 |
|
3.0.0.M4 |
5.4.0 |
|
2.0.4.RELEASE |
2.4.0 |
|
2.0.0.RELEASE |
2.2.0 |
|
1.4.0.M1 |
1.7.3 |
|
1.3.0.RELEASE |
1.5.2 |
|
1.2.0.RELEASE |
1.4.4 |
|
1.1.0.RELEASE |
1.3.2 |
|
1.0.0.RELEASE |
1.1.1 |
而我用的Spring Boot 1.5.6版本對應(yīng)的Spring Data ElasticSearch是2.1.6版本,不支持5.X的ES,所以報錯。到本博文撰寫為止,Spring Boot的RELEASE版本最新的是1.5.8,對應(yīng)的Spring Data ElasticSearch是2.1.8,仍不支持5.X的ES,所以如果一定要使用Java客戶端方式集成ES只能放棄Spring Boot直接使用Spring Data和Spring MVC,或者降低ES的版本使之與Spring boot匹配。
示例代碼:https://github.com/yejingtao/forblog/tree/master/demo-data-elasticsearch
pom.xml依賴:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>yejingtao.demo.springcloud</groupId>
<artifactId>demo-data-elasticsearch</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo-data-elasticsearch</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.8.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
</dependencies>
</project>
不再引用Jest。
application.yml:
server:
port: 7081
spring:
data:
elasticsearch:
cluster-nodes: 192.168.226.133:9300
cluster-name: my-es
repositories:
enabled: true
注意這里是9300端口
Controller、主程序、Service接口同Jest項目不變,不再羅列
實體類稍作變化,指定ES中的index和type:
@Document(indexName="index_entity", type="tstype")
多一個Repository接口,無需實現(xiàn)類,spring data標準用法:
/**
* Entity ES操作類
* @author yejingtao
*
*/
public interface EntityRepository extends ElasticsearchRepository<Entity,Long>{
}
Service實現(xiàn)類與Jest的天壤之別了,從語法上可以看出更像是對數(shù)據(jù)庫層的操作:
@Service
public class CityESServiceImpl implements CityESService{
private static final Logger LOGGER = LoggerFactory.getLogger(CityESServiceImpl.class);
int PAGE_SIZE = 15; //默認分頁大小
int PAGE_NUMBER = 0; //默認當前分頁
String SCORE_MODE_SUM = "sum"; //權(quán)重分求和模式
Float MIN_SCORE = 10.0F; //由于無相關(guān)性的分值默認為1, 設(shè)置權(quán)重分最小值為10
@Autowired
EntityRepository entityRepository;
/**
* 保存內(nèi)容到ES
*/
@Override
public Long saveEntity(Entity entity) {
Entity entityResult = entityRepository.save(entity);
return entityResult.getId();
}
/**
* 在ES中搜索內(nèi)容
*/
@Override
public List<Entity> searchEntity(int pageNumber, int pageSize, String searchContent){
if(pageSize==0) {
pageSize = PAGE_SIZE;
}
if(pageNumber<0) {
pageNumber = PAGE_NUMBER;
}
SearchQuery searchQuery = getEntitySearchQuery(pageNumber,pageSize,searchContent);
LOGGER.info("\n searchCity: searchContent [" + searchContent + "] \n DSL = \n "
+ searchQuery.getQuery().toString());
Page<Entity> cityPage = entityRepository.search(searchQuery);
return cityPage.getContent();
}
/**
* 組裝搜索Query對象
* @param pageNumber
* @param pageSize
* @param searchContent
* @return
*/
private SearchQuery getEntitySearchQuery(int pageNumber, int pageSize, String searchContent) {
FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery()
.add(QueryBuilders.matchPhraseQuery("name", searchContent),
ScoreFunctionBuilders.weightFactorFunction(1000))
//.add(QueryBuilders.matchPhraseQuery("other", searchContent),
//ScoreFunctionBuilders.weightFactorFunction(1000))
.scoreMode(SCORE_MODE_SUM).setMinScore(MIN_SCORE);
//設(shè)置分頁,否則只能按照ES默認的分頁給
Pageable pageable = new PageRequest(pageNumber, pageSize);
return new NativeSearchQueryBuilder().withPageable(pageable).withQuery(functionScoreQueryBuilder).build();
}
}
測試方式同Jest。
這兩種方式,從設(shè)計上來講屬于兩種思路,Spring Data的思路就是將ElasticSearch當自家的數(shù)據(jù)倉庫來管理,直接通過Java客戶端代碼操作ES;Jest的思路是將ElasticSearch當為獨立的服務(wù)端,自己作為客戶端用兼容性最強的RestFul格式來與之交互。
個人比較傾向于Jest方式,第一兼容性好,不需要考慮版本的問題。第二,從ElasticSearch本身的設(shè)計上來分析,9200是對外服務(wù)端口,9300是內(nèi)部管理和集群通信端口,請求9200獲取搜索服務(wù)更符合ES的設(shè)計初衷,不會影響集群內(nèi)部的通信。
以上比較分析僅代表個人觀點,歡迎大神么交流批評。希望對大家的學(xué)習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
SpringMVC數(shù)據(jù)響應(yīng)詳細介紹
Spring MVC 是 Spring 提供的一個基于 MVC 設(shè)計模式的輕量級 Web 開發(fā)框架,本質(zhì)上相當于 Servlet,Spring MVC 角色劃分清晰,分工明細,本章來講解SpringMVC數(shù)據(jù)響應(yīng)2023-02-02
SpringBoot讀取Resource目錄下文件的四種方式總結(jié)
在Spring?Boot項目中,經(jīng)常需要獲取resources目錄下的文件,這些文件可以包括配置文件、模板文件、靜態(tài)資源等,本文將介紹四種常用的方法來獲取resources目錄下的文件,需要的朋友可以參考下2023-08-08
java實現(xiàn)省市區(qū)轉(zhuǎn)換成樹形結(jié)構(gòu)
這篇文章主要為大家詳細介紹了java實現(xiàn)省市區(qū)轉(zhuǎn)換成樹形結(jié)構(gòu),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-08-08
如何利用Java?AWT?創(chuàng)建一個簡易計算器
這篇文章主要介紹了如何利用Java?AWT?創(chuàng)建一個簡易計算器,AWT?是一個有助于構(gòu)建?GUI?的?API?基于?java?應(yīng)用程序,下面關(guān)于其相關(guān)資料實現(xiàn)計算器的內(nèi)容詳細,需要的朋友可以參考一下2022-03-03

