SpringBoot使用JestClient操作ElasticSearch

项目配置

项目中使用了Apollo进行配置管理

application.properties中配置如下

spring.elasticsearch.jest.uris = http://test-es.umgsai.com
spring.elasticsearch.jest.read-timeout = 3s
spring.elasticsearch.jest.connection-timeout = 2s

配置好之后就可以在项目中通过Resource注解拿到JestClient实例了


创建索引

创建index:POST http://localhost:8080/es/createIndex?index=main_order

@RequestMapping("/es")
@RestController
public class EsController {

    @Resource
    protected JestClient jestClient;

    @RequestMapping(value = "/createIndex", method = RequestMethod.POST)
    public Object createIndex(String index) throws IOException {
        CreateIndex createIndex = new CreateIndex.Builder(index).build();
        JestResult result = jestClient.execute(createIndex);
        if (result.isSucceeded()) {
            return Response.success(null);
        }
        return Response.fail(Code.ERROR_PARAM.getCode(), result.getErrorMessage());
    }
}

删除索引

删除index:DELETE http://localhost:8080/es/deleteIndex?index=main_order

@RequestMapping(value = "/deleteIndex", method = RequestMethod.DELETE)
public Object deleteIndex(String index) throws IOException {
    DeleteIndex deleteIndex = new DeleteIndex.Builder(index).build();
    JestResult result = jestClient.execute(deleteIndex);
    if (result.isSucceeded()) {
        return Response.success(null);
    }
    return Response.fail(Code.ERROR_PARAM.getCode(), result.getErrorMessage());
}

创建Mapping

PUT http://localhost:8080/es/putMapping

参数名
indexmain_order
typemain_order
mapping如下JSON
{
    "main_order":{
        "properties":{
            "orderId":{
                "type":"keyword"
            },
            "tags":{
                "type":"long"
            },
            "version":{
                "type":"integer"
            },
            "createTime":{
                "type":"date",
                "format":"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
            },
            "updateTime":{
                "type":"date",
                "format":"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
            }
        }
    }
}
@RequestMapping(value = "/putMapping", method = RequestMethod.PUT)
public Object putMapping(String index, String type, String mapping) throws IOException {
    PutMapping putMapping = new PutMapping.Builder(
            index,
            type,
            mapping
    ).build();

    JestResult result = jestClient.execute(putMapping);
    if (result.isSucceeded()) {
        return Response.success(null);
    }
    return Response.fail(Code.ERROR_PARAM.getCode(), result.getErrorMessage());
}

查询Mapping
@RequestMapping(value = "/getMapping", method = RequestMethod.GET)
public Object getMapping(String index) throws IOException {

    Action getMapping = new GetMapping.Builder().addIndex(index).build();
    JestResult result = jestClient.execute(getMapping);

    if (result.isSucceeded()) {
        return Response.success(JSON.parse(result.getJsonString()));
    }
    return Response.fail(Code.ERROR_PARAM.getCode(), result.getErrorMessage());
}

ES不支持删除mapping,官方建议先删除一个index,然后重新创建index,重新创建mapping

 https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-delete-mapping.html
 It is no longer possible to delete the mapping for a type. Instead you should delete the index and recreate it with the new mappings.

异步写入数据
public <DATA> Response<Object> insertOrUpdate(String indexName, String typeName, List<DATA> dataList, String id, JestResultHandler<JestResult> jestResultHandler) {
    Bulk.Builder bulk = new Bulk.Builder();
    for (DATA data : dataList) {
        Index index = new Index.Builder(data).
                index(indexName).
                type(typeName).
                id(id).
                build();
        bulk.addAction(index);
    }
    jestClient.executeAsync(bulk.build(), jestResultHandler);
    return Response.success(null);
}

同步写入数据
    public <DATA> JestResult insertOrUpdate(String indexName, String typeName, List<DATA> dataList, String id) throws Exception {
        Bulk.Builder bulk = new Bulk.Builder();
        for (DATA data : dataList) {
            Index index = new Index.Builder(data).
                    index(indexName).
                    type(typeName).
                    id(id).
                    build();
            bulk.addAction(index);
        }
        return jestClient.execute(bulk.build());
    }

