springboot整合elasticsearch

我的ElasticSearch 版本是5.6.16

一. pom依赖

鉴于可能有版本冲突问题,我全贴上来。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.boot</groupId>
    <artifactId>es</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>es</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

二. Spring-data-elasticsearch 都提供了什么

1. 方便的注解,让创建索引更简单

例如下面创建User的索引,创建的索引名是user_index,类型是user,主键是no, name和word两个字段使用ik_max_word分词器进行分词。

package com.boot.es.model;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.InnerField;
import org.springframework.data.elasticsearch.annotations.MultiField;

/**
 * Author:   susq
 * Date:     2019-06-10 23:44
 */
@Data
@Document(indexName = "user_index", type = "user", replicas = 0)
public class User {

    @Id
    private Long no;

    @Field(type = FieldType.Integer)
    private int age;

    @MultiField(
        mainField = @Field(type = FieldType.Text, analyzer = "standard", searchAnalyzer = "standard", store = true),
        otherFields = @InnerField(type = FieldType.Keyword, suffix = "raw"))
    private String name;

    @Field(type = FieldType.Text, analyzer = "standard", searchAnalyzer = "standard")
    private String word;
}

这样创建索引的时候,直接根据类来创建就可以:

package com.boot.es;

import com.boot.es.model.User;
import com.boot.es.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.test.context.junit4.SpringRunner;


/**
 * Author:   susq
 * Date:     2019-06-26 00:09
 */
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class EsServiceTests {

    @Autowired
    private ElasticsearchTemplate template;

    @Autowired
    private UserService userService;

    @Test
    public void rebuildIndex() {
        template.createIndex(User.class);
        log.info("user索引在吗" + template.indexExists(User.class));
        template.deleteIndex(User.class);
        log.info("user索引在吗" + template.indexExists(User.class));
    }
}

对用到的注解做一下补充:

ublic @interface Document {
 
String indexName(); //索引库的名称
 
String type() default ""; //类型
 
short shards() default 5; //默认分区数
 
short replicas() default 1; //每个分区默认的备份数,我们上面设置0是因为是单机的,在同一台机器上备份没用,才设置了0, 否则通过elastic-head插件,会出现UNASSIGNED节点。 elasticsearch-head插件使用参考我之前的文章:https://blog.csdn.net/u013041642/article/details/91355916
 
String refreshInterval() default "1s"; //刷新间隔
 
String indexStoreType() default "fs"; //索引文件存储类型

boolean creatIndex() default true; // 是否给当前使用注解的对象自动创建索引
}
public @interface Field {
 
FieldType type() default FieldType.Auto; //自动检测属性的类型,可以根据实际情况自己设置
 
FieldIndex index() default true; //默认情况下分词,一般默认分词就好,除非这个字段你确定查询时不会用到,可以设置成false让它不分词
 
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;
}
2. 封装好的接口,简单的增删改查功能都已经实现,如果功能简单,我们几乎不需要自己写什么代码
package com.boot.es.repository;

import com.boot.es.model.User;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;

// 两个泛型,第一个是索引对应的实体类User,第二个是主键的类型
@Repository
public interface UserRepository extends ElasticsearchRepository<User, Long> {
}

继承层次中,CrudRepository接口定义好了许多接口, 注释我删掉了,大家看源码的话有很多注释,比较简单。

@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {

	<S extends T> S save(S entity);

	<S extends T> Iterable<S> saveAll(Iterable<S> entities);

	Optional<T> findById(ID id);

	boolean existsById(ID id);

	Iterable<T> findAll();

	Iterable<T> findAllById(Iterable<ID> ids);

	long count();

	void deleteById(ID id);

	void delete(T entity);

	void deleteAll(Iterable<? extends T> entities);

	void deleteAll();
}

继承层次中,还有个接口, 如果我们要构建复杂的查询,可以自行拼装QueryBuilder,

public interface ElasticsearchRepository<T, ID extends Serializable> extends ElasticsearchCrudRepository<T, ID> {

	<S extends T> S index(S entity);

	Iterable<T> search(QueryBuilder query);

	Page<T> search(QueryBuilder query, Pageable pageable);

	Page<T> search(SearchQuery searchQuery);

	Page<T> searchSimilar(T entity, String[] fields, Pageable pageable);

	void refresh();

	Class<T> getEntityClass();
}
3. 配合查询的QueryBuilder, 提供了丰富的静态方法,自由组合查询条件,下面列举了一些常用的。

这里使用的标准分词器,对汉字分词不友好,所以这里的name,word, 我都写成了英文形式,后面会专门整理一篇汉字和拼音分词器的。

package com.boot.es;

