当前位置: 代码迷 >> 综合 >> SpringBoot-starter-data整合Elasticsearch
  详细解决方案

SpringBoot-starter-data整合Elasticsearch

热度:57   发布时间:2023-12-03 19:28:06.0

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=8091spring.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 {
    @Idprivate 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();//索引库的名称,个人建议以项目的名称命名@DeprecatedString 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 {
    @Resourceprivate EmployeeInfoRepository elasticRepository;@Resourceprivate ElasticsearchRestTemplate elasticsearchTemplate;@Overridepublic void deleteIndex(String index) {
    elasticsearchTemplate.deleteIndex(index);}@Overridepublic void save(EmployeeInfo docBean) {
    elasticRepository.save(docBean);}@Overridepublic void saveAll(List<EmployeeInfo> list) {
    elasticRepository.saveAll(list);}@Overridepublic Iterator<EmployeeInfo> findAll() {
    return elasticRepository.findAll().iterator();}
}

2.6创建测试controller

@RestController
@RequestMapping("/employeeInfo")
public class EmployeeElasticController {
    @Autowiredprivate 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* 聚合所有的年龄*/@Testpublic 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,如果是第一页 数值为0Pageable 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”到公众号获取相关篇

  相关解决方案