- 整合Spring Data Elasticsearch
- 如何查看官方文档(了解)
官方文档:Java High Level REST Client | Java REST Client [6.8] | Elastic
下面是获得文档的方式(可以不用看):
- 步骤一:查询“文档”
- 步骤二:查看“客户端Client”文档
- 步骤三:查看基于REST的api
- 步骤四:确定使用的版本
- 步骤五:选择使用的API基本
- Low Level Rest Client是低级别封装,提供一些基础功能,但更灵活
- High Level Rest Client,是在Low Level Rest Client基础上进行的高级别封装,功能更丰富和完善
- 简介
Spring Data Elasticsearch是Spring Data项目下的一个子模块。
简化了原生的Elasticsearch的开发。
- 什么是spring data
查看 Spring Data的官网:http://projects.spring.io/spring-data/
Spring Data 是的使命是给各种数据访问提供统一的编程接口,不管是关系型数据库(如MySQL),还是非关系数据库(如Redis),或者类似Elasticsearch这样的索引数据库。从而简化开发人员的代码,提高开发效率。
包含很多不同数据操作的模块:
- 什么是spring data elasticsearch
Spring Data Elasticsearch的页面:https://projects.spring.io/spring-data-elasticsearch/
特征:
- 支持Spring的基于@Configuration的java配置方式
- 提供了用于操作ES的便捷工具类ElasticsearchTemplate。包括实现文档到POJO之间的自动智能映射。
- 利用Spring的数据转换服务实现的功能丰富的对象映射
- 基于注解的元数据映射方式,而且可扩展以支持更多不同的数据格式
- 根据持久层接口自动生成对应实现方法,无需人工编写基本操作代码(类似mybatis,根据接口自动得到实现)。当然,也支持人工定制查询
- 版本限定
https://docs.spring.io/spring-data/elasticsearch/docs/4.2.1/reference/html/#preface.versions
- 创建Demo工程
我们新建一个test-elasticsearch,学习Elasticsearch
- pom依赖:
- 配置工具类
Initialization | Java REST Client [6.8] | Elastic
package com.czxy.changgou4.config;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.RestClients;
import org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
/**
* @author 桐叔
* @email liangtong@itcast.cn
*/
@Configuration
@EnableElasticsearchRepositories
public class RestClientConfig extends AbstractElasticsearchConfiguration {
@Override
@Bean
public RestHighLevelClient elasticsearchClient() {
final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("localhost:9200")
.build();
return RestClients.create(clientConfiguration).rest();
}
}
- 启动类
package com.czxy.changgou4;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author 桐叔
* @email liangtong@itcast.cn
*/
@SpringBootApplication
public class TestESApplication {
public static void main(String[] args) {
SpringApplication.run(TestESApplication.class,args);
}
}
- 测试类
package com.czxy.changgou4;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author 桐叔
* @email liangtong@itcast.cn
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestESApplication.class)
public class TestES {
@Test
public void testDemo() {
System.out.println("....");
}
}
- 索引操作
- 创建索引和映射
- 实体类:首先我们准备好实体类:
public class Item {
private Long id;
private String title; //标题
private String category; //分类
private String brand; //品牌
private Double price; //价格
private String images; //图片地址
}
- 映射
Spring Data通过注解来声明字段的映射属性,有下面的三个注解:
- @Document 作用在类,标记实体类为文档对象,一般有两个属性
- indexName:对应索引库名称
- type:对应在索引库中的类型
- shards:分片数量,默认5
- replicas:副本数量,默认1
- @Id 作用在成员变量,标记一个字段作为id主键
- @Field 作用在成员变量,标记为文档的字段,并指定字段映射属性:
- type:字段类型,是是枚举:FieldType
- index:是否索引,布尔类型,默认是true
- store:是否存储,布尔类型,默认是false
- analyzer:分词器名称
- 示例:
@Document(indexName = "item",type = "docs", shards = 1, replicas = 0)
public class Item {
@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(index = false, type = FieldType.Keyword)
private String images; // 图片地址
}
- 创建索引
package com.czxy;
import com.czxy.changgou4.TestESApplication;
import com.czxy.changgou4.domain.Item;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
/**
* @author 桐叔
* @email liangtong@itcast.cn
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestESApplication.class)
public class TestES {
@Resource
private ElasticsearchRestTemplate elasticsearchTemplate;
@Test
public void testCreateIndex() {
elasticsearchTemplate.createIndex(Item.class);
}
}
- 可以根据类的信息自动生成,也可以手动指定indexName和Settings
映射
映射相关的API:
- 一样,可以根据类的字节码信息(注解配置)来生成映射,或者手动编写映射
我们这里采用类的字节码信息创建索引并映射:
package com.czxy;
import com.czxy.pojo.Item;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
/**
* Created by liangtong.
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ESApplication.class)
public class TestES {
@Resource
private ElasticsearchTemplate elasticsearchTemplate;
@Test
public void createIndex(){
elasticsearchTemplate.createIndex(Item.class );
elasticsearchTemplate.putMapping(Item.class);
}
}
结果:
- 删除索引
删除索引的API:
可以根据类名或索引名删除。
示例:
@Test
public void deleteIndex() {
deleteIndex("item");
}
- 新增文档数据
- Repository接口
Spring Data 的强大之处,就在于你不用写任何DAO处理,自动根据方法名或类的信息进行CRUD操作。只要你定义一个接口,然后继承Repository提供的一些子接口,就能具备各种基本的CRUD功能。
来看下Repository的继承关系:
.
含Crud的接口表示已经完成增删改查操作,例如:ElasticsearchCrudRepository接口:
如果继承ElasticsearchRepository子接口,同时也继承了其父接口声明的所有功能。
所以,我们只需要定义接口,然后继承它就OK了。
public interface ItemRepository extends ElasticsearchRepository<Item,Long> {
}
接下来,我们测试新增数据:
- 新增一个对象
-
- 批量新增
代码:
@Test
public void indexList() {
- 修改
修改和新增是同一个接口,区分的依据就是id,这一点跟我们在页面发起PUT请求是类似的。
- 删除
@Test
public void testDelete() {
itemRepository.deleteById(1L);
}
- 查询
- 基本查询
ElasticsearchRepository提供了一些基本的查询方法:
我们来试试查询所有:
@Test
public void query(){
// 查询全部,并安装价格降序排序
Iterable<Item> items = this.itemRepository.findAll(Sort.by("price").descending());
for (Item item : items) {
out.println("item = "
}
}
结果:
- 自定义方法
Spring Data 的另一个强大功能,是根据方法名称自动实现功能。
比如:你的方法名叫做:findByTitle,那么它就知道你是根据title查询,然后自动帮你完成,无需写实现类。
当然,方法名称要符合一定的约定:
Keyword | Sample | Elasticsearch Query String |
And | findByNameAndPrice | {"bool" : {"must" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}} |
Or | findByNameOrPrice | {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}} |
Is | findByName | {"bool" : {"must" : {"field" : {"name" : "?"}}}} |
Not | findByNameNot | {"bool" : {"must_not" : {"field" : {"name" : "?"}}}} |
Between | findByPriceBetween | {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
LessThanEqual | findByPriceLessThan | {"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
GreaterThanEqual | findByPriceGreaterThan | {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}} |
Before | findByPriceBefore | {"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
After | findByPriceAfter | {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}} |
Like | findByNameLike | {"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}} |
StartingWith | findByNameStartingWith | {"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}} |
EndingWith | findByNameEndingWith | {"bool" : {"must" : {"field" : {"name" : {"query" : "*?","analyze_wildcard" : true}}}}} |
Contains/Containing | findByNameContaining | {"bool" : {"must" : {"field" : {"name" : {"query" : "**?**","analyze_wildcard" : true}}}}} |
In | findByNameIn(Collection<String>names) | {"bool" : {"must" : {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}} |
NotIn | findByNameNotIn(Collection<String>names) | {"bool" : {"must_not" : {"bool" : {"should" : {"field" : {"name" : "?"}}}}}} |
Near | findByStoreNear | Not Supported Yet ! |
True | findByAvailableTrue | {"bool" : {"must" : {"field" : {"available" : true}}}} |
False | findByAvailableFalse | {"bool" : {"must" : {"field" : {"available" : false}}}} |
OrderBy | findByAvailableTrueOrderByNameDesc | {"sort" : [{ "name" : {"order" : "desc"} }],"bool" : {"must" : {"field" : {"available" : true}}}} |
例如,我们来按照价格区间查询,定义这样的一个方法:
public interface ItemRepository extends ElasticsearchRepository<Item,Long> {
/**
* 根据价格区间查询
* @param price1
* @param price2
* @return
*/
findByPriceBetween(double price1, double
}
然后添加一些测试数据:
@Test
不需要写实现类,然后我们直接去运行:
@Test
结果:
- 自定义查询
先来看最基本的match query:
@Test
public void search(){
// 构建查询条件
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 添加基本分词查询
withQuery(QueryBuilders.matchQuery("title", "小米手机"));
// 搜索,获取结果
Page<Item> items = this.itemRepository.search(queryBuilder.build());
// 总条数
long total = items.getTotalElements();
out.println("total = "
for (Item item : items) {
out.println(item);
}
}
- NativeSearchQueryBuilder:Spring提供的一个查询条件构建器,帮助构建json格式的请求体
- QueryBuilders.matchQuery("title", "小米手机"):利用QueryBuilders来生成一个查询。QueryBuilders提供了大量的静态方法,用于生成各种不同类型的查询:
- Page<item>:默认是分页查询,因此返回的是一个分页的结果对象,包含属性:
- totalElements:总条数
- totalPages:总页数
- Iterator:迭代器,本身实现了Iterator接口,因此可直接迭代得到当前页的数据
- 其它属性:
结果:
- 练习1:查询标题中含“手机”,且品牌是“小米”的商品列表信息
@Test
public void testQuery2() {
//1 条件构建器
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
//2 拼凑条件
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(QueryBuilders.matchQuery("title","手机"));
boolQueryBuilder.must(QueryBuilders.matchQuery("brand","小米"));
queryBuilder.withQuery(boolQueryBuilder);
//3 查询
Page<Item> page = this.itemRepository.search(queryBuilder.build());
//4 处理结果
System.out.println(page.getTotalElements());
page.getContent().forEach(System.out::println);
}
- 练习2:查询标题中含“手机”,且品牌不是“小米”的商品列表信息
- 分页查询
利用NativeSearchQueryBuilder可以方便的实现分页:
结果:
可以发现,Elasticsearch中的分页是从第0页开始。
- 排序
排序也通用通过NativeSearchQueryBuilder完成:
结果: