我的ElasticSearch 版本是5.6.16
一. pom依赖
鉴于可能有版本冲突问题,我全贴上来。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.boot</groupId>
<artifactId>es</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>es</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
二. Spring-data-elasticsearch 都提供了什么
1. 方便的注解,让创建索引更简单
例如下面创建User的索引,创建的索引名是user_index,类型是user,主键是no, name和word两个字段使用ik_max_word分词器进行分词。
package com.boot.es.model;
import lombok.Data;
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;
import org.springframework.data.elasticsearch.annotations.InnerField;
import org.springframework.data.elasticsearch.annotations.MultiField;
/**
* Author: susq
* Date: 2019-06-10 23:44
*/
@Data
@Document(indexName = "user_index", type = "user", replicas = 0)
public class User {
@Id
private Long no;
@Field(type = FieldType.Integer)
private int age;
@MultiField(
mainField = @Field(type = FieldType.Text, analyzer = "standard", searchAnalyzer = "standard", store = true),
otherFields = @InnerField(type = FieldType.Keyword, suffix = "raw"))
private String name;
@Field(type = FieldType.Text, analyzer = "standard", searchAnalyzer = "standard")
private String word;
}
这样创建索引的时候,直接根据类来创建就可以:
package com.boot.es;
import com.boot.es.model.User;
import com.boot.es.service.UserService;
import lombok.extern.slf4j.Slf4j;
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;
/**
* Author: susq
* Date: 2019-06-26 00:09
*/
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class EsServiceTests {
@Autowired
private ElasticsearchTemplate template;
@Autowired
private UserService userService;
@Test
public void rebuildIndex() {
template.createIndex(User.class);
log.info("user索引在吗" + template.indexExists(User.class));
template.deleteIndex(User.class);
log.info("user索引在吗" + template.indexExists(User.class));
}
}
对用到的注解做一下补充:
ublic @interface Document {
String indexName(); //索引库的名称
String type() default ""; //类型
short shards() default 5; //默认分区数
short replicas() default 1; //每个分区默认的备份数,我们上面设置0是因为是单机的,在同一台机器上备份没用,才设置了0, 否则通过elastic-head插件,会出现UNASSIGNED节点。 elasticsearch-head插件使用参考我之前的文章:https://blog.csdn.net/u013041642/article/details/91355916
String refreshInterval() default "1s"; //刷新间隔
String indexStoreType() default "fs"; //索引文件存储类型
boolean creatIndex() default true; // 是否给当前使用注解的对象自动创建索引
}
public @interface Field {
FieldType type() default FieldType.Auto; //自动检测属性的类型,可以根据实际情况自己设置
FieldIndex index() default true; //默认情况下分词,一般默认分词就好,除非这个字段你确定查询时不会用到,可以设置成false让它不分词
DateFormat format() default DateFormat.none; //时间类型的格式化
String pattern() default "";
boolean store() default false; //默认情况下不存储原文
String searchAnalyzer() default ""; //指定字段搜索时使用的分词器
String indexAnalyzer() default ""; //指定字段建立索引时指定的分词器
String[] ignoreFields() default {}; //如果某个字段需要被忽略
boolean includeInParent() default false;
}
2. 封装好的接口,简单的增删改查功能都已经实现,如果功能简单,我们几乎不需要自己写什么代码
package com.boot.es.repository;
import com.boot.es.model.User;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
// 两个泛型,第一个是索引对应的实体类User,第二个是主键的类型
@Repository
public interface UserRepository extends ElasticsearchRepository<User, Long> {
}
继承层次中,CrudRepository接口定义好了许多接口, 注释我删掉了,大家看源码的话有很多注释,比较简单。
@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S entity);
<S extends T> Iterable<S> saveAll(Iterable<S> entities);
Optional<T> findById(ID id);
boolean existsById(ID id);
Iterable<T> findAll();
Iterable<T> findAllById(Iterable<ID> ids);
long count();
void deleteById(ID id);
void delete(T entity);
void deleteAll(Iterable<? extends T> entities);
void deleteAll();
}
继承层次中,还有个接口, 如果我们要构建复杂的查询,可以自行拼装QueryBuilder,
public interface ElasticsearchRepository<T, ID extends Serializable> extends ElasticsearchCrudRepository<T, ID> {
<S extends T> S index(S entity);
Iterable<T> search(QueryBuilder query);
Page<T> search(QueryBuilder query, Pageable pageable);
Page<T> search(SearchQuery searchQuery);
Page<T> searchSimilar(T entity, String[] fields, Pageable pageable);
void refresh();
Class<T> getEntityClass();
}
3. 配合查询的QueryBuilder, 提供了丰富的静态方法,自由组合查询条件,下面列举了一些常用的。
这里使用的标准分词器,对汉字分词不友好,所以这里的name,word, 我都写成了英文形式,后面会专门整理一篇汉字和拼音分词器的。
package com.boot.es;
import com.boot.es.model.User;
import com.boot.es.repository.UserRepository;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.util.Lists;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.sort.SortBuilders;
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.query.NativeSearchQueryBuilder;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class EsApplicationTests {
@Autowired
private UserRepository userRepository;
@Test
public void testCurd() {
// 添加,可以saveAll批量添加
User user = new User();
user.setAge(26);
user.setName("zhangsan");
user.setNo(1L);
user.setWord("good good study!");
userRepository.save(user);
// 查询全部
List<User> users = Lists.newArrayList(userRepository.findAll());
log.info("users,{}", users);
// 更新就是重复保存,有变化的字段会覆盖老的
user.setWord("day day up!");
userRepository.save(user);
user = userRepository.findById(1L).orElse(null);
log.info("user,{}", user);
userRepository.deleteById(1L);
user = userRepository.findById(1L).orElse(null);
log.info("user,{}", user);
User user1 = new User();
user1.setAge(26);
user1.setName("zhang san");
user1.setNo(1L);
user1.setWord("good good study!");
User user2 = new User();
user2.setAge(36);
user2.setName("li si");
user2.setNo(2L);
user2.setWord("day day up!");
userRepository.saveAll(Lists.newArrayList(user1, user2));
// 查询全部
users = Lists.newArrayList(userRepository.findAll());
log.info("users,{}", users);
}
@Test
public void testSearch() {
// 空条件查询,返回全部
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
Iterable<User> users = userRepository.search(queryBuilder.build());
List<User> userList = Lists.newArrayList(users);
log.info("userList:{}", userList);
//=========================精确匹配查询==========================================================
// no等于1的,name用标准分词器分词后的倒排索引包含"li"的,name不分词时候等于"li si"的,也可以同时都组合起来,则查询条件就是要同时满足的
// queryBuilder.withQuery(QueryBuilders.termQuery("no", 1L));
// queryBuilder.withQuery(QueryBuilders.termQuery("name", "li"));
// queryBuilder.withQuery(QueryBuilders.termQuery("name.raw", "li si"));
queryBuilder.withQuery(QueryBuilders.termQuery("age", 26));
users = userRepository.search(queryBuilder.build());
userList = Lists.newArrayList(users);
log.info("userList:{}", userList);
//=============================检索==============================================================
queryBuilder.withQuery(QueryBuilders.matchQuery("name", "zhang")); // 检索name 中有"zhang"的
queryBuilder.withQuery(QueryBuilders.multiMatchQuery("good", "name", "word")); // 检索name 或者 word 中有"good"的
users = userRepository.search(queryBuilder.build());
userList = Lists.newArrayList(users);
log.info("userList:{}", userList);
/**
* 我们上面创建的索引,name,word两个字段都设置分词,name用汉字分词器分词,word用拼音分词器分词,
* termQuery() term过滤器会查找我们设定的准确值。如果有分词的话,term过滤器只能匹配到分词后的倒排索引。
* matchQuery()
* 1.被查字段是分词的,那么查询字符串也要被分词
* 2. 类似低级别的term查询
* 3. 综合考虑词频,逆文档频率,包含关键字的字段长度,来计算相关性打分_score
* multiMatchQuery() 可以多词查询,一段文本可以匹配多个字段,或的关系
* 在本文最后我也补充了例子,比较直观
*/
//=============================组合查询条件=======================================================
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("name", "zhang"))
.mustNot(QueryBuilders.matchQuery("name", "li"))
.should(QueryBuilders.matchQuery("word", "good"))
.should(QueryBuilders.matchQuery("word", "up"))
.minimumShouldMatch(1);
queryBuilder.withQuery(boolQueryBuilder);
users = userRepository.search(queryBuilder.build());
userList = Lists.newArrayList(users);
log.info("userList:{}", userList);
//=============================组合查询条件自定义每个条件的权重,按权重排序=============================
BoolQueryBuilder boolQueryBuilder1 = QueryBuilders.boolQuery()
.should(QueryBuilders.functionScoreQuery(QueryBuilders.matchQuery("word", "good")).boost(2))
.should(QueryBuilders.functionScoreQuery(QueryBuilders.matchQuery("word", "up")).boost(1));
queryBuilder.withQuery(boolQueryBuilder1).withSort(SortBuilders.scoreSort());
users = userRepository.search(queryBuilder.build());
userList = Lists.newArrayList(users);
log.info("userList:{}", userList);
/**
* 调整两个boost的大小,最后打印的顺序就会改变
* userList:[User(no=2, age=26, name=li si, word=day day up!), User(no=1, age=26, name=zhang san, word=good good study!)]
* userList:[User(no=1, age=26, name=zhang san, word=good good study!), User(no=2, age=26, name=li si, word=day day up!)]
*/
//======================================= 带过滤条件的查询==========================================
queryBuilder.withFilter(QueryBuilders.rangeQuery("age").lt(35)); // 只要年龄小于35的
users = userRepository.search(queryBuilder.build());
userList = Lists.newArrayList(users);
log.info("userList:{}", userList);
}
}
补充:
-
should: 所有的must子句必须匹配,所有的must_not 必须不匹配。默认的,不需要should匹配,但是,如果没有must语句,则至少需要一个should匹配。像我们控制match查询精度一样,我们也可以通过minimum_should_match 参数控制多少should子句需要被匹配,这个参数可以是整数,也可以是百分比。
-
match在匹配时会对所查找的关键词进行分词,然后按分词匹配查找,而term会直接对关键词进行查找。一般模糊查找的时候,多用match,而精确查找时可以使用term。
举个例子说明一下:
{ "match": { "title": "my cat"} } { "bool": { "should": [ { "term": { "title": "my" }}, { "term": { "title": "cat" }} ] } }
match 会将关键词进行分词分成“my”和“cat”,查找时包含其中任一均可被匹配到。
term结合bool使用,不进行分词,但是有2个关键词,并且使用“或”匹配,也就是会匹配关键字一“my”或关键字“cat”,效果和上面的match是相同的。如果要想精确的匹配“my cat”而不匹配“my lovely cat”,则可以如下方式匹配:
{ "bool": { "should": [ { "term": { "title": "my cat" }} ] } }