ElasticSearch从入门到精通–第五话(整合SpringBoot高效开发、分页高亮等、Kibana使用篇)
ElasticSearch从入门到精通–第一话(入门篇)
ElasticSearch从入门到精通–第二话(原生API调用–纯代码篇)
ElasticSearch从入门到精通–第三话(集群环境搭建篇)
ElasticSearch从入门到精通–第四话(核心概念篇)
ElasticSearch从入门到精通–第五话(整合SpringBoot高效开发、分页高亮等、Kibana使用篇)
Kibana
Kibana是一个开源的图形化管理界面,能够对Elasticsearch的数据进行可视化管理,且可在Elastic Stack中进行导航,可以完成各种操作,从跟踪查询负载,到理解请求如何流经应用都能轻松完成。
windows版7.3.0版本kibana下载(最好和es版本相同的)
-
下载好后,解压(解压了2个多小时。。。)
-
修改config/kibana.yml文件
# 服务端口 server.port: 5601 # ES服务器/集群的地址 elasticsearch.hosts: ["http://localhost:1001","http://localhost:1002","http://localhost:1003"] # 索引名 kibana.index: ".kibana" # 编码格式 i18n.locale: "zh-CN"
-
启动bin下的执行文件
-
访问http://localhost:5601
可到Console控制台中,直接通过json接口处理数据
整合Spring Data
SpringData是一个用于简化数据库、非关系数据库、索引库访问,支持云服务的开源框架。Spring Data 的使命是给各种数据访问提供统一的编程接口,不管是关系型数据库(如MySQL),还是非关系数据库(如Redis),或者类似Elasticsearch这样的索引数据库。从而简化开发人员的代码,提高开发效率。
我们要使用的就是 Spirng Data Elasticsearch模块项目。
项目特性:
- 支持Spring的基于@Configuration的java配置方式,或者XML配置方式
- 提供了用于操作ES的便捷工具类ElasticsearchTemplate。包括实现文档到POJO之间的自动智能映射。
利用Spring的数据转换服务实现的功能丰富的对象映射 - 基于注解的元数据映射方式,而且可扩展以支持更多不同的数据格式
- 根据持久层接口自动生成对应实现方法,无需人工编写基本操作代码(类似mybatis,根据接口自动得到实现)。当然,也支持人工定制查询
那么大概也看了一下,整合SpringData后,可以使用几种方式去操作es索引,都可以使用,但是有些方法已经过时了,建议通常情况下涉及复杂业务查询时,还是使用自定义方法查询
或原生API查询(代码较多,未封装)
,有几种方式可使用(注意:引入spring-boot-starter-data-elasticsearch
后,这几种方式都可以用的):
- 使用原生API,
RestHighLevelClient
高级客户端,但是使用起来还是有些复杂的 - 使用springdata提供的模板工具类
ElasticsearchRestTemplate
,这个直接引入就可以了(是spring容器中的组件),对这个工具类了解不多,有兴趣可以查阅资料 - (
个人推荐使用
)使用继承自ElasticsearchRepository
的接口类,ElasticsearchRepository
类提供了一些常用的增删改查,这些增删改查的操作是指针对文档
数据的,若要对索引操作,还是用上面两种方法;且我们可以在ElasticsearchRepository
子接口中,像使用jpa一样,自定义方法名且不需要具体实现,springdata会帮我们自动生成实现内容(开发效率非常高,像复杂查询、高亮、分页都可以使用这种方式实现,非常好用)
集成前,注意版本
创建一个项目,然后引入相关依赖pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.6.RELEASE</version>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--lombock工具-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--spring data elasticsearch-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
</dependencies>
做些配置,application.yml
文件
# es相关
elasticsearch:
host: 127.0.0.1
port: 9200
# 日志相关
logging:
level:
com.wlh.es: info
然后创建一个数据实体类,通过注解方式进行java类和es索引数据的双向关联
@Data
@NoArgsConstructor
@AllArgsConstructor
/**
* indexName:索引名称
* shards:分片数量
* replicas:副本数,默认1
*/
@Document(indexName = "shopping", shards = 3, replicas = 1)
public class Product {
// 标明是id主键
@Id
private Long id;
/**
* 标明是文档中的字段field
* type:设置字段的映射类型是text类型,那么会被分词器拆解词条
* analyzer:设置使用的分词器
* index:是否能被索引查询
*/
@Field(type = FieldType.Text)
private String title;
// 设置为Keyword类型,不会被分词,是作为一个整体存在
@Field(type = FieldType.Keyword)
private String category;
// 浮点数类型
@Field(type = FieldType.Double)
private Double price;
// Keyword类型,且不能被索引查询
@Field(type = FieldType.Keyword, index = false)
private String images;
}
搞个es的config配置类做个配置
@Configuration
@ConfigurationProperties(prefix = "elasticsearch")
@Data
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration {
private String host;
private Integer port;
@Override
public RestHighLevelClient elasticsearchClient() {
return new RestHighLevelClient(RestClient.builder(new HttpHost(host, port)));
}
}
创建个数据访问对象,可直接继承spring data es提供的,然后直接用里面的定义好的访问方法即可。
@Repository
// 有两个泛型,第一是要操作的数据对象类,第二个是序列化的id
public interface ProductDao extends ElasticsearchRepository<Product, Long> {
}
测试执行下试试
索引
创建索引
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringDataTest {
@Autowired
private ElasticsearchRestTemplate template;
@Test
public void createIndex() {
// 当项目启动时,索引如果不存在则会被自动创建
System.out.println("索引被创建啦");
}
}
通常项目启动时,会去找数据索引类,然后去es服务器看看有没有索引,没有的话会自动创建一个索引,执行下试试。
执行完毕
然后去kibana
看看创建的索引
已经创建完毕啦。
删除索引
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringDataTest {
@Autowired
private ElasticsearchRestTemplate template;
@Test
public void deleteIndex() {
template.deleteIndex(Product.class);
System.out.println("删除索引啦");
}
}
deleteIndex()方法是一个过时方法了,有兴趣可以自行查阅资料。
文档
新增文档
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringDocTest {
@Autowired
private ProductDao productDao;
/**
* 新增文档
*/
@Test
public void save() {
Product product = new Product(1L, "小米手机", "手机", 2999.99, "http://localhost/a.jpeg");
productDao.save(product);
}
}
执行完毕,到kibana看下数据情况
修改文档
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringDocTest {
@Autowired
private ProductDao productDao;
/**
* 修改文档
* 实际只要是和文档有相同id的,继续调用save方法,即可完成修改
*/
@Test
public void update() {
Product product = new Product(1L, "华为的手机", "手机", 3999.99, "http://localhost/a.jpeg");
productDao.save(product);
}
}
执行下,到kibana看看,数据已经变更了
查询文档
/**
* 根据id查询
*/
@Test
public void getById() {
Product product = productDao.findById(1L).get();
System.out.println(product);
}
/**
* 查所有
*/
@Test
public void findAll() {
Iterable<Product> all = productDao.findAll();
for (Product product : all) {
System.out.println(product);
}
}
Product(id=1, title=华为的手机, category=手机, price=3999.99, images=http://localhost/a.jpeg)
.......
Product(id=1, title=华为的手机, category=手机, price=3999.99, images=http://localhost/a.jpeg)
删除文档
@Test
public void delete() {
Product product = new Product();
product.setId(1L);
productDao.delete(product);
}
批量新增
/**
* 批量新增
*/
@Test
public void saveBatch() {
List<Product> products = new ArrayList<>();
for (int i = 0; i < 7; i++) {
Product product = new Product((long) i, "【"+i+"】华为手机", "手机", 2000.00 + i, "http://localhost/a.jpeg");
products.add(product);
}
productDao.saveAll(products);
}
新增完,查询一下看看
排序+分页查询
/**
* 排序+分页查询
*/
@Test
public void findByPageable() {
// 设置排序(根据id升序排序)
Sort sort = Sort.by(Sort.Direction.ASC, "id");
int page = 0;
int size = 5;
PageRequest pageRequest = PageRequest.of(page, size, sort);
Page<Product> all = productDao.findAll(pageRequest);
all.getContent().forEach(System.out::println);
}
结果排序+分页
Product(id=0, title=【0】华为手机, category=手机, price=2000.0, images=http://localhost/a.jpeg)
Product(id=1, title=【1】华为手机, category=手机, price=2001.0, images=http://localhost/a.jpeg)
Product(id=2, title=【2】华为手机, category=手机, price=2002.0, images=http://localhost/a.jpeg)
Product(id=3, title=【3】华为手机, category=手机, price=2003.0, images=http://localhost/a.jpeg)
Product(id=4, title=【4】华为手机, category=手机, price=2004.0, images=http://localhost/a.jpeg)
注意:上面的page为0表示是查询第一页,如果要查询第二页数据,那么page=1
自定义查询
继承自ElasticsearchRepository
类后,能继承一些基本的索引、文档的操作,但是涉及到复杂查询时,我们就需要去自定义一些查询了
自定义查询的方式挺多,简单介绍下类似jpa方式的查询,在继承ElasticsearchRepository的子接口中
,定义相关的方法即可。
条件查询
并且在编写方法名称时,会自动提示,用起来嘎嘎爽。
接口层方法
@Repository
public interface ProductDao extends ElasticsearchRepository<Product, Long> {
List<Product> findByTitle(String title);
}
处理层
@Test
public void termQuery() {
List<Product> products = productDao.findByTitle("为华");
products.forEach(System.out::println);
}
结果
Product(id=5, title=【5】华为手机, category=手机, price=2005.0, images=http://localhost/a.jpeg)
Product(id=0, title=【0】华为手机, category=手机, price=2000.0, images=http://localhost/a.jpeg)
Product(id=2, title=【2】华为手机, category=手机, price=2002.0, images=http://localhost/a.jpeg)
Product(id=3, title=【3】华为手机, category=手机, price=2003.0, images=http://localhost/a.jpeg)
Product(id=4, title=【4】华为手机, category=手机, price=2004.0, images=http://localhost/a.jpeg)
Product(id=1, title=【1】华为手机, category=手机, price=2001.0, images=http://localhost/a.jpeg)
Product(id=6, title=【6】华为手机, category=手机, price=2006.0, images=http://localhost/a.jpeg)
并且有分词器的存在,查询时,虽然是写的为华
,但是查询时,会将为华
拆解为为、华
,然后去倒排表中查询,所有匹配到的数据都会被查询出来。
条件+分页+排序查询
接口层
Page<Product> findByTitleOrderByIdAsc(String title, Pageable pageable);
处理层
要注意:es这边分页查询时,页数从0开始的
@Test
public void query() {
PageRequest pageRequest = PageRequest.of(1, 5);
Page<Product> order = productDao.findByTitleOrderByIdAsc("华", pageRequest);
order.forEach(System.out::println);
}
结果
Product(id=5, title=【5】华为手机, category=手机, price=2005.0, images=http://localhost/a.jpeg)
Product(id=6, title=【6】华为手机, category=手机, price=2006.0, images=http://localhost/a.jpeg)
过多不再介绍,遇到复杂的业务场景时,可以查阅官方文档
高亮查询❤
这个处理起来也比较简单了,先给个官方地址吧,方便查阅,高亮查询注解版
接口层,一个@Highligh注解搞定,注意配置高亮标签参数
@Highlight(fields = {
@HighlightField(name = "title")},
parameters =
@HighlightParameters(preTags = "<strong><font style='color:red'>", postTags = "</font></strong>", fragmentSize = 500, numberOfFragments = 3)
)
SearchPage<Product> findByTitleOrderByIdAsc(String title, Pageable pageable);
处理层
@Test
public void query() {
PageRequest pageRequest = PageRequest.of(1, 5);
SearchPage<Product> productSearchPage = productDao.findByTitleOrderByIdAsc("华", pageRequest);
productSearchPage.getSearchHits().forEach(System.out::println);
}
高亮字段在highlightFields中
SearchHit{id='5', score=NaN, sortValues=[5], content=Product(id=5, title=【5】华为手机, category=手机, price=2005.0, images=http://localhost/a.jpeg), highlightFields={title=[【5】<font style='color:red'>华</font>为手机]}}
SearchHit{id='6', score=NaN, sortValues=[6], content=Product(id=6, title=【6】华为手机, category=手机, price=2006.0, images=http://localhost/a.jpeg), highlightFields={title=[【6】<font style='color:red'>华</font>为手机]}}
如果查询时,不进行分页,那么Dao这么写
@SuppressWarnings("SpringDataRepositoryMethodReturnTypeInspection") // 压制警告
@Highlight(fields = {
@HighlightField(name = "title")},
parameters =
@HighlightParameters(preTags = "<font style='color:red'>", postTags = "</font>", fragmentSize = 500, numberOfFragments = 3)
)
List<SearchHit<Product>> findByTitle(String title);
// 或这么写
SearchHits<Product> findByTitle(String title);