美文网首页springbootIT必备技能
spring-boot 整合elasticsearch 7.x(

spring-boot 整合elasticsearch 7.x(

作者: 小小的人_e5f6 | 来源:发表于2021-09-11 11:31 被阅读0次

    spring-boot 整合elasticsearch 7.x

    elasticsearch 下面简称为 es/ES

    技术版本说明

    1. springboot .version - 2.5.3
    2. jdk .version - 8
    3. lombok.version  - version  - 1.18.10
    4. hutool-all.version - 5.6.2
    5. spring-boot-starter-data-elasticsearch.version - 延用springboot里的版本(即 2.5.3)
      注意: 虽然延用(继承)  2.5.3 但是有三个版本必须移除然后选择自己es对应的版本!
      如下三个: (使用你自己装的es的版本,例如我的7.7.0)
        elasticsearch-rest-high-level-client - 7.7.0
        elasticsearch-rest-client - 7.7.0
        elasticsearch - 7.7.0
    6. fastjson - version - 1.2.75
    7. spring-boot-starter-aop - 延用springboot里的版本(即 2.5.3)
    
    

    源码地址

    gitee地址: https://gitee.com/zjydzyjs/spring-boot-use-case-collection/tree/master/es

    为什么选择这个版本的springboot?

    首先得理解es 6.x 和 7.x 的不同,导致的springboot版本整合不同!

    首先,来先跟我一起打开spring-data/elasticsearch官网

    来到 3.1 Version

    版本关系

    看到如下图: spring-data-elasticsearch对应es版本

    因为我的 ES 版本为7.7.0,所以我的springboot 版本需要为 2.4.x以上。

    你也应该根据自己的 ES 版本来选择对应的springboot 版本,或根据springboot版本选择对应的 ES 版本。

    那么新版本有什么不一样?

    原文

    图(原文)
    译文 译文

    我在译文那里标了红框,官方说:

    弃用TransportClient使用

    那么这是为什么呢?

    为什么弃用 TransportClient

    在文档5.1. Transport Client中找到答案

    image

    译文:

    TransportClient被弃用Elasticsearch 7的,并会在Elasticsearch 8被移除(见Elasticsearch文档)。TransportClient只要在使用的 Elasticsearch版本中可用,Spring Data Elasticsearch 就会支持它,但自 4.0 版以来已弃用使用它的类。

    那官方都说了是 ES 官方放弃了这个 Transport Client ,那我们去看看。

    image

    译文:

    在 7.0.0 中已弃用。

    TransportClient是赞成不赞成使用的Java的高级REST客户端,将在Elasticsearch 8.0被删除。该迁移指南描述了所有需要迁移的步骤。

    ok了,那么我们就可以知道了,在Spring Data Elasticsearch中应该要使用 Java High Level REST Client

    弃用TransportClient后应该用什客户端?

    然后我们继续在Spring Data Elasticsearch文档中找这个客户端,

    发现了文档中的 5.2. High Level REST Client

    刚好与Java High Level REST Client 对应上。

    yml配置说明

    如下图(yml配置):
    
    yml配置
    yml 配置(图)
    
    备注:  我这里只是简单配置,如果需要自定义复杂配置,请自行配置;
    

    提示: 我这里配置的是 ElasticsearchRestClientProperties

    为什么这么配置?

    先找到 ES 对应自动配置;
    
    image

    搜索关键字 elasticsearch 得到下图结果:

    搜索结果

    我们可以看到红框内容,然后点进黄框(ctrl+鼠标左击)

    得到以下内容:

    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnClass({ElasticsearchRestTemplate.class})
    @AutoConfigureAfter({ElasticsearchRestClientAutoConfiguration.class, ReactiveElasticsearchRestClientAutoConfiguration.class})
    @Import({BaseConfiguration.class, RestClientConfiguration.class, ReactiveRestClientConfiguration.class})
    public class ElasticsearchDataAutoConfiguration {
        public ElasticsearchDataAutoConfiguration() {
        }
    }
    

    @ConditionalOnClass({ElasticsearchRestTemplate.class})

    解析: @ConditionalOnClass在 本文@ConditionalOnXXXX系列常用注解可以看到解释, 即为: "当前classpath下存在指定类,则实例化当前Bean"。

    那么现在的意思就是我们的spring工程中有引入ElasticsearchRestTemplate.class就实例化 ElasticsearchDataAutoConfiguration.class

    @AutoConfigureAfter({ElasticsearchRestClientAutoConfiguration.class, ReactiveElasticsearchRestClientAutoConfiguration.class})

    解析: 顾名思义, ElasticsearchDataAutoConfiguration.class 加载会在 {ElasticsearchRestClientAutoConfiguration.class, ReactiveElasticsearchRestClientAutoConfiguration.class}两个类之后。

    @Import({BaseConfiguration.class, RestClientConfiguration.class, ReactiveRestClientConfiguration.class})

    解析: @Import可以将类加入 IOC 容器,那么这就意味这交给spring去管理了,

    它这里共写了三个类,因为我们是基于rest的高级客户端,所以我们要留意RestClientConfiguration,

    其他的配置这里不深入讲述,可以参照官方文档!

    OK,刚刚那些内容我都已经简单的讲述了,那么我们现在进去研究一下,yml配置 ,我们先进入 ElasticsearchRestClientProperties.class,顾名思义,它就是配置类!

    ElasticsearchRestClientProperties 查看

    @ConfigurationProperties(
        prefix = "spring.elasticsearch.rest"
    )
    public class ElasticsearchRestClientProperties {
        private List<String> uris = new ArrayList(Collections.singletonList("http://localhost:9200"));
        private String username;
        private String password;
        private Duration connectionTimeout = Duration.ofSeconds(1L);
        private Duration readTimeout = Duration.ofSeconds(30L);
        private final ElasticsearchRestClientProperties.Sniffer sniffer = new ElasticsearchRestClientProperties.Sniffer();
    
        public ElasticsearchRestClientProperties() {
        }
    
        public List<String> getUris() {
            return this.uris;
        }
    
        public void setUris(List<String> uris) {
            this.uris = uris;
        }
    
        public String getUsername() {
            return this.username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return this.password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public Duration getConnectionTimeout() {
            return this.connectionTimeout;
        }
    
        public void setConnectionTimeout(Duration connectionTimeout) {
            this.connectionTimeout = connectionTimeout;
        }
    
        public Duration getReadTimeout() {
            return this.readTimeout;
        }
    
        public void setReadTimeout(Duration readTimeout) {
            this.readTimeout = readTimeout;
        }
    
        public ElasticsearchRestClientProperties.Sniffer getSniffer() {
            return this.sniffer;
        }
    
        public static class Sniffer {
            private Duration interval = Duration.ofMinutes(5L);
            private Duration delayAfterFailure = Duration.ofMinutes(1L);
    
            public Sniffer() {
            }
    
            public Duration getInterval() {
                return this.interval;
            }
    
            public void setInterval(Duration interval) {
                this.interval = interval;
            }
    
            public Duration getDelayAfterFailure() {
                return this.delayAfterFailure;
            }
    
            public void setDelayAfterFailure(Duration delayAfterFailure) {
                this.delayAfterFailure = delayAfterFailure;
            }
        }
    }
    

    相信大家看到

    @ConfigurationProperties(
    prefix = "spring.elasticsearch.rest"
    )

    应该知道我的yml为什么那么配置了吧!

    那么yml 为什么怎么配置就完结... 撒花😃😃😃

    那么它到底用的是不是 java High Level REST Client

    接下来回到ElasticsearchRestClientAutoConfiguration再进入RestHighLevelClientConfiguration.class

    得到如下代码:

    //这一个内部类,省略其他部分
        @Configuration(
            proxyBeanMethods = false
        )
        @ConditionalOnMissingBean({RestHighLevelClient.class})
        static class RestHighLevelClientConfiguration {
            RestHighLevelClientConfiguration() {
            }
    
            @Bean
            RestHighLevelClient elasticsearchRestHighLevelClient(RestClientBuilder restClientBuilder) {
                return new RestHighLevelClient(restClientBuilder);
            }
        }
    

    OK了,它其实就是 new RestHighLevelClient(restClientBuilder) 交给spring去管理。

    所以,没错的,他就是用的 java High Level REST Client

    怎么去 ES 进行操作

    1. 继承 ElasticsearchRepository 接口

    说明
    使用起来就和jpa差不多
    
    实现
    继承接口,用例如下:
    
    @Repository
    public interface DemoElasticsearchRepository extends ElasticsearchRepository<DemoEsDTO,String> {
    
        List<DemoEsDTO> getByNumber(Integer number);
    
        List<DemoEsDTO> getByDes(String des);
    
        void deleteByDes(String des);
    
    }
    具体方法命名空间参考官网:
    https://docs.spring.io/spring-data/elasticsearch/docs/4.2.4/reference/html/#repositories.namespace-reference 下的
    附录 A:命名空间参考
    
    测试

    在类 {@link com.blacktea.es.EsApplicationDaoTests} 下,有部分例子,可以直接了解一下。

    底层
    它使用的 ES 客户端是什么?
    
    我们先点击 **ElasticsearchRepository**,再点击**PagingAndSortingRepository**,
    
    有一个方法:
    

    Page<T> searchSimilar(T var1, @Nullable String[] var2, Pageable var3);

    点击实现 ->
    
    可以看到
    
    public Page<T> searchSimilar(T entity, @Nullable String[] fields, Pageable pageable) {
            Assert.notNull(entity, "Cannot search similar records for 'null'.");
            Assert.notNull(pageable, "'pageable' cannot be 'null'");
            MoreLikeThisQuery query = new MoreLikeThisQuery();
            query.setId(this.stringIdRepresentation(this.extractIdFromBean(entity)));
            query.setPageable(pageable);
            if (fields != null) {
                query.addFields(fields);
            }
    
            SearchHits<T> searchHits = (SearchHits)this.execute((operations) -> {
                return operations.search(query, this.entityClass, this.getIndexCoordinates());
            });
            SearchPage<T> searchPage = SearchHitSupport.searchPageFor(searchHits, pageable);
            return (Page)SearchHitSupport.unwrapSearchHits(searchPage);
        }
    
    获取数据的代码如下:
    
    SearchHits<T> searchHits = (SearchHits)this.execute((operations) -> {
        return operations.search(query, this.entityClass, this.getIndexCoordinates());
    });
    

    点击operations.searc()

    进入接口 SearchOperations,

    再点击实现类

    得到如图


    image

    点击 ElasticsearchOperations

    得到实现类


    image

    其实早在上一步就可以发现了,当前可以用的最终底层实现就是

    ElasticsearchRestTemplate.class

    底层实现图
    image
    底层原理解析
    public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
        private static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchRestTemplate.class);
        
        // ElasticsearchRestTemplate的底层交互ES的客户端是 RestHighLevelClient,
        // 说明实际上我们之前的配置都是对的!
        // ES 交互的三种方式也是对的,
        // 1.继承 ElasticsearchRepository 接口
        // 2.使用 ElasticsearchRestTemplate 类
        // 3.直接使用 RestHighLevelClient 类
        private final RestHighLevelClient client;
        private final ElasticsearchExceptionTranslator exceptionTranslator = new ElasticsearchExceptionTranslator();
    
        public ElasticsearchRestTemplate(RestHighLevelClient client) {
            Assert.notNull(client, "Client must not be null!");
            this.client = client;
            this.initialize(this.createElasticsearchConverter());
        }
    
        public ElasticsearchRestTemplate(RestHighLevelClient client, ElasticsearchConverter elasticsearchConverter) {
            Assert.notNull(client, "Client must not be null!");
            this.client = client;
            this.initialize(elasticsearchConverter);
        }
    }
    

    2. 使用ElasticsearchRestTemplate类

    说明
    有方法1(继承 ElasticsearchRepository 接口) 的锚点 - **底层原理解析**,可以知道使用该类其实是对 RestHighLevelClient类执行方法的封装,而继承接口的方式又是对该类的封装,所以你也可以直接使用该类进行直接调用。
    

    3.使用RestHighLevelClient类

    说明
    既然前面两种都是基于**RestHighLevelClient**类,进行使用的,那么我也可以直接使用该类进行调用,所以我自己写了个 {@link com.blacktea.es.service.ElasticsearchServiceImpl} 接口服务(工具类)
    
    测试
    测试的例子,我大部分都写在了,{@link com.blacktea.es.EsApplicationTests}
    
    ElasticsearchServiceImpl 说明
    该类是我自己在学习过程中,参考资料进行编写的,没有经过大数据量的测试,如果需要用于项目中,建议使用方法1(继承 ElasticsearchRepository 接口)
    
    /**
     * @description: 基于 RestHighLevelClient 封装的ElasticsearchService
     *
     * @author: black tea
     * @date: 2021/9/6 14:09
     * @version 1.0.0
     */
    public interface ElasticsearchService<T> {
    
        /**
         * 创建文档,并返回布尔值
         *   当索引不存在时,会自动创建
         * @param var2 入参类(实际文档内容)
         * @param index 索引
         * @param id id标识
         * @return true 成功
         * @throws IOException 异常
         */
        boolean createDocument(Object var2, String index, String id) throws IOException;
    
        /**
         * 创建文档,并返回创建成功的文档内容
         *   当索引不存在时,会自动创建
         * @param var1 返回的对象类型
         * @param var2 入参类(实际文档内容)
         * @param index 索引
         * @param id id标识
         * @return null 表示失败
         * @throws IOException 异常
         */
        T createDocument(Class<T> var1,Object var2,String index,String id) throws IOException;
    
        /**
         * 批量创建文档
         * @param var 入参map -> k:id,v:文档内容
         * @param index 索引
         * @return boolean true 成功
         * @throws IOException 异常
         */
        boolean addBatchDocument(Map<String,Object> var,String index) throws IOException;
    
        /**
         * 通过id标识,删除文档,并返回布尔值
         *   不会删除索引
         * @param index 索引
         * @param id id标识
         * @return true 成功
         * @throws IOException 异常
         */
        boolean deleteDocument(String index, String id) throws IOException;
    
        /**
         * 根据 ids集合 批量删除文档
         * @param index 索引
         * @param ids _id 集合
         * @return boolean
         * @throws IOException 异常
         */
        boolean deleteBatchDocument(String index, List<String> ids) throws IOException;
    
        /**
         * 按单个条件(term)删除文档,并返回删除数量
         *   不会删除索引
         * @param index 索引
         * @param key fieldName 字段名称
         * @param value 字段值
         * @return Long 删除数量
         * @throws IOException 异常
         */
        long deleteDocument(String index, String key, Object value) throws IOException;
    
        /**
         * 根据多条件进行删除文档,并返回删除数量
         * @param index 索引
         * @param conditionDTOS 自定义条件集合
         * @return Long 删除数量
         * @throws IOException 异常
         */
        long deleteDocumentByCondition(String index, List<ESConditionDTO> conditionDTOS) throws IOException;
    
        /**
         * 根据id更新文档,并返回布尔值
         *   当索引不存在时,会自动创建
         * @param var2 入参类(实际文档内容)
         * @param index 索引
         * @param id id标识
         * @return true 成功
         * @throws IOException 异常
         */
        boolean updateDocument(Object var2, String index, String id) throws IOException;
    
        /**
         * 根据id更新文档,并返回T
         *
         * @param var1 返回的对象类型
         * @param var2 修改的内容文档
         * @param index 索引
         * @param id id标识
         * @return T null 表示为修改失败
         *           失败原因:
         *              1: 该索引下不存在当前id;
         *              2: es修改返回 status = false;
         * @throws IOException
         */
        T updateDocument(Class<T> var1, Object var2, String index, String id) throws IOException;
    
        /**
         * 根据多条件进行文档更新,并返回更新数量
         * @param var2 更新后的文档内容
         * @param index 索引
         * @param conditionDTOS 自定义条件集合
         * @return long 更新数量
         * @throws IOException 异常
         */
        long updateDocumentByCondition(Object var2, String index, List<ESConditionDTO> conditionDTOS) throws IOException;
    
        /**
         * 根据 ScriptDto 进行文档更新,并返回更新数量
         * @param scriptDto {@link ScriptDto} 对象,包含了需要修改的字段属性等信息
         * @param index 索引
         * @param conditionDTOS 条件集合(类似于 mysql where 条件)
         * @return long 更新数量
         * @throws IOException 异常 ESException(自定义)
         */
        long updateDocumentByCondition(ScriptDto scriptDto, String index, List<ESConditionDTO> conditionDTOS) throws IOException;
    
        /**
         *  根据Map -> k组成的id进行批量更新文档
         *    Map 的 k 表示更新文档的 _id,
         *    Map 的 v 表示更新文档的内容 _source
         * @param index 索引
         * @param params 包含id和对应更新文档内容的Map
         * @return boolean
         * @throws IOException 异常
         */
        boolean updateBatchDocument(String index, Map<String,Object> params) throws IOException;
    
        /**
         * 通过索引下的id获取该文档内容
         * @param var1 返回的对象类型
         * @param index 索引
         * @param id id
         * @return T
         * @throws IOException 异常
         */
        T getDocument (Class<T> var1, String index, String id) throws IOException;
    
        /**
         * 根据 map 条件查询列表
         *   当前查询操作条件均为 and term
         * @param var1 返回的对象类型
         * @param index 索引
         * @param map 条件map,当前条件操作全部设置为 term Query
         * @return List<T>
         * @throws IOException 异常
         */
        List<T> getListByAndMap(Class<T> var1, String index, Map<String,Object> map) throws IOException;
    
        /**
         * 根据 map 条件查询列表
         *   当前查询操作条件均为 and term
         * @param var1 返回的对象类型
         * @param index 索引
         * @param map 条件map,当前条件操作全部设置为 term Query
         * @param sortOrderMap 排序Map -> k,v 分别表示排序 字段名称 和 值({@link org.elasticsearch.search.sort.SortOrder})
         * @return List<T>
         * @throws IOException 异常
         */
        List<T> getListByAndMap(Class<T> var1, String index, Map<String,Object> map ,Map<String, SortOrder> sortOrderMap) throws IOException;
    
        /**
         * 根据 conditionDos 条件集合 去查询列表(仅拼接条件)
         *   因为es不支持查询全部,必须的分页查询,所以查询所有也是用的分页
         * @param var1 返回的对象类型
         * @param index 索引
         * @param conditionDos 条件集合{@link ESConditionDTO} 根据该集合对象进行组合
         * @return List<T>
         * @throws IOException 异常 ESException(自定义)
         */
        List<T> getListByCondition(Class<T> var1, String index, List<ESConditionDTO> conditionDos) throws IOException;
    
        /**
         * 根据 searchDto 条件 去查询列表(包含拼接条件与排序)
         *   因为es不支持查询全部,必须的分页查询,所以查询所有也是用的分页
         * @param var1 返回的对象类型
         * @param index 索引
         * @param searchDto {@link ESSearchDto} 根据该对象进行组合
         * @return List<T>
         * @throws IOException 异常 ESException(自定义)
         */
        List<T> getListByCondition(Class<T> var1, String index, ESSearchDto searchDto) throws IOException;
    
        /**
         * 根据条件进行分页查询
         *   {@link PageRequest} 三种分页方案, 资料博客: https://blog.csdn.net/pony_maggie/article/details/105478557
         *   1: {@link com.blacktea.es.entites.dto.RequestFromSizePage} from-size -> 占用空间大,可以指定页数,但是目前默认最大仅支持10000以内的分页
         *   2: {@link com.blacktea.es.entites.dto.RequestScrollPage} scroll-> 效率高,不可以指定页数且非实时,但是可以查询大量数据,例如 10000以上的list查询使用!
         *   3: {@link com.blacktea.es.entites.dto.RequestSearchAfterPage} Search_After -> 需要进行很深度的分页,但是可以不指定页数翻页,只要可以实时请求下一页就行。比如一些实时滚动的场景。
         *
         *  注意: 不建议你去使用除 from-size 外的分页方法去实现指定页数跳转!
         * @param var1 返回的对象类型
         * @param index 索引
         * @param esSearchDto {@link ESSearchDto} 根据该对象进行组合
         * @param pageRequest 分页对象,目前共三种 {@link PageRequest} 实现
         * @return Page<T>
         * @throws IOException 异常 ESException(自定义)
         */
        Page<T> getPageByCondition(Class<T> var1, String index, ESSearchDto esSearchDto, PageRequest pageRequest) throws IOException;
    
        /**
         * 根据条件进行分页查询
         *   {@link com.blacktea.es.entites.dto.RequestFromSizePage} from-size -> 占用空间大,可以指定页数,但是目前默认最大仅支持10000以内的分页
         * @param var1 返回的对象类型
         * @param index 索引
         * @param esSearchDto {@link ESSearchDto} 根据该对象进行组合
         * @param requestFromSizePage 分页对象,{@link RequestFromSizePage}
         * @return Page<T>
         * @throws IOException 异常 ESException(自定义)
         */
        Page<T> getPageFromSizeByCondition(Class<T> var1, String index, ESSearchDto esSearchDto, RequestFromSizePage requestFromSizePage) throws IOException;
    
        /**
         *  获取指定条件下的数据集合数量(array.size)
         * @param idnex 索引
         * @param query 查询对象, 一般为 SearchSourceBuilder.queryBuilder
         * @return long
         * @throws IOException 异常
         */
        Long count(String idnex, QueryBuilder query) throws IOException;
    }
    

    @ConditionalOnXXXX系列常用注解

    @ConditionalOnBean:当给定的在bean存在时,则实例化当前Bean
        
    @ConditionalOnMissingBean:当给定的在bean不存在时,则实例化当前Bean
        
    @ConditionalOnClass:当前classpath下存在指定类,则实例化当前Bean
        
    @ConditionalOnMissingClass:当前classpath下不存在指定类,则实例化当前Bean
        
    @ConditionalOnExpression:依赖于SpEL表达式值的条件元素的配置注释,条件为true,则实例化当前bean。如:@ConditionalOnExpression("${redis.enabled}==1&&${redis.cluster.enabled:true}&&'${redis.name}'.equals('myredis')")
        
    @ConditionalOnWebApplication:当Spring为web服务时,才使注解的类生效;通常是配置类;
        
    @ConditionalOnProperty:通过@ConditionalOnProperty控制配置类是否生效,可以将配置与代码进行分离,实现了更好的控制配置。如:@ConditionalOnProperty(prefix = "filter",name = "loginFilter",havingValue = "true"),配置文件代码为filter.loginFilter=true。@ConditionalOnProperty 实现是通过 havingValue 与配置文件中的值对比,返回为true则配置类生效,反之失效。
        
    @ConditionalOnResource:仅当指定的资源在类路径上时才生效。如:@ConditionalOnResource(resources="classpath:jdbc.properties")
    
    
    

    感谢

    spring-boot 官网

    es 官网

    https://blog.csdn.net/qq_33375499/article/details/106711248

    https://www.jianshu.com/p/733e7e1e4de5

    b 站 狂神说java

    相关文章

      网友评论

        本文标题:spring-boot 整合elasticsearch 7.x(

        本文链接:https://www.haomeiwen.com/subject/pznowltx.html