ElasticSearch
是⼀个开源的搜索引擎,建⽴在⼀个全⽂搜索引擎库
Apache Lucene™
基础之上。
Lucene
可以说是当下最先进、⾼性能、全功能的搜索引擎库
——
⽆论是开源还是私有。
ElasticSearch
使⽤
Java
编写的,它的内部使⽤的是
Lucene
做索引与搜索,它的
⽬的是使全⽂检索变得简
单,通过隐藏
Lucene
的复杂性,取⽽代之提供了⼀套简单⼀致的
RESTful API
。
然⽽,
ElasticSearch
不仅仅是
Lucene
,并且也不仅仅只是⼀个全⽂搜索引擎,它可以被下⾯这样准确地形
容:
- ⼀个分布式的实时⽂档存储,每个字段可以被索引与搜索
- ⼀个分布式实时分析搜索引擎
- 能胜任上百个服务节点的扩展,并⽀持 PB 级别的结构化或者⾮结构化数据
ElasticSearch
已经被各⼤互联⽹公司验证其抢到的检索能⼒:
- Wikipedia 使⽤ ElasticSearch 提供带有⾼亮⽚段的全⽂搜索,还有 search-as-you-type 和 did-youmean 的建议;
- 《卫报》使⽤ ElasticSearch 将⽹络社交数据结合到访客⽇志中,实时的给编辑们提供公众对于新⽂章 的反馈;
- Stack Overflflow 将地理位置查询融⼊全⽂检索中去,并且使⽤ more-like-this 接⼝去查找相关的问题与 答案;
- GitHub 使⽤ ElasticSearch 对 1300 亿⾏代码进⾏查询。 ⼩故事
关于
ElasticSearch
有⼀个⼩故事,在这⾥也分享给⼤家:
多年前,
Shay Banon
是⼀位刚结婚不久的失业开发者,由于妻⼦要去伦敦学习厨师,他便跟着也去
了。在他找⼯作的过程中,为了给妻⼦构建⼀个⻝谱的搜索引擎,他开始构建⼀个早期版本的
Lucene
。
直接基于
Lucene
⼯作会⽐较困难,因此
Shay
开始抽象
Lucene
代码以便
Java
程序员可以在应⽤中添
加搜索功能,他发布了他的第⼀个开源项⽬,叫做
“Compass”
。
后来
Shay
找到了⼀份⼯作,这份⼯作处在⾼性能和内存数据⽹格的分布式环境中,因此⾼性能的、实
时的、分布式的搜索引擎也是理所当然需要的,然后他决定重写
Compass
库使其成为⼀个独⽴的服务
叫做
ElasticSearch
。
第⼀个公开版本出现在
2010
年
2
⽉,在那之后
ElasticSearch
已经成为
GitHub
上最受欢迎的项⽬之
⼀,代码贡献者超过
300
⼈。⼀家主营
ElasticSearch
的公司就此成⽴,他们⼀边提供商业⽀持⼀边开
GitChat
发新功能,不过
ElasticSearch
将永远开源且对所有⼈可⽤。
Shay
的妻⼦依旧等待着她的⻝谱搜索
……
在没有
Spring Boot
之前
Java
程序员使⽤
ElasticSearch
⾮常痛苦,需要对接链接资源、进⾏⼀些列的封装
等操作。
Spring Boot
在
spring-data-elasticsearch
的基础上进⾏了封装,让
Spring Boot
项⽬⾮常⽅便的去
操作
ElasticSearch
,如果前⾯了解过
JPA
技术的话,会发现他的操作语法和
JPA
⾮常的类似。
值得注意的是,
Spring Data ElasticSearch
和
ElasticSearch
是有对应关系的,不同的版本之间不兼容,
Spring Boot 2.1
对应的是
Spring Data ElasticSearch 3.1.2
版本。
Spring Data ElasticSearch | ElasticSearch |
3.1.x | 6.2.2 |
3.0.x | 5.5.0 |
2.1.x | 2.4.0 |
2.0.x | 2.2.0 |
1.3.x | 1.5.2 |
Spring Boot 集成 ElasticSearch
相关配置
在
Pom
中添加
ElasticSearch
的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
配置
ElasticSearch
集群地址:
# 集群名(默认值: elasticsearch,配置⽂件`cluster.name`: es-mongodb)
spring.data.elasticsearch.cluster-name=es-mongodb
# 集群节点地址列表,⽤逗号分隔
spring.data.elasticsearch.cluster-nodes=localhost:9300
相关配置
@Document(indexName = "customer", type = "customer", shards = 1, replicas = 0, ref
reshInterval = "-1")
public class Customer {
@Id
private String id;
private String userName;
private String address;
private int age;
//省略部分 getter/setter
}
- @Document 注解会对实体中的所有属性建⽴索引
- indexName = "customer" 表示创建⼀个名称为 "customer" 的索引
- type = "customer" 表示在索引中创建⼀个名为 "customer" 的 type
- shards = 1 表示只使⽤⼀个分⽚
- replicas = 0 表示不使⽤复制
- refreshInterval = "-1" 表示禁⽤索引刷新
创建操作的
repository
public interface CustomerRepository extends ElasticsearchRepository<Customer, Stri
ng> {
public List<Customer> findByAddress(String address);
public Customer findByUserName(String userName);
public int deleteByUserName(String userName);
}
我们创建了两个查询和⼀个删除的⽅法,从语法可以看出和前⾯
JPA
的使⽤⽅法⾮常类似,跟踪
ElasticsearchRepository
的代码会发现:
ElasticsearchRepository
继承于
ElasticsearchCrudRepository
:
public interface ElasticsearchRepository<T, ID extends Serializable> extends Elast
icsearchCrudRepository<T, ID> {...}
⽽
ElasticsearchCrudRepository
继承于
PagingAndSortingRepository
:
public interface ElasticsearchCrudRepository<T, ID extends Serializable> extends P
agingAndSortingRepository<T, ID>{...}
最后
PagingAndSortingRepository
继承于
CrudRepository
:
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID>{.
..}
类图如下:

