Spring项目 提供SpringData子模块,为各种数据访问提供统一编程接口,包括关系数据库(Mysql)、非关系数据库(Redis)或者类似Elasticsearch这样的分布式索引数据库。从而简化代码开发,提高开发效率。
Spring Data Elasticsearch 基于Spring Data API简化elasticsearch操作,将elasticsearch原始客户端API进行封装。Spring Data Elasticsearch为elasticsearch项目提供集成搜索引擎,提供了Spring Data风格的操纵数据方式,避免大量样板代码。
常用注解说明
@Document
@Persistent
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
//标示映射到Elasticsearch文档上的领域对象
public @interface Document {
//索引库名===>关系数据库中数据库概念
String indexName();
//类型=====》关系数据库表的概念
String type() default "";
//创建索引是使用服务端设置-默认false
boolean useServerConfiguration() default false;
//分片数-默认5
short shards() default 5;
//副本数量-默认1
short replicas() default 1;
//刷新时间
String refreshInterval() default "1s";
//索引存储类型
String indexStoreType() default "fs";
//配置是否在存储库上创建索引
boolean createIndex() default true;
//版本管理配置
VersionType versionType() default VersionType.EXTERNAL;
}
@Id
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
//Document中的id========>关系数据库表行的概念
public @interface Id {
}
@Field
@Field(type = FieldType.Keyword)
@Field(analyzer = "ik_max_word",type = FieldType.Text)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Documented
@Inherited
public @interface Field {
//文章中字段类型
FieldType type() default FieldType.Auto;
//是否建立倒排索引
boolean index() default true;
DateFormat format() default DateFormat.none;
String pattern() default "";
//是否存储数据
boolean store() default false;
boolean fielddata() default false;
String searchAnalyzer() default "";
//分词器名次
String analyzer() default "";
String normalizer() default "";
String[] ignoreFields() default {};
boolean includeInParent() default false;
String[] copyTo() default {};
}
@Field注解中为文章自动指定元数据类型
public enum FieldType {
Text,//会进行分词并建了索引的字符类型
Integer,
Long,
Date,
Float,
Double,
Boolean,
Object,
Auto,//自动判断对象类型
Nested,//嵌套对象类型
Ip,
Attachment,
Keyword;//不会进行分词建立索引的类型
private FieldType() {
}
}
Spring Data风格的操作数据
1. 通过继承ElasticsearchRepository,来完成ES普通的CRUD和分页查询和普通JPA没什么区别
ElasticsearchRepository里有几个特殊的search方法,是ES独有的,是区别与其他普通JPA的地方,用来构建ES查询的。
<S extends T> S index(S var1);
Iterable<T> search(QueryBuilder var1);
Page<T> search(QueryBuilder var1, Pageable var2);
Page<T> search(SearchQuery var1);
Page<T> searchSimilar(T var1, String[] var2, Pageable var3);
void refresh();
Class<T> getEntityClass();
普通操作:
List<Order> findByNameAndPrice(String name, Integer price);
List<Order> findByNameOrPrice(String name, Integer price);
Page<Order> findByName(String name,Pageable page);
Page<Order> findByNameNot(String name,Pageable page);
Page<Order> findByPriceBetween(int price,Pageable page);
Page<Order> findByNameLike(String name,Pageable page);
//使用@query注解可以直接使用elasticsearch的DSL语句进行查询
@Query("{\"bool\" : {\"must\" : {\"term\" : {\"message\" : \"?0\"}}}}")
Page<Order> findByMessage(String message, Pageable pageable);
//批量插入
List<order> orderList = orderDao.getAllOrderList(null);
Iterable<EsProduct> esProductIterable = orderRepository.saveAll(orderList);
从ElasticsearchRepository API中我们主要关注QueryBuilder 和SearchQuery 对象,要完成一些特殊查询就主要看这两个参数。
从接口关系图中可以看到SearchQuery是个接口,其实现类是NativeSearchQuery。实际使用中,我们主要是通过构建NativeSearchQuery来完成一些复杂查询。
从NativeSearchQuery源码查看,NativeSearchQuery构造函数主要需要两个参数:QueryBuilder 和 SortBuilder。
public NativeSearchQuery(QueryBuilder query) {
this.query = query;
}
public NativeSearchQuery(QueryBuilder query, QueryBuilder filter) {
this.query = query;
this.filter = filter;
}
public NativeSearchQuery(QueryBuilder query, QueryBuilder filter, List<SortBuilder> sorts) {
this.query = query;
this.filter = filter;
this.sorts = sorts;
}
public NativeSearchQuery(QueryBuilder query, QueryBuilder filter, List<SortBuilder> sorts, Field[] highlightFields) {
this.query = query;
this.filter = filter;
this.sorts = sorts;
this.highlightFields = highlightFields;
}
当然,实际使用过程基本不直接New NativeSearchQuery,而是通过NativeSearchQueryBuilder 的build方法来构建查询。
QueryBuilder 主要用来构建查询条件和过滤条件和 SortBuilder主要构建排序。我们可以通过NativeSearchQueryBuilder 来组合这些QueryBuilder和SortBuilder,在组合分页参数,最后得到一个SearchQuery。
NativeSearchQuery实现:
Pageable pageable = PageRequest.of(pageNum, pageSize);
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
//分页
nativeSearchQueryBuilder.withPageable(pageable);
//过滤
if (brandId != null || productCategoryId != null) {
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
if (brandId != null) {
boolQueryBuilder.must(QueryBuilders.termQuery("brandId", brandId));
}
if (productCategoryId != null) {
boolQueryBuilder.must(QueryBuilders.termQuery("productCategoryId", productCategoryId));
}
nativeSearchQueryBuilder.withFilter(boolQueryBuilder);
}
//搜索
if (StringUtils.isEmpty(keyword)) {
nativeSearchQueryBuilder.withQuery(QueryBuilders.matchAllQuery());
} else {
List<FunctionScoreQueryBuilder.FilterFunctionBuilder> filterFunctionBuilders = new ArrayList<>();
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("name", keyword),
ScoreFunctionBuilders.weightFactorFunction(10)));
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("subTitle", keyword),
ScoreFunctionBuilders.weightFactorFunction(5)));
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("keywords", keyword),
ScoreFunctionBuilders.weightFactorFunction(2)));
FunctionScoreQueryBuilder.FilterFunctionBuilder[] builders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[filterFunctionBuilders.size()];
filterFunctionBuilders.toArray(builders);
FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(builders)
.scoreMode(FunctionScoreQuery.ScoreMode.SUM)
.setMinScore(2);
nativeSearchQueryBuilder.withQuery(functionScoreQueryBuilder);
}
//排序
if(sort==1){
//按新品从新到旧
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("id").order(SortOrder.DESC));
}else if(sort==2){
//按销量从高到低
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("sale").order(SortOrder.DESC));
}else if(sort==3){
//按价格从低到高
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.ASC));
}else if(sort==4){
//按价格从高到低
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.DESC));
}else{
//按相关度
nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC));
}
nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC));
NativeSearchQuery searchQuery = nativeSearchQueryBuilder.build();
LOGGER.info("DSL:{}", searchQuery.getQuery().toString());
return productRepository.search(searchQuery);
ElasticsearchTemplate使用
ElasticsearchTemplate是ElasticsearchRepository的补充,提供更多底层操作方法。比如通过ElasticsearchRepository的saveAll方法能够实现批量插入,但是只支持小量数据查询,如果是超大数据插入就需要使用ES原生的API bulk 了,可以实现百万级数据迅速插入。
在ElasticsearchTemplate中提供了对应方法:
SpringBoot 整合 elasticsearch
1、在pom.xml中添加相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
2、修改SpringBoot配置文件,添加elasticsearch相关配置
data:
elasticsearch:
repositories:
enabled: true
cluster-nodes: 127.0.0.1:9300
cluster-name: elasticsearch
3、构建Elasticsearch POJO,使用注解@Document创建ES 文章对象。
4、继承ElasticsearchRepository接口,同时添加自定义衍生查询,用于操作ES数据
5、创建Service实现ES操作
6、定义API
7、测试API