更新
public JestResult update(String indexName, String typeName, String id, Map<String, Object> paramMap) throws Exception {
    Map<String, Object> docMap = Maps.newHashMap();
    docMap.put("doc", paramMap);
    final Update update = new Update.Builder(docMap).id(id).build();
    final Bulk bulk = new Bulk.Builder()
            .addAction(update).defaultIndex(indexName).defaultType(typeName)
            .build();
    return jestClient.execute(bulk);
}

删除
// 异步删除
public Response<Object> deleteByKeyList(String indexName, String typeName, String deleteKeyName, List<String> deleteKeyList, JestResultHandler<JestResult> jestResultHandler) {
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery().filter(QueryBuilders.termsQuery(deleteKeyName, deleteKeyList));
    DeleteByQuery deleteByQuery = new DeleteByQuery.Builder(new SearchSourceBuilder().query(boolQueryBuilder).toString())
            .addIndex(indexName)
            .addType(typeName)
            .refresh(true).build();
    jestClient.executeAsync(deleteByQuery, jestResultHandler);
    return Response.success(null);
}

// 同步删除
public JestResult deleteByKeyList(String indexName, String typeName, String deleteKeyName, List<String> deleteKeyList) throws Exception {
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery().filter(QueryBuilders.termsQuery(deleteKeyName, deleteKeyList));
    DeleteByQuery deleteByQuery = new DeleteByQuery.Builder(new SearchSourceBuilder().query(boolQueryBuilder).toString())
            .addIndex(indexName)
            .addType(typeName)
            .refresh(true).build();
    return jestClient.execute(deleteByQuery);
}

// 按文档ID异步删除
public Response<Object> deleteById(String indexName, String typeName, String id, JestResultHandler<JestResult> jestResultHandler) {
    Delete.Builder builder = new Delete.Builder(id);
    Delete delete = builder.index(indexName).type(typeName).build();
    jestClient.executeAsync(delete, jestResultHandler);
    return Response.success(null);
}

查询数据

public <T> Response<PageResult<T>> query(BoolQueryBuilder boolQueryBuilder, String indexName, String typeName, Class<T> sourceType,
                                         String orderBy, Boolean desc, List<String> fieldList, Integer pageNum, Integer pageSize) {
    String query = "";
    Transaction transaction = Cat.newTransaction(TransactionType.ES_SEARCH_DATA, indexName);
    try {
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(boolQueryBuilder);
        if (CollectionUtils.isNotEmpty(fieldList)) {
            //指定查询字段,不传则查询全部
            searchSourceBuilder.fetchSource(fieldList.toArray(new String[0]), null);
        }
        EsBuilder.pageParamBuilder(searchSourceBuilder, orderBy, desc, pageNum, pageSize);
        query = searchSourceBuilder.toString();
        int from = searchSourceBuilder.from();
        if (from > esSearchMaxFrom) {
            String errorMsg = String.format("从ES查询数据出现错误,indexName=%s, typeName=%s, query=%s, msg=不允许深度分页", indexName, typeName, query);
            log.error(errorMsg);
            orderErrorHandler.save("", OrderErrorTypeEnum.SEARCH_ES_ERROR, errorMsg);
            return Response.fail(Code.ERROR_PARAM.getCode(), "不允许深度分页,请检查参数");
        }
        log.info("SearchManager.query, indexName={}, typeName={}, query={}", indexName, typeName, query);
        Search search = new Search.Builder(query).addIndex(indexName).addType(typeName).build();
        SearchResult searchResult = jestClient.execute(search);
        if (!searchResult.isSucceeded()) {
            OrderSearchException orderSearchException = new OrderSearchException(ErrorCode.QUERY_ERROR);
            transaction.setStatus(orderSearchException);
            String errorMsg = String.format("从ES查询数据出现错误,indexName=%s, typeName=%s, query=%s, msg=%s", indexName, typeName, query, searchResult.getErrorMessage());
            log.error(errorMsg, orderSearchException);
            orderErrorHandler.save("", OrderErrorTypeEnum.SEARCH_ES_ERROR, errorMsg);
            return Response.fail(ErrorCode.QUERY_ERROR.getCode(), searchResult.getErrorMessage());
        }
        transaction.setStatus(Transaction.SUCCESS);
        long totalCount = searchResult.getTotal();
        PageResult<T> pageResult = new PageResult<>();
        pageResult.setCount(totalCount);
        List<T> resultList = Lists.newArrayList();
        List<SearchResult.Hit<T, Void>> hits = searchResult.getHits(sourceType);
        if (CollectionUtils.isNotEmpty(hits)) {
            for (SearchResult.Hit<T, Void> hit : hits) {
                resultList.add(hit.source);
            }
        }
        pageResult.setList(resultList);
        pageResult.setEnd(CollectionUtils.size(resultList) < searchSourceBuilder.size());
        return Response.success(pageResult);
    } catch (Exception e) {
        transaction.setStatus(e);
        String errorMsg = String.format("从ES查询数据出现异常,indexName=%s, typeName=%s, query=%s, msg=%s", indexName, typeName, query, e.getMessage());
        orderErrorHandler.save("", OrderErrorTypeEnum.SEARCH_ES_ERROR, errorMsg);
        log.error(errorMsg, e);
        return Response.fail(ErrorCode.QUERY_ERROR.getCode(), errorMsg);
    } finally {
        transaction.complete();
    }
}

