1. Spring Data ElasticSearch简介
1.1. 什么是Spring Data
spring-boot-starter-data-redis > redisTemplate Spring Data 是一个用于简化数据访问,并支持云服务的开源框架。其主要目标是使得对数据的访问变得方便快捷。 Spring Data可以极大的简化数据操作的写法,可以在几乎不用写实现的情况下,实现对数据的访问和操作。除了CRUD外,还包括如分页、排序等一些常用的功能。 Spring Data的官网
1.2. 什么是SpringDataES
SpringDataElasticsearch(后面简称SDE)是Spring Data项目下的一个子模块,是Spring提供的操作ElasticSearch的数据层,封装了大量的基础操作,通过它可以很方便的操作ElasticSearch的数据。 Spring Data 的使命是给各种数据访问提供统一的编程接口,不管是关系型数据库(如MySQL),还是非关系数据库(如Redis),或者类似Elasticsearch这样的索引数据库。从而简化开发人员的代码,提 高开发效率。 包含很多不同数据操作的模块: Spring Data Elasticsearch的页面 特征:
支持Spring的基于@Configuration的java配置方式,或者XML配置方式提供了用于操作ES的便捷工具类ElasticsearchTemplate。包括实现文档到POJO之间的自动智能映射。利用Spring的数据转换服务实现的功能丰富的对象映射基于注解的元数据映射方式,而且可扩展以支持更多不同的数据格式根据持久层接口自动生成对应实现方法,无需人工编写基本操作代码(类似mybatis,根据接口自动得到实现)。当然,也支持人工定制查询
2. 准备工作
2.1. 创建spring操作es的父工程
父工程,删除src目录
2.2. 创建spring data es工程
导入依赖 配置信息
server:
port: 8081
spring:
application:
name: spring
-data
-es
data:
elasticsearch:
cluster-name: elasticsearch
cluster-nodes: 127.0.0.1
:9300
需要注意的是,SpringDataElasticsearch底层使用的不是Elasticsearch提供的RestHighLevelClient,而是TransportClient,并不采用Http协议通信,而是访问elasticsearch对外开放的tcp端口,ElasticSearch默认tcp端口。 另外,SpringBoot已经帮我们配置好了各种SDE配置,并且注册了一个ElasticsearchTemplate供我们使用。接下来一起来试试吧。
3. 索引库操作
3.1. 新建实体类Goods
作为与索引库对应的文档,通过实体类上的注解来配置索引库信息的,比如:索引库名、类型名、分片、副本数量、还有映射信息;
package com
.test
.pojo
;
import lombok
.AllArgsConstructor
;
import lombok
.Data
;
import lombok
.NoArgsConstructor
;
import org
.springframework
.data
.annotation
.Id
;
import org
.springframework
.data
.elasticsearch
.annotations
.Document
;
import org
.springframework
.data
.elasticsearch
.annotations
.Field
;
import org
.springframework
.data
.elasticsearch
.annotations
.FieldType
;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName
= "goods",type
= "goods")
public class Goods {
@Id
private Long id
;
@Field(type
= FieldType
.Text
,analyzer
= "ik_max_word")
private String title
;
@Field(type
= FieldType
.Keyword
)
private String category
;
@Field(type
= FieldType
.Keyword
)
private String brand
;
@Field(type
= FieldType
.Double
)
private Double price
;
@Field(type
= FieldType
.Keyword
,index
= false)
private String images
;
}
几个用到的注解:
@Document:声明索引库配置 indexName:索引库名称 type:类型名称,默认是“docs” shards:分片数量,默认5 replicas:副本数量,默认1@Id:声明实体类的id@Field:声明字段属性 type:字段的数据类型 analyzer:指定分词器类型 index:是否创建索引 默认为true store:是否存储 默认为false
3.2. 创建索引库
package com
.test
;
import com
.test
.pojo
.Goods
;
import org
.junit
.Test
;
import org
.junit
.runner
.RunWith
;
import org
.springframework
.beans
.factory
.annotation
.Autowired
;
import org
.springframework
.boot
.test
.context
.SpringBootTest
;
import org
.springframework
.data
.elasticsearch
.core
.ElasticsearchTemplate
;
import org
.springframework
.test
.context
.junit4
.SpringRunner
;
@RunWith(SpringRunner
.class)
@SpringBootTest
public class SpringDataEsApplicationTests {
@Autowired
private ElasticsearchTemplate template
;
@Test
public void createIndex() {
boolean flag
= template
.createIndex(Goods
.class);
System
.out
.println("创建索引是否成功: " + flag
);
}
}
测试:
3.3. 创建映射
package com
.test
;
import com
.test
.pojo
.Goods
;
import org
.junit
.Test
;
import org
.junit
.runner
.RunWith
;
import org
.springframework
.beans
.factory
.annotation
.Autowired
;
import org
.springframework
.boot
.test
.context
.SpringBootTest
;
import org
.springframework
.data
.elasticsearch
.core
.ElasticsearchTemplate
;
import org
.springframework
.test
.context
.junit4
.SpringRunner
;
@RunWith(SpringRunner
.class)
@SpringBootTest
public class SpringDataEsApplicationTests {
@Autowired
private ElasticsearchTemplate template
;
@Test
public void testPutMapping() {
boolean flag
= template
.putMapping(Goods
.class);
System
.out
.println("创建映射是否成功: " + flag
);
}
}
测试:
3.4. 删除索引库
package com
.test
;
import com
.test
.pojo
.Goods
;
import org
.junit
.Test
;
import org
.junit
.runner
.RunWith
;
import org
.springframework
.beans
.factory
.annotation
.Autowired
;
import org
.springframework
.boot
.test
.context
.SpringBootTest
;
import org
.springframework
.data
.elasticsearch
.core
.ElasticsearchTemplate
;
import org
.springframework
.test
.context
.junit4
.SpringRunner
;
@RunWith(SpringRunner
.class)
@SpringBootTest
public class SpringDataEsApplicationTests {
@Autowired
private ElasticsearchTemplate template
;
@Test
public void delMapping() {
boolean flag
= template
.deleteIndex(Goods
.class);
System
.out
.println("删除索引库是否成功:" + flag
);
}
}
4. 使用ElasticsearchRepository对数据进行增删改
4.1. 新建 GoodRepository 接口
SDE的文档索引数据CRUD并没有封装在ElasticsearchTemplate中,而是有一个叫做ElasticsearchRepository的接口: 我们需要自定义接口,继承ElasticsearchRespository:
package com
.test
.repository
;
import com
.test
.pojo
.Goods
;
import org
.springframework
.data
.elasticsearch
.repository
.ElasticsearchRepository
;
public interface GoodsESRepository extends ElasticsearchRepository<Goods, Integer> {
}
执行springboot的main方法, 发现创建了 索引库和映射type
4.2. 创建单个文档
package com
.test
.repository
;
import com
.test
.pojo
.Goods
;
import org
.junit
.Test
;
import org
.junit
.runner
.RunWith
;
import org
.springframework
.beans
.factory
.annotation
.Autowired
;
import org
.springframework
.boot
.test
.context
.SpringBootTest
;
import org
.springframework
.test
.context
.junit4
.SpringRunner
;
@RunWith(SpringRunner
.class)
@SpringBootTest
public class GoodsESRepositoryTest {
@Autowired
private GoodsESRepository goodsESRepository
;
@Test
public void demo01() {
Goods goods
= new Goods(1L
, "华为手机1", "手机", "华为", 1999.0, "http://wwww.baidu.com");
goodsESRepository
.save(goods
);
}
}
测试:
4.3. 批量创建文档
@Test
public void demo02() {
List
<Goods> goodsList
= new ArrayList<Goods>();
for (long i
= 2; i
< 10 ; i
++) {
Goods goods
= new Goods(i
, "小米手机" + i
, "手机", "小米", 1999.0 + 1000, "http://wwww.baidu.com");
goodsList
.add(goods
);
}
goodsESRepository
.saveAll(goodsList
);
}
4.4. 修改和删除
@Test
public void demo03() {
Optional
<Goods> optional
= goodsESRepository
.findById(2);
if (optional
.isPresent()) {
Goods goods
= optional
.get();
System
.out
.println(goods
);
}
}
@Test
public void demo04() {
Iterable
<Goods> it
= goodsESRepository
.findAll();
for (Goods goods
: it
) {
System
.out
.println(goods
);
}
}
@Test
public void demo05() {
QueryBuilder queryBuilder
= QueryBuilders
.termQuery("title", "华为");
Iterable
<Goods> it
= goodsESRepository
.search(queryBuilder
);
for (Goods goods
: it
) {
System
.out
.println(goods
);
}
}
5. 查询文档数据
默认提供了根据id查询, 查询所有两个功能
5.1. 根据id查询
调用goods仓库根据id查询判断返回 Optional 对象是有有值从Optional 对象中获取查询结果
@Test
public void demo03() {
Optional
<Goods> optional
= goodsESRepository
.findById(6);
if(optional
.isPresent()) {
Goods goods
= optional
.get();
System
.out
.println(goods
);
}
}
结果:
5.2. 查询所有
@Test
public void demo04() {
Iterable
<Goods> it
= goodsESRepository
.findAll();
for (Goods goods
: it
) {
System
.out
.println(goods
);
}
}
结果:
5.3. 使用search查询
构建QueryBuilder 对象设置查询类型和查询条件调用good仓库search方法进行查询遍历打印输出查询结果
@Test
public void demo05() {
QueryBuilder queryBuilder
= QueryBuilders
.termQuery("title", "华为");
Iterable
<Goods> it
= goodsESRepository
.search(queryBuilder
);
for (Goods goods
: it
) {
System
.out
.println(goods
);
}
}
结果:
5.4. 使用search查询并分页排序
构建Sort排序对象,指定排序字段和排序方式使用PageRequest构建Pageable分页对象,指定分页参数,并将排序对象设置到分页对象中调用goods仓库search方法进行查询解析结果
@Test
public void demo06() {
Sort sort
= new Sort(Sort
.Direction
.ASC
, "id");
Pageable pageable
= PageRequest
.of(0, 3, sort
);
Page
<Goods> page
= goodsESRepository
.search(QueryBuilders
.matchAllQuery(), pageable
);
long totalElements
= page
.getTotalElements();
System
.out
.println("总记录数: ===== " + totalElements
);
int totalPages
= page
.getTotalPages();
System
.out
.println("总页数: ===== " + totalPages
);
List
<Goods> goodsList
= page
.getContent();
for (Goods goods
: goodsList
) {
System
.out
.println("========== " + goods
);
}
}
结果:
6. 使用ElasticsearchTemplate查询
SDE也支持使用ElasticsearchTemplate进行原生查询
而查询条件的构建是通过一个名为NativeSearchQueryBuilder的类来完成的,不过这个类的底层还是使用的原生API中的QueryBuilders、HighlightBuilders等工具。
6.1. 分页和排序
可以通过NativeSearchQueryBuilder类来构建分页和排序、聚合等操作
queryBuilder.withQuery() //设置查询类型和查询条件
queryBuilder.withPageable() //设置分页
queryBuilder.withSort()//设置排序
构建NativeSearchQueryBuilder查询对象使用QueryBuilders指定查询类型和查询条件使用SortBuilders指定排序字段和排序方式使用PageRequest对象指定分页参数调用NativeSearchQueryBuilder的build方法完成构建使用ElasticsearchTemplate完成查询解析结果
package com
.test
.repository
;
import com
.test
.pojo
.Goods
;
import org
.elasticsearch
.index
.query
.QueryBuilders
;
import org
.elasticsearch
.search
.sort
.SortBuilders
;
import org
.elasticsearch
.search
.sort
.SortOrder
;
import org
.junit
.Test
;
import org
.junit
.runner
.RunWith
;
import org
.springframework
.beans
.factory
.annotation
.Autowired
;
import org
.springframework
.boot
.test
.context
.SpringBootTest
;
import org
.springframework
.data
.domain
.PageRequest
;
import org
.springframework
.data
.elasticsearch
.core
.ElasticsearchTemplate
;
import org
.springframework
.data
.elasticsearch
.core
.aggregation
.AggregatedPage
;
import org
.springframework
.data
.elasticsearch
.core
.query
.NativeSearchQuery
;
import org
.springframework
.data
.elasticsearch
.core
.query
.NativeSearchQueryBuilder
;
import org
.springframework
.test
.context
.junit4
.SpringRunner
;
@RunWith(SpringRunner
.class)
@SpringBootTest
public class GoodsESRepositoryTest {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate
;
@Test
public void nativeSearchQuery(){
NativeSearchQueryBuilder searchQueryBuilder
= new NativeSearchQueryBuilder();
searchQueryBuilder
.withQuery(QueryBuilders
.matchQuery("title","小米"));
searchQueryBuilder
.withSort(SortBuilders
.fieldSort("id").order(SortOrder
.DESC
));
searchQueryBuilder
.withPageable(PageRequest
.of(0,2));
NativeSearchQuery searchQuery
= searchQueryBuilder
.build();
AggregatedPage
<Goods> page
= elasticsearchTemplate
.queryForPage(searchQuery
, Goods
.class);
long totalElements
= page
.getTotalElements();
System
.out
.println("totalElements: " + totalElements
);
int totalPages
= page
.getTotalPages();
System
.out
.println("totalPages: " + totalPages
);
for (Goods goods
: page
) {
System
.out
.println("结果 : " + goods
);
}
}
}
6.2. 高亮显示
@Test
public void demo07() {
NativeSearchQueryBuilder nativeSearchQueryBuilder
= new NativeSearchQueryBuilder();
nativeSearchQueryBuilder
.withHighlightFields(new HighlightBuilder.Field("title").preTags("<font color='pink'>").postTags("</font>"));
nativeSearchQueryBuilder
.withQuery(QueryBuilders
.matchQuery("title","小米"));
nativeSearchQueryBuilder
.withSort(SortBuilders
.fieldSort("id").order(SortOrder
.ASC
));
nativeSearchQueryBuilder
.withPageable(PageRequest
.of(0,3));
NativeSearchQuery searchQuery
= nativeSearchQueryBuilder
.build();
AggregatedPage
<Goods> page
= template
.queryForPage(searchQuery
, Goods
.class, new SearchResultMapper() {
@Override
public <T> AggregatedPage
<T> mapResults(SearchResponse searchResponse
, Class
<T> aClass
, Pageable pageable
) {
ArrayList
<Goods> goodsList
= new ArrayList<>();
SearchHits hits
= searchResponse
.getHits();
if (hits
.getHits().length
<=0){
return null
;
}
for (SearchHit hit
: hits
) {
Goods goods
= new Goods();
goods
.setId(Long
.parseLong(hit
.getId()));
if (hit
.getSourceAsMap().get("category")!=null
){
goods
.setCategory(hit
.getSourceAsMap().get("category").toString());
}
if (hit
.getSourceAsMap().get("brand")!=null
){
goods
.setBrand(hit
.getSourceAsMap().get("brand").toString());
}
if (hit
.getSourceAsMap().get("price")!=null
){
goods
.setPrice(Double
.parseDouble(hit
.getSourceAsMap().get("price").toString()));
}
if (hit
.getHighlightFields().size()>0){
String highLightTitle
= hit
.getHighlightFields().get("title").fragments()[0].toString();
goods
.setTitle(highLightTitle
);
}
goodsList
.add(goods
);
}
return new AggregatedPageImpl<>((List
<T>) goodsList
);
}
});
long totalElements
= page
.getTotalElements();
int totalPages
= page
.getTotalPages();
System
.out
.println("总条数:" + totalElements
);
System
.out
.println("总页数" + totalPages
);
List
<Goods> goodsList
= page
.getContent();
for (Goods goods
: goodsList
) {
System
.out
.println(goods
);
}
}
}