通过查看源码发现,
ElasticsearchRepository
最终使⽤和
JPA
操作数据库使⽤的⽗类是⼀样的。通过这些也
可以发现,
Spring Data
项⽬中的成员在最上层有着统⼀的接⼝标准,只是在最终的实现层对不同的数据库进
⾏了差异化封装。
以上简单配置完成之后我们在业务中就可以使⽤
ElasticSearch
了。
测试 CustomerRepository
创建⼀个测试类引⼊
CustomerRepository
:
@RunWith(SpringRunner.class)
@SpringBootTest
public class CustomerRepositoryTest {
@Autowired
private CustomerRepository repository;
}
做⼀个数据插⼊测试:
@Test
public void saveCustomers() {
repository.save(new Customer("Alice", "北京",13));
repository.save(new Customer("Bob", "北京",23));
repository.save(new Customer("neo", "⻄安",30));
repository.save(new Customer("summer", "烟台",22));
}
repository
已经帮我们默认实现了很多的⽅法,其中就包括
save();
。
我们对插⼊的数据做⼀个查询:
@Test
public void fetchAllCustomers() {
System.out.println("Customers found with findAll():");
System.out.println("-------------------------------");
for (Customer customer : repository.findAll()) {
System.out.println(customer);
}
}
输出:
Customers found with findAll():
-------------------------------
Customer{id='aBVS7WYB8U8_i9prF8qm', userName='Alice', address='北京', age=13}
Customer{id='aRVS7WYB8U8_i9prF8rw', userName='Bob', address='北京', age=23}
Customer{id='ahVS7WYB8U8_i9prGMot', userName='neo', address='⻄安', age=30}
Customer{id='axVS7WYB8U8_i9prGMp2', userName='summer', address='北京市海淀区⻄直⻔',
age=22}
通过查询可以发现,插⼊时⾃动⽣成了
ID
信息。
对插⼊的数据进⾏删除:
@Test
public void fetchAllCustomers() {
@Test
public void deleteCustomers() {
repository.deleteAll();
repository.deleteByUserName("neo");
}
}
可以根据属性条件来删除,也可以全部删除。
对属性进⾏修改:
@Test
public void updateCustomers() {
Customer customer= repository.findByUserName("summer");
System.out.println(customer);
customer.setAddress("北京市海淀区⻄直⻔");
repository.save(customer);
Customer xcustomer=repository.findByUserName("summer");
System.out.println(xcustomer);
}
输出:
Customer[id=AWKVYFY4vPQX0UVGnJ7o, userName='summer', address='烟台']
Customer[id=AWKVYFY4vPQX0UVGnJ7o, userName='summer', address='北京市海淀区⻄直⻔']
通过输出发现
summer
⽤户的地址信息已经被变更。
我们可以根据地址信息来查询在北京的顾客信息
:
@Test
public void fetchIndividualCustomers() {
for (Customer customer : repository.findByAddress("北京")) {
System.out.println(customer);
}
}
输出:
Customer{id='aBVS7WYB8U8_i9prF8qm', userName='Alice', address='北京', age=13}
Customer{id='aRVS7WYB8U8_i9prF8rw', userName='Bob', address='北京', age=23}
Customer{id='axVS7WYB8U8_i9prGMp2', userName='summer', address='北京市海淀区⻄直⻔',
age=22}
通过输出可以发现
ElasticSearch
默认给我们进⾏的就是字段全⽂(模糊)查询。
通过以上的示例发现使⽤
Spring Boot
操作
ElasticSearch
⾮常简单,通过少量代码即可实现我们⽇常⼤部分
的业务需求。
⾼级使⽤
上⾯演示了在
Spring Boot
项⽬中对
ElasticSearch
的增、删、改、查操作,通过上⾯的操作也可以发现操作
ElasticSearch
的语法和
Spring Data JPA
的语法⾮常类似,下⾯介绍⼀些复杂的使⽤场景。
分⻚查询
分⻚查询有两种实现⽅式,第⼀种是使⽤
Spring Data
⾃带的分⻚⽅案,另⼀种是⾃⾏组织查询条件最后封
装进⾏查询。我们先来看第⼀个⽅案:
@Test
public void fetchPageCustomers() {
Sort sort = new Sort(Sort.Direction.DESC, "address.keyword");
Pageable pageable = PageRequest.of(0, 10, sort);
Page<Customer> customers=repository.findByAddress("北京", pageable);
System.out.println("Page customers "+customers.getContent().toString());
}
这段代码的含义是,分⻚查询地址包含
“
北京
”
的客户信息,并且按照地址进⾏排序,每⻚显示
10
条。需要注
意的是排序是使⽤的关键字是
address.keyword
,⽽不是
address
,属性后⾯带
.keyword
代表了精确匹配。
QueryBuilder
我们也可以使⽤
QueryBuilder
来构建分⻚查询,
QueryBuilder
是⼀个功能强⼤的多条件查询构建⼯具,可以
使⽤
QueryBuilder
构建出各种各样的查询条件。
@Test
public void fetchPage2Customers() {
QueryBuilder customerQuery = QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("address", "北京"));
Page<Customer> page = repository.search(customerQuery, PageRequest.of(0, 10));
System.out.println("Page customers "+page.getContent().toString());
}
使⽤
QueryBuilder
可以构建多条件查询,再结合
PageRequest
最后使⽤
search()
⽅法完成分⻚查询。
BoolQueryBuilder
有⼀些关键字和
AND
、
OR
、
NOT
⼀⼀对应:
- must(QueryBuilders):AND
- mustNot(QueryBuilders):NOT
- should::OR
QueryBuilder
是⼀个强⼤的多条件构建⼯具,有以下⼏种⽤法。
精确查询
单个匹配:
//不分词查询 参数1: 字段名,参数2:字段查询值,因为不分词,所以汉字只能查询⼀个字,英语是⼀个单词
QueryBuilder queryBuilder=QueryBuilders.termQuery("fieldName", "fieldlValue");
//分词查询,采⽤默认的分词器
QueryBuilder queryBuilder2 = QueryBuilders.matchQuery("fieldName", "fieldlValue");
多个匹配:
//不分词查询,参数1:字段名,参数2:多个字段查询值,因为不分词,因此汉字只能查询⼀个字,英语是⼀个
单词
QueryBuilder queryBuilder=QueryBuilders.termsQuery("fieldName", "fieldlValue1","fi
eldlValue2...");
//分词查询,采⽤默认的分词器
QueryBuilder queryBuilder= QueryBuilders.multiMatchQuery("fieldlValue", "fieldName
1", "fieldName2", "fieldName3");
//匹配所有⽂件,相当于就没有设置查询条件
QueryBuilder queryBuilder=QueryBuilders.matchAllQuery();
模糊查询
模糊查询常⻅的
5
个⽅法如下:
//1.常⽤的字符串查询
QueryBuilders.queryStringQuery("fieldValue").field("fieldName");//左右模糊
//2.常⽤的⽤于推荐相似内容的查询
QueryBuilders.moreLikeThisQuery(new String[] {"fieldName"}).addLikeText("pipeidhua
");//如果不指定filedName,则默认全部,常⽤在相似内容的推荐上
//3.前缀查询,如果字段没分词,就匹配整个字段前缀
QueryBuilders.prefixQuery("fieldName","fieldValue");
//4.fuzzy query:分词模糊查询,通过增加 fuzziness 模糊属性来查询,如能够匹配 hotelName 为 te
l 前或后加⼀个字⺟的⽂档,fuzziness 的含义是检索的 term 前后增加或减少 n 个单词的匹配查询
QueryBuilders.fuzzyQuery("hotelName", "tel").fuzziness(Fuzziness.ONE);
//5.wildcard query:通配符查询,⽀持* 任意字符串;?任意⼀个字符
QueryBuilders.wildcardQuery("fieldName","ctr*");//前⾯是fieldname,后⾯是带匹配字符的字
符串
QueryBuilders.wildcardQuery("fieldName","c?r?");
范围查询
//闭区间查询
QueryBuilder queryBuilder0 = QueryBuilders.rangeQuery("fieldName").from("fieldValu
e1").to("fieldValue2");
//开区间查询
QueryBuilder queryBuilder1 = QueryBuilders.rangeQuery("fieldName").from("fieldValu
e1").to("fieldValue2").includeUpper(false).includeLower(false);//默认是 true,也就是
包含
//⼤于
QueryBuilder queryBuilder2 = QueryBuilders.rangeQuery("fieldName").gt("fieldValue"
);
//⼤于等于
QueryBuilder queryBuilder3 = QueryBuilders.rangeQuery("fieldName").gte("fieldValue
");
//⼩于
QueryBuilder queryBuilder4 = QueryBuilders.rangeQuery("fieldName").lt("fieldValue"
);
//⼩于等于
QueryBuilder queryBuilder5 = QueryBuilders.rangeQuery("fieldName").lte("fieldValue
");
多条件查询
QueryBuilders.boolQuery()
QueryBuilders.boolQuery().must();//⽂档必须完全匹配条件,相当于 and
QueryBuilders.boolQuery().mustNot();//⽂档必须不匹配条件,相当于 not
聚合查询
聚合查询分为五步来实现,我们以统计客户总年龄为例进⾏演示。
第⼀步,使⽤
QueryBuilder
构建查询条件:
QueryBuilder customerQuery = QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("address", "北京"));
第⼆步,使⽤
SumAggregationBuilder
指明需要聚合的字段:
SumAggregationBuilder sumBuilder = AggregationBuilders.sum("sumAge").field("age");
第三步,以前两部分的内容为参数构建成
SearchQuery
:
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(customerQuery)
.addAggregation(sumBuilder)
.build();
第四步,使⽤
Aggregations
进⾏查询:
Aggregations aggregations = elasticsearchTemplate.query(searchQuery, new ResultsEx
tractor<Aggregations>() {
@Override
public Aggregations extract(SearchResponse response) {
return response.getAggregations();
}
});
第五步,解析聚合查询结果:
//转换成 map 集合
Map<String, Aggregation> aggregationMap = aggregations.asMap();
//获得对应的聚合函数的聚合⼦类,该聚合⼦类也是个 map 集合,⾥⾯的 value 就是桶 Bucket,我们要获
得 Bucket
InternalSum sumAge = (InternalSum) aggregationMap.get("sumAge");
System.out.println("sum age is "+sumAge.getValue());
以上就是聚合查询的使⽤⽅式。
总结
Spring Boot
对
ElasticSearch
的集成延续了
Spring Data
的思想,通过继承对应的
repository
默认帮我们实
现了很多常⽤操作,通过注解也⾮常的⽅便设置索引映射在
ElasticSearch
的数据使⽤。在⼤规模搜索中使
⽤
Spring Boot
操作
ElasticSearch
是⼀个最佳的选择。