查询数量
public Response<Long> queryCount(BoolQueryBuilder boolQueryBuilder, String indexName, String typeName) {
    String query = "";
    Transaction transaction = Cat.newTransaction(TransactionType.ES_SEARCH_COUNT, indexName);
    try {
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(boolQueryBuilder);
        query = searchSourceBuilder.toString();
        Count count = new Count.Builder()
                .addIndex(indexName)
                .addType(typeName)
                .query(query)
                .build();
        log.info("SearchManager.queryCount, indexName={}, typeName={}, query={}", indexName, typeName, query);
        CountResult countResult = jestClient.execute(count);
        if (!countResult.isSucceeded()) {
            OrderSearchException orderSearchException = new OrderSearchException(ErrorCode.QUERY_ERROR);
            transaction.setStatus(orderSearchException);
            String errorMsg = String.format("从ES查询数量出现错误,indexName=%s, typeName=%s, query=%s, msg=%s", indexName, typeName, query, countResult.getErrorMessage());
            orderErrorHandler.save("", OrderErrorTypeEnum.SEARCH_ES_ERROR, errorMsg);
            log.error(errorMsg, orderSearchException);
            return Response.fail(ErrorCode.QUERY_ERROR.getCode(), countResult.getErrorMessage());
        }
        Double resultsCount = countResult.getCount();
        transaction.setStatus(Transaction.SUCCESS);
        return Response.success(resultsCount.longValue());
    } catch (Exception e) {
        transaction.setStatus(e);
        String errorMsg = String.format("从ES查询数量出现异常,indexName=%s, typeName=%s, query=%s, msg=%s", indexName, typeName, query, e.getMessage());
        log.error(errorMsg, e);
        orderErrorHandler.save("", OrderErrorTypeEnum.SEARCH_ES_ERROR, errorMsg);
        return Response.fail(ErrorCode.QUERY_ERROR.getCode(), errorMsg);
    } finally {
        transaction.complete();
    }
}

JestClient连接池

