7. SpringData操作ES
参考https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#elasticsearch.mapping
7.1 创建项目、导入依赖、编写配置文件
一定要注意版本的问题:版本不一致,会带来很多问题:
Spring Data Elasticsearch | Elasticsearch | Spring Boot |
---|---|---|
3.2.x | 6.8.4 | 2.2.x |
3.1.x | 6.2.2 | 2.1.x |
3.0.x | 5.5.0 | 2.0.x |
2.1.x | 2.4.0 | 1.5.x |
首先导入关键dependency
<!--关键依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!--lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
# cluster-name不要写错
spring.data.elasticsearch.cluster-name=elasticsearch
# Java调用时端口是9300,restfu调用端口是9200,注意区分
spring.data.elasticsearch.cluster-nodes=127.0.0.1:9300
spring.data.elasticsearch.properties.transport.tcp.connect_timeout=120s
7.2 编写实体类型
然后编写实体类型type
/**
* @author Honyelchak
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
// 若ES中没有指定的索引,会自动创建。
// shards 分片数 replicas 副本数 refreshInterval 刷新间隔
@Document(indexName = "hello_world", type="user", shards = 2, replicas = 3)
public class Item implements Serializable {
@Id
private String id;
private String userName;
@Field(type = FieldType.Keyword)
private String passWord;
@Field(type = FieldType.Integer)
private Integer age;
// text 类型的会分词
@Field(type = FieldType.Text, analyzer = "")
private String comment;
}
-
@Document(indexName = "hello_world",type = "user",shards = 5,replicas = 1)
: 标注在实体类上,声明存储的索引和类型刚才, -
indexName
: 索引名称type
:索引类型shards
:分片的数量,默认为·5replicas
:副本的数量,默认为1refreshInterval
: 刷新间隔,默认为1s
indexStoreType
:索引文件存储类型,默认为fs
-
@Field
标注在属性上,用来指定属性的类型。其中的属性如下: -
analyzer
:指定分词器,es中默认使用的标准分词器,比如我们需要指定中文IK分词器,可以指定值为ik_max_word
type
: 指定该属性在es中的类型,其中的值是FileType
类型的值,比如FileType.Text
类型对应es中的text类型index
:指定该词是否需要索引,默认为truestore
:指定该属性内容是否存储在ES,默认为falsefielddata
:指定该属性能否进行排序,因为es中的text类型是不能进行排序(已经分词了)searchAnalyzer
: 指定搜索使用的分词器name
:字段名称,可以在ES文档中表示,若未设置,则使用Java字段名称。
7.3 使用方法:
-
直接在
dao
接口继承ElasticsearchRepository
-
直接在
Service/Controller
中引入ElasticsearchTemplate
7.3.1 继承ElasticsearchRepository
7.3.1.1 SpringData重要接口
Spring Data的中央接口是Repository
。
- 它需要实体类以及实体类的ID作为类型参数来管理。
- 该接口主要用作标记接口(内无实现),以捕获要使用的类型并帮助您发现扩展该接口的接口
CrudRepository
规定对于正在管理的实体类复杂的CRUD功能。
public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S entity);
Optional<T> findById(ID primaryKey);
Iterable<T> findAll();
long count();
void delete(T entity);
boolean existsById(ID primaryKey);
// … more functionality omitted.
}
ES中的PagingAndSortingRepository
接口 继承了CrudRepository
。简化了分页操作和排序操作:
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
}
而在SpringBoot项目中,常用的是ElasticsearchRepository
,他在ElasticsearchCrudRepository
的基础上补充了一些与查询相关的方法。
7.3.1.2 根据方法名查询
interface BookRepository extends Repository<Book, String> {
List<Book> findByNameAndPrice(String name, Integer price);
}
7.3.1.3 @Query(json)查询
上面的方法名称将转换为以下Elasticsearch json查询,
{
"query": {
"bool" : {
"must" : [
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } },
{ "query_string" : { "query" : "?", "fields" : [ "price" ] } }
]
}
}
}
还可以用@Query
注解来自定义查询语句,例如:
@Query("{\n" +
" \"bool\": {\n" +
" \"must\": [\n" +
" {\n" +
" \"match\": {\n" +
" \"userName\": \"?0\"\n" +
" }\n" +
" }\n" +
" ]\n" +
" }\n" +
" }")
List<User> selectByUserName(String userName);
PS: 其中的?是占位符,0表示第一个参数
对应表:
关键词 | 样品 | Elasticsearch查询字符串 |
---|---|---|
And | findByNameAndPrice | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } }, { "query_string" : { "query" : "?", "fields" : [ "price" ] } } ] } }} |
Or | findByNameOrPrice | { "query" : { "bool" : { "should" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } }, { "query_string" : { "query" : "?", "fields" : [ "price" ] } } ] } }} |
Is | findByName | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } } ] } }} |
Not | findByNameNot | { "query" : { "bool" : { "must_not" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } } ] } }} |
Between | findByPriceBetween | { "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }} |
LessThan | findByPriceLessThan | { "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : false } } } ] } }} |
LessThanEqual | findByPriceLessThanEqual | { "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }} |
GreaterThan | findByPriceGreaterThan | { "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : false, "include_upper" : true } } } ] } }} |
GreaterThanEqual | findByPriceGreaterThan | { "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } } ] } }} |
Before | findByPriceBefore | { "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }} |
After | findByPriceAfter | { "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } } ] } }} |
Like | findByNameLike | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }} |
StartingWith | findByNameStartingWith | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }} |
EndingWith | findByNameEndingWith | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "*?", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }} |
Contains/Containing | findByNameContaining | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "*?*", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }} |
In | findByNameIn(Collectionnames) | { "query" : { "bool" : { "must" : [ {"bool" : {"must" : [ {"terms" : {"name" : ["?","?"]}} ] } } ] } }} |
NotIn | findByNameNotIn(Collectionnames) | { "query" : { "bool" : { "must" : [ {"bool" : {"must_not" : [ {"terms" : {"name" : ["?","?"]}} ] } } ] } }} |
Near | findByStoreNear | Not Supported Yet ! |
True | findByAvailableTrue | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "true", "fields" : [ "available" ] } } ] } }} |
False | findByAvailableFalse | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "false", "fields" : [ "available" ] } } ] } }} |
OrderBy | findByAvailableTrueOrderByNameDesc | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "true", "fields" : [ "available" ] } } ] } }, "sort":[{"name":{"order":"desc"}}] } |
7.3.2 引入ElasticsearchTemplate
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
@GetMapping("createIndex")
public boolean createIndex(String indexName) {
return elasticsearchTemplate.createIndex(indexName);
}
@GetMapping("deleteIndex")
public boolean deleteIndex(String indexName) {
return elasticsearchTemplate.deleteIndex(indexName);
}
@GetMapping("indexIsExist")
public boolean indexIsExist(String indexName) {
return elasticsearchTemplate.indexExists(indexName);
}
@GetMapping("typeIsExist")
public boolean typeIsExist(String indexName, String type) {
return elasticsearchTemplate.typeExists(indexName, type);
}
@GetMapping("getMapping")
public Map getMapping(String indexName, String type) {
return elasticsearchTemplate.getMapping(indexName, type);
}
@GetMapping("getSetting")
public Map getSetting(String indexName) {
return elasticsearchTemplate.getSetting(indexName);
}
7.4 高级查询
- QueryBuilders 构建查询关键词
- SortBuilders 构建对关键字的排序
- NativeSearchQueryBuilder 对前两个条件进行封装,
- Repository : 进行查询
// 分词查询 :comment中包含中国的用户,并且以age倒序返回。
@Test
public void searchByCommentSortByAge() {
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.should(QueryBuilders.termQuery("comment", "中国"));
FieldSortBuilder sortBuilder = SortBuilders.fieldSort("age").order(SortOrder.DESC);
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
nativeSearchQueryBuilder.withSort(sortBuilder);
NativeSearchQuery searchQuery = nativeSearchQueryBuilder.build();
Page<Item> itemsPage = itemRepository.search(searchQuery);
if (itemsPage != null) {
List<Item> items = itemsPage.getContent();
items.forEach(System.out::println);
} else {
System.out.println("can not find that!");
}
}
// 查询userName为fanqi的用户,并且以age倒序返回。
@Test
public void searchByUserNameSortByAge() {
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.should(QueryBuilders.termQuery("userName", "fanqi"));
FieldSortBuilder sortBuilder = SortBuilders.fieldSort("age").order(SortOrder.DESC);
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
nativeSearchQueryBuilder.withSort(sortBuilder);
NativeSearchQuery searchQuery = nativeSearchQueryBuilder.build();
Page<Item> itemsPage = itemRepository.search(searchQuery);
if (itemsPage != null) {
List<Item> items = itemsPage.getContent();
items.forEach(System.out::println);
} else {
System.out.println("can not find that!");
}
}