第 4-8 课:Spring Boot 集成 ElasticSearch

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 是⼀个最佳的选择。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值