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语法示例
AndfindByNameAndPrice{ “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] } }, { “query_string” : { “query” : “?”, “fields” : [ “price” ] } } ] } }}
OrfindByNameOrPrice{ “query” : { “bool” : { “should” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] } }, { “query_string” : { “query” : “?”, “fields” : [ “price” ] } } ] } }}
IsfindByName{ “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] } } ] } }}
NotfindByNameNot{ “query” : { “bool” : { “must_not” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] } } ] } }}
BetweenfindByPriceBetween{ “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : ?, “to” : ?, “include_lower” : true, “include_upper” : true } } } ] } }}
LessThanfindByPriceLessThan{ “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : null, “to” : ?, “include_lower” : true, “include_upper” : false } } } ] } }}
LessThanEqualfindByPriceLessThanEqual{ “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : null, “to” : ?, “include_lower” : true, “include_upper” : true } } } ] } }}
GreaterThanfindByPriceGreaterThan{ “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : ?, “to” : null, “include_lower” : false, “include_upper” : true } } } ] } }}
GreaterThanEqualfindByPriceGreaterThan{ “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : ?, “to” : null, “include_lower” : true, “include_upper” : true } } } ] } }}
BeforefindByPriceBefore{ “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : null, “to” : ?, “include_lower” : true, “include_upper” : true } } } ] } }}
AfterfindByPriceAfter{ “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : ?, “to” : null, “include_lower” : true, “include_upper” : true } } } ] } }}
LikefindByNameLike{ “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?*”, “fields” : [ “name” ] }, “analyze_wildcard”: true } ] } }}
StartingWithfindByNameStartingWith{ “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?*”, “fields” : [ “name” ] }, “analyze_wildcard”: true } ] } }}
EndingWithfindByNameEndingWith{ “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “*?”, “fields” : [ “name” ] }, “analyze_wildcard”: true } ] } }}
Contains/ContainingfindByNameContaining{ “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” : ["?","?"]}} ] } } ] } }}
InfindByNameIn(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” : ["?","?"]}} ] } } ] } }}
NotInfindByNameNotIn(Collectionnames){“query”: {“bool”: {“must”: [{“query_string”: {“query”: “NOT(”?" “?”)", “fields”: [“name”]}}]}}}
NearfindByStoreNearNot Supported Yet !
TruefindByAvailableTrue{ “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “true”, “fields” : [ “available” ] } } ] } }}
FalsefindByAvailableFalse{ “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “false”, “fields” : [ “available” ] } } ] } }}
OrderByfindByAvailableTrueOrderByNameDesc{ “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”到公众号获取相关篇

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