JestClient是基于HttpClient的,使用了http连接池和ES服务端进行通信。
httpclient通过maxTotal和defaultMaxPerRoute来配置连接池。
maxTotal:设置连接池的最大连接数,即整个池子的大小。初始值20
defaultMaxPerRoute:设置每一个路由的最大连接数,这里的路由是指IP+PORT,例如连接池大小(MaxTotal)设置为300,路由连接数设置为200(DefaultMaxPerRoute),对于www.a.com与www.b.com两个路由来说,发起服务的主机连接到每个路由的最大连接数(并发数)不能超过200,两个路由的总连接数不能超过300。初始值2.
在defaultMaxPerRoute小于maxTotal时,主要是defaultMaxPerRoute起作用,因为在我这个例子中只有一个 ip+port
在这里插入图片描述
设置maxTotal和defaultMaxPerRoute的方法

@Slf4j
@Component
public class HttpSettingsCustomizer implements HttpClientConfigBuilderCustomizer {
 
    //Apollo配置
    @Value("${es.max.total.connection:100}")
    private Integer maxTotalConnection;
    @Value("${es.default.max.total.connection.per.route:35}")
    private Integer defaultMaxTotalConnectionPerRoute;
 
    @Override
    public void customize(HttpClientConfig.Builder builder) {
        log.info("set maxTotalConnection={}, defaultMaxTotalConnectionPerRoute={}", maxTotalConnection, defaultMaxTotalConnectionPerRoute);
        builder.maxTotalConnection(maxTotalConnection).defaultMaxTotalConnectionPerRoute(defaultMaxTotalConnectionPerRoute);
    }
}

参数设置过程可参考JestAutoConfiguration源码

@Configuration
@ConditionalOnClass(JestClient.class)
@EnableConfigurationProperties(JestProperties.class)
@AutoConfigureAfter(GsonAutoConfiguration.class)
public class JestAutoConfiguration {
 
   private final JestProperties properties;
 
   private final ObjectProvider<Gson> gsonProvider;
 
   private final List<HttpClientConfigBuilderCustomizer> builderCustomizers;
 
   public JestAutoConfiguration(JestProperties properties, ObjectProvider<Gson> gson,
         ObjectProvider<List<HttpClientConfigBuilderCustomizer>> builderCustomizers) {
      this.properties = properties;
      this.gsonProvider = gson;
      this.builderCustomizers = builderCustomizers.getIfAvailable();
   }
 
   @Bean(destroyMethod = "shutdownClient")
   @ConditionalOnMissingBean
   public JestClient jestClient() {
      JestClientFactory factory = new JestClientFactory();
      factory.setHttpClientConfig(createHttpClientConfig());
      return factory.getObject();
   }
 
   protected HttpClientConfig createHttpClientConfig() {
      HttpClientConfig.Builder builder = new HttpClientConfig.Builder(
            this.properties.getUris());
      PropertyMapper map = PropertyMapper.get();
      map.from(this.properties::getUsername).whenHasText().to((username) -> builder
            .defaultCredentials(username, this.properties.getPassword()));
      Proxy proxy = this.properties.getProxy();
      map.from(proxy::getHost).whenHasText().to((host) -> {
         Assert.notNull(proxy.getPort(), "Proxy port must not be null");
         builder.proxy(new HttpHost(host, proxy.getPort()));
      });
      map.from(this.gsonProvider::getIfUnique).whenNonNull().to(builder::gson);
      map.from(this.properties::isMultiThreaded).to(builder::multiThreaded);
      map.from(this.properties::getConnectionTimeout).whenNonNull()
            .asInt(Duration::toMillis).to(builder::connTimeout);
      map.from(this.properties::getReadTimeout).whenNonNull().asInt(Duration::toMillis)
            .to(builder::readTimeout);
      customize(builder);
      return builder.build();
   }
 
   private void customize(HttpClientConfig.Builder builder) {
      if (this.builderCustomizers != null) {
         for (HttpClientConfigBuilderCustomizer customizer : this.builderCustomizers) {
            customizer.customize(builder);
         }
      }
   }
 
}

参考:https://www.jianshu.com/p/df5d89955eaa
https://docs.spring.io/spring-boot/docs/2.0.3.RELEASE/reference/htmlsingle/#boot-features-elasticsearch

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值