SpringBoot 集成 Elasticsearch 7.7.1实现基础增删改查功能

因为最近项目中需要用到Elasticsearch7的新特性,并且环境中只有7.7.1版本的组件可以用,所以弃用了spring-boot-starter-data-elasticsearch(请注意:SpringBoot是2.2.0.RELEASE才兼容elasticsearch 7.x)。

本Demo使用elasticsearch原生客户端,模仿spring-boot-starter-data-elasticsearch实现一套相简单的增删改查功能,目的是满足业务上的面对es版本灵活切换,大家如果有相似需求可以参考此式例。

先来添加引用

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>${elasticsearch.version}</version>
    <exclusions>
        <exclusion>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-client</artifactId>
    <version>${elasticsearch.version}</version>
</dependency>
<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>${elasticsearch.version}</version>
</dependency>

指定版本

<elasticsearch.version>7.7.1</elasticsearch.version>

创建es配置文件
EsProp.java

@Data
@Component
@ConfigurationProperties(prefix = "elasticsearch")
public class EsProp {
    
    
    /**
     * 是否开启es
     */
    private Boolean enabled;
    /**
     * 使用冒号隔开ip和端口
     */
    private String[] address;
    /**
     * 用户名
     */
    private String username;
    /**
     * 密码
     */
    private String password;
    /**
     * 7.X 设置为http
     */
    private String scheme;
}

EsConfiguration.java

@Slf4j
@Component
@Configuration
public class EsConfiguration {
    
    

    //权限验证
    final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();

    @Autowired
    private EsProp esProp;

    @Bean
    public RestClientBuilder restClientBuilder() {
    
    
        HttpHost[] hosts = Arrays.stream(esProp.getAddress())
                .map(this::makeHttpHost)
                .filter(Objects::nonNull)
                .toArray(HttpHost[]::new);
        log.debug("hosts:{}", Arrays.toString(hosts));
        //配置权限验证
        credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(esProp.getUsername(), esProp.getPassword()));
        RestClientBuilder restClientBuilder = RestClient.builder(hosts).setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
    
    
            @Override
            public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {
    
    
                return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
            }
        });
        return restClientBuilder;
    }

    @Bean(name = "restHighLevelClient")
    public RestHighLevelClient highLevelClient(@Autowired RestClientBuilder restClientBuilder) {
    
    
//        restClientBuilder.setMaxRetryTimeoutMillis(60000);
        return new RestHighLevelClient(restClientBuilder);
    }

    /**
     * 处理请求地址
     *
     * @param s
     * @return HttpHost
     */
    private HttpHost makeHttpHost(String s) {
    
    
        String[] address = s.split(":");
        if (address.length == address.length) {
    
    
            String ip = address[0];
            int port = Integer.parseInt(address[1]);
            return new HttpHost(ip, port, esProp.getScheme());
        } else {
    
    
            return null;
        }
    }
}

创建注解类@EsDocument,用以标注索引名称和属性

