SpringBoot-starter-data整合Elasticsearch
1.前言本文主要讲解,springBoot 使用spring-boot-starter-data-elasticsearch方式整合Elasticsearch文末附源码什么是Spring Data ElasticSearch Spring Data ElasticSearch 基于 spring data API 简化 elasticSearch操作,将原始操作elasticSearch的客户端
1.前言
本文主要讲解,springBoot 使用spring-boot-starter-data-elasticsearch方式整合Elasticsearch
文末附源码
什么是Spring Data ElasticSearch
Spring Data ElasticSearch 基于 spring data API 简化 elasticSearch操作,将原始操作elasticSearch的客户端API进行封装 。Spring Data为Elasticsearch项目提供集成搜索引擎。Spring Data Elasticsearch POJO的关键功能区域为中心的模型与Elastichsearch交互文档和轻松地编写一个存储库数据访问层。
这里需要注意一下版本
官方版本对照:传送门
优点:
- 封装了很多通用方法以及注解式操作简化了开发流程。
- 与springBoot 集成更快速。
缺点:
- elasticsearch官方更新的版本速度太快,而springboot速度明显略慢。
- elasticsearch随着版本升级API略有变化,spring-data-elasticsearch容易遇到版本不兼容问题。
2.入门使用实例
开发环境:
- springboot版本:2.3.0.RELEASE
- elasticSearch版本: 7.2.0
- spring data elasticsearch :4.0.0
官网文档4.0版本client使用介绍:传送门
2.1.pom依赖
创建springBoot项目,添加整合elasticsearch依赖
<!--spring boot 整合 elasticsearch -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
2.2.配置application.properties
server.port=8091
spring.elasticsearch.rest.uris=http://47.114.56.113:9200
spring.elasticsearch.rest.username=elastic
spring.elasticsearch.rest.password=1234567
2.3.测试实体类
创建一个员工的实体类
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
/**
* 员工实体类
*
* @author 程序员小强
*/
@Document(indexName = "employee_info", shards = 2, replicas = 2)
public class EmployeeInfo {
@Id
private Long id;
/**
* 工号
*/
@Field(name = "job_no")
private String jobNo;
/**
* 姓名
*/
@Field(name = "name")
private String name;
/**
* 英文名
*/
@Field(name = "english_name")
private String englishName;
/**
* 工作岗位
*/
private String job;
/**
* 性别
*/
private Integer sex;
/**
* 年龄
*/
private Integer age;
/**
* 薪资
*/
private BigDecimal salary;
/**
* 入职时间
*/
@Field(name = "job_day",format= DateFormat.date_time)
private Date jobDay;
/**
* 备注
*/
private String remark;
//省略get set
}
注:注解说明
Document注解
@Persistent
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Document {
String indexName();//索引库的名称,个人建议以项目的名称命名
@Deprecated
String type() default "";//类型,7.x之后以废弃
short shards() default 1;//默认分区数
short replicas() default 1;//每个分区默认的备份数
String refreshInterval() default "1s";//刷新间隔
String indexStoreType() default "fs";//索引文件存储类型
boolean createIndex() default true; //是否创建索引
VersionType versionType() default VersionType.EXTERNAL; //版本
}
@Field
注解
可以通过@Field
注解来进行详细的指定,如果无特殊需求,那么只需要添加@Document即可。
常见@Field,用于定义别名,或者针对日期属性进行格式化设定。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@Inherited
public @interface Field {
@AliasFor("name")
String value() default ""; //属性别名
@AliasFor("value")
String name() default ""; //属性别名
FieldType type() default FieldType.Auto; //属性类型,默认自动根据参数类型自动属性的类型
boolean index() default true; //默认情况下分词
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;
String[] copyTo() default {};
int ignoreAbove() default -1;
boolean coerce() default true;
boolean docValues() default true;
boolean ignoreMalformed() default false;
IndexOptions indexOptions() default IndexOptions.none;
boolean indexPhrases() default false;
IndexPrefixes[] indexPrefixes() default {};
boolean norms() default true;
String nullValue() default "";
int positionIncrementGap() default -1;
Similarity similarity() default Similarity.Default;
TermVector termVector() default TermVector.none;
double scalingFactor() default 1;
int maxShingleSize() default -1;
}
2.4创建xxRepository
import com.example.elasticsearch.bean.EmployeeInfo;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
/**
* @author 程序员小强
*/
//泛型的参数分别是实体类型和主键类型
public interface EmployeeInfoRepository extends ElasticsearchRepository<EmployeeInfo, Long> {
}
2.5创建Service接口
/**
* @author 程序员小强
*/
public interface EmployeeInfoService {
void deleteIndex(String index);
void save(EmployeeInfo docBean);
void saveAll(List<EmployeeInfo> list);
Iterator<EmployeeInfo> findAll();
}
/**
* @author 程序员小强
*/
@Service
public class EmployeeInfoServiceImpl implements EmployeeInfoService {
@Resource
private EmployeeInfoRepository elasticRepository;
@Resource
private ElasticsearchRestTemplate elasticsearchTemplate;
@Override
public void deleteIndex(String index) {
elasticsearchTemplate.deleteIndex(index);
}
@Override
public void save(EmployeeInfo docBean) {
elasticRepository.save(docBean);
}
@Override
public void saveAll(List<EmployeeInfo> list) {
elasticRepository.saveAll(list);
}
@Override
public Iterator<EmployeeInfo> findAll() {
return elasticRepository.findAll().iterator();
}
}
2.6创建测试controller
@RestController
@RequestMapping("/employeeInfo")
public class EmployeeElasticController {
@Autowired
private EmployeeInfoService employeeInfoService;
@RequestMapping("/batchSave")
public String init() throws Exception {
List<EmployeeInfo> list = new ArrayList<>();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
list.add(new EmployeeInfo(1001L, "2001", "张三", "zhangsan", "Java", 1, 19, new BigDecimal("12500.01"), simpleDateFormat.parse("2019-09-10"), "备注"));
list.add(new EmployeeInfo(1002L, "2002", "李四", "lisi", "PHP", 1, 18, new BigDecimal("11600.01"), simpleDateFormat.parse("2019-09-10"), "备注"));
list.add(new EmployeeInfo(1003L, "2003", "王五", "wangwu", "C++", 1, 20, new BigDecimal("9900.01"), simpleDateFormat.parse("2019-09-10"), "备注"));
list.add(new EmployeeInfo(1004L, "2004", "赵六", "zhaoliu", "Java Leader", 1, 20, new BigDecimal("20000.01"), simpleDateFormat.parse("2019-09-10"), "备注"));
list.add(new EmployeeInfo(1005L, "2005", "小五", "xiaowu", "H5", 1, 17, new BigDecimal("10600.01"), simpleDateFormat.parse("2019-09-10"), "备注"));
list.add(new EmployeeInfo(1006L, "2006", "小六", "xaioliu", "web", 1, 20, new BigDecimal("12600.01"), simpleDateFormat.parse("2019-09-10"), "备注"));
list.add(new EmployeeInfo(1007L, "2007", "小七", "xiaoqi", "app", 1, 22, new BigDecimal("20000.01"), simpleDateFormat.parse("2019-09-10"), "备注"));
list.add(new EmployeeInfo(1008L, "2008", "小八", "xaioba", "Java", 1, 21, new BigDecimal("11000.01"), simpleDateFormat.parse("2019-09-10"), "备注"));
list.add(new EmployeeInfo(1009L, "2009", "小九", "xiaojiu", "Java", 1, 20, new BigDecimal("14000.01"), simpleDateFormat.parse("2019-09-10"), "备注"));
list.add(new EmployeeInfo(1010L, "2010", "大十", "dashi", "Java", 1, 20, new BigDecimal("13000.01"), simpleDateFormat.parse("2019-09-10"), "备注"));
employeeInfoService.batchSaveOrUpdate(list);
return "success -> " + list.size();
}
@GetMapping("/listAll")
public Iterator<EmployeeInfo> all() {
return employeeInfoService.findAll();
}
}
3.更多查询
简介
spring data elsaticsearch提供了三种构建查询模块的方式:
- 基本的增删改查:继承spring data提供的接口就默认提供
- 接口中声明方法:**无需实现类。**spring data根据方法名,自动生成实现类,方法名必须符合一定的规则
接口只要继承 ElasticsearchRepository 类即可。默认会提供很多实现,比如 CRUD 和搜索相关的实现。类似于 JPA 读取数据。
支持的默认方法有:
count(), findAll(), findOne(ID), delete(ID), deleteAll(), exists(ID), save(DomainObject), save(Iterable)。
接口的命名是遵循规范的。常用命名规则如下:
表格内容摘自官网(官方文档:传送门)
关键字 | 方法命名 | Elasticsearch 查询DSL语法示例 |
---|---|---|
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 (when annotated as FieldType.Keyword) | findByNameIn(Collectionnames) | { “query” : { “bool” : { “must” : [ {“bool” : {“must” : [ {“terms” : {“name” : ["?","?"]}} ] } } ] } }} |
In | findByNameIn(Collectionnames) | { “query”: {“bool”: {“must”: [{“query_string”:{“query”: “”?" “?”", “fields”: [“name”]}}]}}} |
NotIn (when annotated as FieldType.Keyword) | findByNameNotIn(Collectionnames) | { “query” : { “bool” : { “must” : [ {“bool” : {“must_not” : [ {“terms” : {“name” : ["?","?"]}} ] } } ] } }} |
NotIn | findByNameNotIn(Collectionnames) | {“query”: {“bool”: {“must”: [{“query_string”: {“query”: “NOT(”?" “?”)", “fields”: [“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”}}] } |
按照接口的命名方法示例
/**
* @author 程序员小强
*/
public interface EmployeeInfoRepository extends ElasticsearchRepository<EmployeeInfo, Long> {
/**
* 精确查找
* 方法名规则:finByxxx
*
* @param name
* @return 员工数据集
*/
List<EmployeeInfo> findByName(String name);
/**
* AND 语句查询
*
* @param name
* @param age
* @return 员工数据集
*/
List<EmployeeInfo> findByNameAndAge(String name, Integer age);
/**
* OR 语句查询
*
* @param name
* @param age
* @return 员工数据集
*/
List<EmployeeInfo> findByNameOrAge(String name, Integer age);
/**
* 分页查询员工信息
*
* @param name
* @param page
* @return 员工数据集
* 注:等同于下面代码 @Query("{\"bool\" : {\"must\" : {\"term\" : {\"name\" : \"?0\"}}}}")
*/
Page<EmployeeInfo> findByName(String name, Pageable page);
/**
* NOT 语句查询
*
* @param name
* @param page
* @return 员工数据集
*/
Page<EmployeeInfo> findByNameNot(String name, Pageable page);
/**
* LIKE 语句查询
*
* @param name
* @param page
* @return 员工数据集
*/
Page<EmployeeInfo> findByNameLike(String name, Pageable page);
}
4.高级查询
Data ElasticSearch 支持了一些常见的查询
但是一些高级查询呢?可以使用类组装DSL语法支持
/**
* 聚合查询-groupBy
* 聚合所有的年龄
*/
@Test
public void groupByAge() {
//1.构建查询对象
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("groupByAge")
.field("age").size(30));
SearchHits<EmployeeInfo> search = elasticsearchRestTemplate.search(nativeSearchQueryBuilder.build(), EmployeeInfo.class);
Aggregations aggregations = search.getAggregations();
//解析聚合分组后结果数据
ParsedLongTerms parsedLongTerms = aggregations.get("groupByAge");
//groupBy后的年龄集
List<String> ageList = parsedLongTerms.getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
System.out.println(ageList);
}
/**
* 分页查询
* 带参数
*/
@Test
public void listPageMatch() {
int pageNo = 1;
int pageSize = 5;
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
nativeSearchQueryBuilder.withQuery(QueryBuilders.matchQuery("name", "小"));
//注:Pageable类中 pageNum需要减1,如果是第一页 数值为0
Pageable pageable = PageRequest.of(pageNo - 1, pageSize);
nativeSearchQueryBuilder.withPageable(pageable);
SearchHits<EmployeeInfo> searchHitsResult = elasticsearchRestTemplate.search(nativeSearchQueryBuilder.build(), EmployeeInfo.class);
//7.获取分页数据
SearchPage<EmployeeInfo> searchPageResult = SearchHitSupport.searchPageFor(searchHitsResult, pageable);
System.out.println("分页查询");
System.out.println(String.format("totalPages:%d, pageNo:%d, size:%d", searchPageResult.getTotalPages(), pageNo, pageSize));
System.out.println(JSON.toJSONString(searchPageResult.getSearchHits(), SerializerFeature.PrettyFormat));
}
5.源码地址
关注程序员小强公众号更多编程趣事,知识心得与您分享
👏关注“程序员小强”发送关键字“elasticSearch”到公众号获取相关篇
更多推荐
所有评论(0)