import com.boot.es.model.User;
import com.boot.es.repository.UserRepository;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.util.Lists;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.sort.SortBuilders;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class EsApplicationTests {

    @Autowired
    private UserRepository userRepository;

    @Test
    public void testCurd() {
        // 添加,可以saveAll批量添加
        User user = new User();
        user.setAge(26);
        user.setName("zhangsan");
        user.setNo(1L);
        user.setWord("good good study!");
        userRepository.save(user);

        // 查询全部
        List<User> users = Lists.newArrayList(userRepository.findAll());
        log.info("users,{}", users);

        // 更新就是重复保存,有变化的字段会覆盖老的
        user.setWord("day day up!");
        userRepository.save(user);
        user = userRepository.findById(1L).orElse(null);
        log.info("user,{}", user);

        userRepository.deleteById(1L);
        user = userRepository.findById(1L).orElse(null);
        log.info("user,{}", user);

        User user1 = new User();
        user1.setAge(26);
        user1.setName("zhang san");
        user1.setNo(1L);
        user1.setWord("good good study!");

        User user2 = new User();
        user2.setAge(36);
        user2.setName("li si");
        user2.setNo(2L);
        user2.setWord("day day up!");

        userRepository.saveAll(Lists.newArrayList(user1, user2));
        // 查询全部
        users = Lists.newArrayList(userRepository.findAll());
        log.info("users,{}", users);
    }


    @Test
    public void testSearch() {
        // 空条件查询,返回全部
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        Iterable<User> users = userRepository.search(queryBuilder.build());
        List<User> userList = Lists.newArrayList(users);
        log.info("userList:{}", userList);

        //=========================精确匹配查询==========================================================

        // no等于1的,name用标准分词器分词后的倒排索引包含"li"的,name不分词时候等于"li si"的,也可以同时都组合起来,则查询条件就是要同时满足的
//        queryBuilder.withQuery(QueryBuilders.termQuery("no", 1L));
//        queryBuilder.withQuery(QueryBuilders.termQuery("name", "li"));
//        queryBuilder.withQuery(QueryBuilders.termQuery("name.raw", "li si"));
        queryBuilder.withQuery(QueryBuilders.termQuery("age", 26));
        users = userRepository.search(queryBuilder.build());
        userList = Lists.newArrayList(users);
        log.info("userList:{}", userList);

        //=============================检索==============================================================
        queryBuilder.withQuery(QueryBuilders.matchQuery("name", "zhang")); // 检索name 中有"zhang"的
        queryBuilder.withQuery(QueryBuilders.multiMatchQuery("good", "name", "word")); // 检索name 或者 word 中有"good"的
        users = userRepository.search(queryBuilder.build());
        userList = Lists.newArrayList(users);
        log.info("userList:{}", userList);


        /**
         * 我们上面创建的索引,name,word两个字段都设置分词,name用汉字分词器分词,word用拼音分词器分词,
         * termQuery() term过滤器会查找我们设定的准确值。如果有分词的话,term过滤器只能匹配到分词后的倒排索引。
         * matchQuery()
         *   1.被查字段是分词的,那么查询字符串也要被分词
         *   2. 类似低级别的term查询
         *   3. 综合考虑词频,逆文档频率,包含关键字的字段长度,来计算相关性打分_score
         * multiMatchQuery() 可以多词查询,一段文本可以匹配多个字段,或的关系
         * 在本文最后我也补充了例子,比较直观
         */
        //=============================组合查询条件=======================================================

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
                .must(QueryBuilders.matchQuery("name", "zhang"))
                .mustNot(QueryBuilders.matchQuery("name", "li"))
                .should(QueryBuilders.matchQuery("word", "good"))
                .should(QueryBuilders.matchQuery("word", "up"))
                .minimumShouldMatch(1);
        queryBuilder.withQuery(boolQueryBuilder);
        users = userRepository.search(queryBuilder.build());
        userList = Lists.newArrayList(users);
        log.info("userList:{}", userList);

        //=============================组合查询条件自定义每个条件的权重,按权重排序=============================
        BoolQueryBuilder boolQueryBuilder1 = QueryBuilders.boolQuery()
                .should(QueryBuilders.functionScoreQuery(QueryBuilders.matchQuery("word", "good")).boost(2))
                .should(QueryBuilders.functionScoreQuery(QueryBuilders.matchQuery("word", "up")).boost(1));
        queryBuilder.withQuery(boolQueryBuilder1).withSort(SortBuilders.scoreSort());
        users = userRepository.search(queryBuilder.build());
        userList = Lists.newArrayList(users);
        log.info("userList:{}", userList);

        /**
         * 调整两个boost的大小,最后打印的顺序就会改变
         * userList:[User(no=2, age=26, name=li si, word=day day up!), User(no=1, age=26, name=zhang san, word=good good study!)]
         * userList:[User(no=1, age=26, name=zhang san, word=good good study!), User(no=2, age=26, name=li si, word=day day up!)]
         */

        //======================================= 带过滤条件的查询==========================================
        queryBuilder.withFilter(QueryBuilders.rangeQuery("age").lt(35)); // 只要年龄小于35的
        users = userRepository.search(queryBuilder.build());
        userList = Lists.newArrayList(users);
        log.info("userList:{}", userList);
    }
}

补充:

  • should: 所有的must子句必须匹配,所有的must_not 必须不匹配。默认的,不需要should匹配,但是,如果没有must语句,则至少需要一个should匹配。像我们控制match查询精度一样,我们也可以通过minimum_should_match 参数控制多少should子句需要被匹配,这个参数可以是整数,也可以是百分比。

  • match在匹配时会对所查找的关键词进行分词,然后按分词匹配查找,而term会直接对关键词进行查找。一般模糊查找的时候,多用match,而精确查找时可以使用term。

    举个例子说明一下:

    {  
        "match": { "title": "my cat"}  
    }
    {  
      "bool": {  
        "should": [  
          { "term": { "title": "my" }},  
          { "term": { "title": "cat"   }}  
        ]  
      }  
    } 
    

    match 会将关键词进行分词分成“my”和“cat”,查找时包含其中任一均可被匹配到。

    term结合bool使用,不进行分词,但是有2个关键词,并且使用“或”匹配,也就是会匹配关键字一“my”或关键字“cat”,效果和上面的match是相同的。如果要想精确的匹配“my cat”而不匹配“my lovely cat”,则可以如下方式匹配:

    {  
      "bool": {  
        "should": [  
          { "term": { "title": "my cat" }}  
        ]  
      }  
    } 
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值