@Target({
    
    ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface EsDocument {
    
    

    String indexName();

    int shards() default 1;

    int replicas() default 0;

    String mappingId() default "id";
}

创建注解类@EsField,用以标注字段名称和属性

@Target({
    
    ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EsField {
    
    

    String name() default "";

    String type() default "";

    int dims() default 0;

    boolean fielddata() default false;
}

创建字段类型常量类EsFieldType.java,这里我只实现了几个用到的类型,大家可以自行扩展

public class EsFieldType {
    
    

    public static final String TEXT = "text";
    public static final String INTEGER = "integer";
    public static final String LONG = "long";
    public static final String DOUBLE = "double";
    public static final String DATE = "date";
    public static final String DENSE_VECTOR = "dense_vector";
}

下面实现一套基础的增删改查基础实现

创建BaseEntity.java

@Data
public class BaseEsEntity implements Serializable {
    
    

    @EsField(name = "id")
    private String id;

    @EsField(name = "creat_date", type = EsFieldType.DATE)
    private Date createDate;
}

创建BaseEsService.java

public interface BaseEsService<T, ID> {
    
    

    /**
     * 创建索引
     */
    void createIndex();

    /**
     * 删除索引
     */
    void deleteIndex();

    /**
     * 判断索引是否存在
     * @return
     * @throws Exception
     */
    boolean isExistsIndex() throws Exception;

    /**
     * 插入
     * @param entity
     */
    void index(T entity);

    /**
     * 插入(异步)
     * @param entity
     */
    void indexAsync(T entity);

    /**
     * 批量插入
     * @param list
     */
    void insertBatch(List<T> list);

    /**
     * 批量删除
     * @param idList
     */
    void deleteBatch(Collection<T> idList);

    /**
     * 条件删除
     * @param builder
     */
    void deleteByQuery(QueryBuilder builder);

    /**
     * 条件查询
     * @param builder
     * @return
     */
    List<T> search(SearchSourceBuilder builder);

创建BaseEsServiceImpl.java

@Slf4j
public class BaseEsServiceImpl<T extends BaseEsEntity, ID> {
    
    

    protected Class<T> entityClass;

    protected String indexName;
    protected int shards;
    protected int replicas;
    protected String mappingId;

    public BaseEsServiceImpl() {
    
    
        EsDocument annotation = resolveReturnedClassFromGenericType().getAnnotation(EsDocument.class);
        this.indexName = annotation.indexName();
        this.mappingId = annotation.mappingId();
        this.shards = annotation.shards();
        this.replicas = annotation.replicas();
    }

    /**
     * 创建索引
     */
    public void createIndex() {
    
    
        log.info("es 创建索引:{}", getIndexName());
        try {
    
    
            if (this.isExistsIndex()) {
    
    
                log.error(" idxName={} 已经存在", getIndexName());
                return;
            }
            CreateIndexRequest request = new CreateIndexRequest(getIndexName());
            buildSetting(request);
            buildMapping(request);
            CreateIndexResponse res = restHighLevelClient().indices().create(request, RequestOptions.DEFAULT);
            if (!res.isAcknowledged()) {
    
    
                throw new RuntimeException("初始化失败");
            } else {
    
    
                log.info("es 索引创建成功: {}", getIndexName());
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
            System.exit(0);
        }
    }

    /**
     * 删除索引
     */
    public void deleteIndex() {
    
    
        deleteIndex(getIndexName());
    }

    /**
     * 删除索引
     *
     * @param idxName
     */
    protected void deleteIndex(String idxName) {
    
    
        try {
    
    
            if (!this.isExistsIndex()) {
    
    
                log.error("DELETE Index error, idxName={} 不存在.", idxName);
                return;
            }
            restHighLevelClient().indices().delete(new DeleteIndexRequest(idxName), RequestOptions.DEFAULT);
            log.info("DELETE Index [{}].", idxName);
        } catch (Exception e) {
    
    
            throw new RuntimeException(e);
        }
    }

    /**
     * 索引是否存在
     *
     * @return
     * @throws Exception
     */
    public boolean isExistsIndex() throws Exception {
    
    
        return isExistsIndex(getIndexName());
    }

    /**
     * 索引是否存在
     *
     * @param idxName
     * @return
     * @throws Exception
     */
    protected boolean isExistsIndex(String idxName) throws Exception {
    
    
        return restHighLevelClient().indices().exists(new GetIndexRequest(idxName), RequestOptions.DEFAULT);
    }

    /**
     * 保存对象
     *
     * @param entity
     */
    public void index(T entity) {
    
    
        IndexRequest request = new IndexRequest(indexName);
        request.id(entity.getId());
        request.type("_doc");
        request.source(JSONObject.toJSONString(entity), XContentType.JSON);
//        request.source(entity, XContentType.JSON);
        try {
    
    
            restHighLevelClient().index(request, RequestOptions.DEFAULT);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    /**
     * 保存对象(异步)
     *
     * @param entity
     */
    public void indexAsync(T entity) {
    
    
        IndexRequest request = new IndexRequest();
        request.id(entity.getId());
        ActionListener listener = new ActionListener() {
    
    
            @Override
            public void onResponse(Object o) {
    
    
            }

            @Override
            public void onFailure(Exception e) {
    
    
            }
        };
        try {
    
    
            restHighLevelClient().indexAsync(request, RequestOptions.DEFAULT, listener);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            throw new RuntimeException(e);
        }

    }

    /**
     * 批量插入
     *
     * @param list
     */
    public void insertBatch(List<T> list) {
    
    
        BulkRequest request = new BulkRequest();
        list.forEach(item -> request.add(new IndexRequest(getIndexName()).id(item.getId())
                .source(JSONObject.toJSONString(item), XContentType.JSON)));
        try {
    
    
            restHighLevelClient().bulk(request, RequestOptions.DEFAULT);
        } catch (Exception e) {
    
    
            throw new RuntimeException(e);
        }
    }

    /**
     * 批量删除
     *
     * @param idList
     */
    public void deleteBatch(Collection<T> idList) {
    
    
        BulkRequest request = new BulkRequest();
        idList.forEach(item -> request.add(new DeleteRequest(getIndexName(), item.toString())));
        try {
    
    
            restHighLevelClient().bulk(request, RequestOptions.DEFAULT);
        } catch (Exception e) {
    
    
            throw new RuntimeException(e);
        }
    }

    /**
     * 根据条件删除
     *
     * @param builder
     */
    public void deleteByQuery(QueryBuilder builder) {
    
    
        DeleteByQueryRequest request = new DeleteByQueryRequest(getIndexName());
        request.setQuery(builder);
        //设置批量操作数量,最大为10000
        request.setBatchSize(10000);
        request.setConflicts("proceed");
        try {
    
    
            restHighLevelClient().deleteByQuery(request, RequestOptions.DEFAULT);
        } catch (Exception e) {
    
    
            throw new RuntimeException(e);
        }
    }

    /**
     * 查询
     *
     * @param builder
     * @return
     */
    public List<T> search(SearchSourceBuilder builder) {
    
    
        SearchRequest request = new SearchRequest(getIndexName());
        request.source(builder);
        try {
    
    
            SearchResponse response = restHighLevelClient().search(request, RequestOptions.DEFAULT);
            SearchHit[] hits = response.getHits().getHits();
            List<T> res = new ArrayList<>(hits.length);
            for (SearchHit hit : hits) {
    
    
                res.add(JSON.parseObject(hit.getSourceAsString(), getEntityClass()));
            }
            return res;
        } catch (Exception e) {
    
    
            throw new RuntimeException(e);
        }
    }

    /**
     * 设置分片&副本
     *
     * @param request
     */
    private void buildSetting(CreateIndexRequest request) {
    
    
        request.settings(Settings.builder().put("index.number_of_shards", shards)
                .put("index.number_of_replicas", replicas));
    }

    /**
     * 设置Mapping
     *
     * @param request
     */
    private void buildMapping(CreateIndexRequest request) {
    
    
        JSONObject properties = new JSONObject();
        Field[] fields = getAllFields(getEntityClass());
        Arrays.stream(fields).forEach(field -> {
    
    
            EsField annotation = field.getAnnotation(EsField.class);
            if (null != annotation) {
    
      //判断字段是否包含EsField注解
                String name = StringUtils.isEmpty(annotation.name()) == true ? field.getName() : annotation.name();
                String type = StringUtils.isEmpty(annotation.type()) == true ? "text" : annotation.type();
                boolean fielddata = annotation.fielddata();
                int dims = annotation.dims();
                JSONObject attribute = new JSONObject();
                attribute.put("type", type);
                if (fielddata) {
    
    
                    attribute.put("fielddata", fielddata);
                }
                if (dims != 0) {
    
    
                    attribute.put("dims", dims);
                }
                properties.put(name, attribute);
            }
        });
        JSONObject json = new JSONObject();
        json.put("properties", properties);
        log.info(json.toJSONString());
        request.mapping(json.toJSONString(), XContentType.JSON);
    }

    private Field[] getAllFields(Class<?> clazz) {
    
    
        List<Field> fieldList = new ArrayList<>();
        while (clazz != null) {
    
    
            fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
            clazz = clazz.getSuperclass();
        }
        Field[] fields = new Field[fieldList.size()];
        fieldList.toArray(fields);
        return fields;
    }

    public RestHighLevelClient restHighLevelClient() {
    
    
        return (RestHighLevelClient) SpringUtils.getObject("restHighLevelClient");
    }

    @SuppressWarnings("unchecked")
    private Class<T> resolveReturnedClassFromGenericType() {
    
    
        ParameterizedType parameterizedType = resolveReturnedClassFromGenericType(getClass());
        return (Class<T>) parameterizedType.getActualTypeArguments()[0];
    }

    private ParameterizedType resolveReturnedClassFromGenericType(Class<?> clazz) {
    
    
        Object genericSuperclass = clazz.getGenericSuperclass();
        if (genericSuperclass instanceof ParameterizedType) {
    
    
            ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
            return parameterizedType;
        }
        return resolveReturnedClassFromGenericType(clazz.getSuperclass());
    }

    protected Class<T> getEntityClass() {
    
    
        if (null == entityClass) {
    
    
            try {
    
    
                this.entityClass = resolveReturnedClassFromGenericType();
            } catch (Exception e) {
    
    
                throw new RuntimeException("Unable to resolve EntityClass. Please use according setter!", e);
            }
        }
        return entityClass;
    }

    protected String getIndexName() {
    
    
        return indexName;
    }

    protected String getMappingId() {
    
    
        return mappingId;
    }
}

好了,接下来我们就可以使用它实现表索引创建和基础的增删改查操作了。
这里复杂一点的查询操作我选择用DSL实现,后期会针对业务使用RestHighLevelClient做出一些拓展,将会维护到我的github。
如果大家有一些建议和想法请与我留言。

猜你喜欢

转载自blog.csdn.net/bluerebel/article/details/110436654