目前市面上有两类客户端
一类是TransportClient 为代表的ES原生客户端,不能执行原生dsl语句必须使用它的Java api方法。
另外一种是以Rest Api为主的missing client,最典型的就是jest。 这种客户端可以直接使用dsl语句拼成的字符串,直接传给服务端,然后返回json字符串再解析。
两种方式各有优劣,但是最近elasticsearch官网,宣布计划在7.0以后的版本中废除TransportClient。以RestClient为主。
由于原生的Elasticsearch客户端API非常麻烦。所以这里直接学习Spring提供的套件:Spring Data Elasticsearch。
spring-data-Elasticsearch 使用之前,必须先确定版本,elasticsearch 对版本的要求比较高。
4.1. 创建module
在gmall工程下创建一个模块:
引入依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> <exclusions> <exclusion> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> </exclusion> <exclusion> <groupId>org.elasticsearch.client</groupId> <artifactId>transport</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> <version>6.8.1</version> </dependency> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>transport</artifactId> <version>6.8.1</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> </dependency>
在application.properties中添加配置
spring.elasticsearch.rest.uris=http://172.16.116.100:9200 # 集群情况下 spring.elasticsearch.rest.uris[0]=http://172.16.116.100:9200 spring.elasticsearch.rest.uris[1]=http://172.16.116.100:9200
4.2. 实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "user", type = "info", shards = 3, replicas = 2)
public class User {
@Id
private Long id;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String name;
@Field(type = FieldType.Integer)
private Integer age;
@Field(type = FieldType.Keyword)
private String password;
} Spring Data通过注解来声明字段的映射属性,有下面的三个注解:
-
@Document作用在类,标记实体类为文档对象,一般有四个属性-
indexName:对应索引库名称
-
type:对应在索引库中的类型
-
shards:分片数量,默认5
-
replicas:副本数量,默认1
-
-
@Id作用在成员变量,标记一个字段作为id主键 -
@Field作用在成员变量,标记为文档的字段,并指定字段映射属性:-
type:字段类型,取值是枚举:FieldType
-
index:是否索引,布尔类型,默认是true
-
store:是否存储,布尔类型,默认是false
-
analyzer:分词器名称:ik_max_word
-
4.3. 创建索引及映射
@SpringBootTest
class EsDemoApplicationTests {
// ElasticsearchTemplate是TransportClient客户端
// ElasticsearchRestTemplate是RestHighLevel客户端
@Autowired
ElasticsearchRestTemplate restTemplate;
@Test
void contextLoads() {
// 创建索引
this.restTemplate.createIndex(User.class);
// 创建映射
this.restTemplate.putMapping(User.class);
// 删除索引
// this.restTemplate.deleteIndex("user");
}
} 4.4. Repository文档操作
Spring Data 的强大之处,就在于你不用写任何DAO处理,自动根据方法名或类的信息进行CRUD操作。只要你定义一个接口,然后继承Repository提供的一些子接口,就能具备各种基本的CRUD功能。
其中ElasticsearchRepository接口功能最强大。该接口的方法包括:
4.4.1. 新增
@Autowired
UserRepository userRepository;
@Test
void testAdd(){
this.userRepository.save(new User(1l, "zhang3", 20, "123456"));
} 修改和新增是同一个接口,区分的依据就是id,这一点跟我们在页面发起PUT请求是类似的。
4.4.2. 删除
@Test
void testDelete(){
this.userRepository.deleteById(1l);
} 4.5. 查询
4.5.1. 基本查询
查询一个:
@Test
void testFind(){
System.out.println(this.userRepository.findById(1l).get());
} 4.5.2. 条件查询
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}}}} |
准备一组数据:
@Test
void testAddAll(){
List<User> users = new ArrayList<>();
users.add(new User(1l, "柳岩", 18, "123456"));
users.add(new User(2l, "范冰冰", 19, "123456"));
users.add(new User(3l, "李冰冰", 20, "123456"));
users.add(new User(4l, "锋哥", 21, "123456"));
users.add(new User(5l, "小鹿", 22, "123456"));
users.add(new User(6l, "韩红", 23, "123456"));
this.userRepository.saveAll(users);
} 在UserRepository中定义一个方法:
第一种写法:
public interface UserRepository extends ElasticsearchRepository<User, Long> {
/**
* 根据年龄区间查询
* @param age1
* @param age2
* @return
*/
List<User> findByAgeBetween(Integer age1, Integer age2);
} 测试:
@Test
void testFindByAgeBetween(){
System.out.println(this.userRepository.findByAgeBetween(20, 30));
} 第二种写法:
@Query("{\n" +
" \"range\": {\n" +
" \"age\": {\n" +
" \"gte\": \"?0\",\n" +
" \"lte\": \"?1\"\n" +
" }\n" +
" }\n" +
" }")
List<User> findByQuery(Integer age1, Integer age2); 测试:
@Test
void testFindByQuery(){
System.out.println(this.userRepository.findByQuery(20, 30));
} 4.5.3. 自定义查询
@Test
void testNative(){
// 初始化自定义查询对象
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 构建查询
queryBuilder.withQuery(QueryBuilders.matchQuery("name", "冰冰"));
// 排序
queryBuilder.withSort(SortBuilders.fieldSort("age").order(SortOrder.ASC));
// 分页
queryBuilder.withPageable(PageRequest.of(0, 2));
// 高亮
queryBuilder.withHighlightBuilder(new HighlightBuilder().field("name").preTags("<em>").postTags("</em>"));
// 执行查询,获取分页结果集
Page<User> userPage = this.userRepository.search(queryBuilder.build());
// 总页数
System.out.println(userPage.getTotalPages());
// 总记录数
System.out.println(userPage.getTotalElements());
// 当前页数据
System.out.println(userPage.getContent());
} NativeSearchQueryBuilder:Spring提供的一个查询条件构建器,帮助构建json格式的请求体
Page<item>:默认是分页查询,因此返回的是一个分页的结果对象,包含属性:
-
totalElements:总条数
-
totalPages:总页数
-
Iterator:迭代器,本身实现了Iterator接口,因此可直接迭代得到当前页的数据

京公网安备 11010502036488号