Spring-data 整合 Elasticsearch

最近在项目使用了Elasticsearch,整理如下的整合流程。

一. 版本的匹配

根据自己项目要求的版本进行匹配

我们这里用Spring Boot 2.2.0

二. 导入引用的Spring-data-elasticsearch

<!-- https://docs.spring.io/spring-data/elasticsearch/docs/3.2.x/reference/html -->
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-elasticsearch</artifactId>
        <version>3.2.0.RELEASE</version>
    </dependency>

三. Elasticsearch Config

@Configuration
@ConfigurationProperties(prefix = "elastic-search")
@EnableElasticsearchRepositories(
    basePackages = "com.XX.repository")
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration {

  private String host;
  private int port;
  private String userName;
  private String password;
  private String certificateBase64;
  private static final String HTTPS = "https";

  @Autowired
  private LogService logService;

  @Bean
  @Override
  public RestHighLevelClient elasticsearchClient() {
    if (StringUtils.isEmpty(certificateBase64)) {
      logService.info(getClass(), "Run the mode of JRE installation certificate.");
      final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
      credentialsProvider.setCredentials(AuthScope.ANY,
          new UsernamePasswordCredentials(userName, password));

      RestClientBuilder builder = RestClient.builder(new HttpHost(host, port, HTTPS))
          .setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder
              .setDefaultCredentialsProvider(credentialsProvider));

      return new RestHighLevelClient(builder);
    } else {
      ClientConfiguration clientConfiguration = ClientConfiguration.builder()
          .connectedTo(host + ":" + port).usingSsl(this.getSSLContext(certificateBase64))
          .withBasicAuth(userName, password).build();
      return RestClients.create(clientConfiguration).rest();
    }
  }

  @Bean
  @Override
  public EntityMapper entityMapper() {
    ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(
        elasticsearchMappingContext(), new DefaultConversionService());
    entityMapper.setConversions(elasticsearchCustomConversions());

    return entityMapper;
  }

  private SSLContext getSSLContext(String base64Cert) {
    SSLContext sslContext = null;
    try {
      sslContext = SSLContext.getInstance("TLSv1.2");
      InputStream is = new ByteArrayInputStream(Base64.getDecoder().decode(base64Cert));
      CertificateFactory cf = CertificateFactory.getInstance("X.509");
      X509Certificate caCert = (X509Certificate) cf.generateCertificate(is);
      TrustManagerFactory tmf =
          TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
      KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
      ks.load(null);
      ks.setCertificateEntry("caCert", caCert);
      tmf.init(ks);
      sslContext.init(null, tmf.getTrustManagers(), null);
    } catch (Exception e) {
      logService.error(getClass(), "load ssl error." + e.getMessage());
    }
    return sslContext;
  }

四. 写实体类和Repository

  • Entity
@Document(indexName = "es_knowledge", type = "knowledge")
public class KnowledgeEs {
  
  @Id
  private String id;
  private String updatedBy;
  private Date updatedOn;
  private Map<String, Integer> ratings = new HashMap<>();
  • CrudRepository
public interface KnowledgeEsRepository extends ElasticsearchRepository<KnowledgeEs, String> {

  @Query("{\"bool\" : {\"must\" : {\"field\" : {\"subject.keyword\" : \"?\"}}}}")
  Page<KnowledgeEs> findBySubject(String subject, Pageable pageable);

  @Query("{\"bool\" : {\"must\" : {\"field\" : {\"content.keyword\" : \"?\"}}}}")
  Page<KnowledgeEs> findByContent(String content, Pageable pageable);
}
  • CustomRepository 利用了RestHighLevelClient的rest client
    里面包含(权重,高亮)的查询
@Repository
public class KnowledgeCustomEsRepositoryImpl implements KnowledgeCustomEsRepository {

  @Autowired
  RestHighLevelClient highLevelClient;

 @Override
  public List<Knowledge> getKnowledgesVisible(String enterpriseId, String teamId, String channelId,
      String keyword) {
    SearchRequest searchRequest = new SearchRequest(INDEX);
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
    // search by keyword
    this.enrichKeywordQuery(keyword, boolQueryBuilder);

    // Boost
    this.enrichShouldBoostQuery(boolQueryBuilder, enterpriseId, teamId, channelId);

    sourceBuilder.query(boolQueryBuilder);

    // Highlight
    this.enrichHighlightBuilder(sourceBuilder);

    searchRequest.source(sourceBuilder);

    SearchResponse response;
    try {
      response = highLevelClient.search(searchRequest, RequestOptions.DEFAULT);
    } catch (IOException e) {
      logService.error(getClass(), "es query fail." + e.getMessage());
      return new ArrayList<>();
    }
    // deal result
    List<Knowledge> knowledges = new ArrayList<>();
    for (SearchHit hit : response.getHits()) {
      try {
        Knowledge knowledge =
            new ObjectMapper().readValue(hit.getSourceAsString(), Knowledge.class);
        Map<String, HighlightField> highlightMap = hit.getHighlightFields();
        if (!highlightMap.isEmpty()) {
          if (highlightMap.get(CONTENT) != null
              && !ArrayUtils.isEmpty(highlightMap.get(CONTENT).getFragments())) {
            knowledge.setContent(highlightMap.get(CONTENT).getFragments()[0].toString());
          }
          if (highlightMap.get(SUBJECT) != null
              && !ArrayUtils.isEmpty(highlightMap.get(SUBJECT).getFragments())) {
            knowledge.setSubject(highlightMap.get(SUBJECT).getFragments()[0].toString());
          }
        }
        knowledge.setId(hit.getId());
        knowledges.add(knowledge);
      } catch (JsonProcessingException e) {
        logService.error(getClass(), "SearchHit to enity excption." + e.getMessage());
      }
    }
    return knowledges;
  }

/**
   * search by keyword
   * 
   * @param filters
   * @param queryBuilder
   */
  private void enrichKeywordQuery(String keyword, BoolQueryBuilder queryBuilder) {
    if (!StringUtils.isEmpty(keyword)) {
      String queryString = keyword.trim();
      if (queryString.contains(",")) {
        String[] queryArray = queryString.split(",");
        for (String value : queryArray) {
          // if query value is a comma separated values, then use AND operator between values
          if (!value.isEmpty()) {
            queryBuilder.must(QueryBuilders.queryStringQuery(value).analyzer(STOP_ANALYZER));
          }
        }
      } else if (queryString.startsWith("\"") && queryString.endsWith("\"")) {
        // exact match
        queryBuilder.must(QueryBuilders.queryStringQuery(queryString));
      } else if (!queryString.contains(" ")) {
        // to prevent elasticsearch from parsing email
        StringBuilder queryValue = new StringBuilder();
        queryValue.append("\"" + queryString + "\"");
        queryBuilder.must(QueryBuilders.queryStringQuery(queryValue.toString()));
      } else {
        queryBuilder.must(QueryBuilders.queryStringQuery(queryString).analyzer(STOP_ANALYZER));
      }
    }
  }

/**
   * HighlightBuilder
   * 
   * @param sourceBuilder
   */
  private void enrichHighlightBuilder(SearchSourceBuilder sourceBuilder) {
    // highlight
    HighlightBuilder highlightBuilder = new HighlightBuilder();
    highlightBuilder.preTags("*");
    highlightBuilder.postTags("*");
    highlightBuilder.fields().addAll(
        Arrays.asList(new HighlightBuilder.Field(SUBJECT), new HighlightBuilder.Field(CONTENT)));
    sourceBuilder.highlighter(highlightBuilder);
  }

/**
   * Boost
   * 
   * @param boolQueryBuilder
   * @param enterpriseId
   * @param teamId
   * @param channelId
   */
  private void enrichShouldBoostQuery(BoolQueryBuilder boolQueryBuilder, String enterpriseId,
      String teamId, String channelId) {
    BoolQueryBuilder shouldBoolQueryBuilder = QueryBuilders.boolQuery();

    // Query according to enterpriseId, teamId, channelId, with the highest weight
    BoolQueryBuilder queryEnterpriseTeamChannel = QueryBuilders.boolQuery();
    queryEnterpriseTeamChannel
        .must(QueryBuilders.termQuery(FieldNames.CHANNEL_ID + KEYWORD, channelId).boost(2.0f))
        .must(QueryBuilders.termQuery(FieldNames.TEAM_ID + KEYWORD, teamId).boost(2.0f))
        .must(StringUtils.isEmpty(enterpriseId)
            ? QueryBuilders.termsQuery(FieldNames.ENTERPRISE_ID + KEYWORD, Arrays.asList("", null))
            : QueryBuilders.termQuery(FieldNames.ENTERPRISE_ID + KEYWORD, enterpriseId)
                .boost(2.0f));
    shouldBoolQueryBuilder.should(queryEnterpriseTeamChannel.boost(2.0f));

    // Query according to enterpriseId, teamId , level is ENTERPRISE ,WORKSPACE
    BoolQueryBuilder queryEnterpriseTeam = QueryBuilders.boolQuery();
    queryEnterpriseTeam.must(QueryBuilders.termQuery(FieldNames.TEAM_ID + KEYWORD, teamId))
        .must(StringUtils.isEmpty(enterpriseId)
            ? QueryBuilders.termsQuery(FieldNames.ENTERPRISE_ID + KEYWORD, Arrays.asList("", null))
            : QueryBuilders.termQuery(FieldNames.ENTERPRISE_ID + KEYWORD, enterpriseId))
        .mustNot(QueryBuilders.termQuery(FieldNames.CHANNEL_ID + KEYWORD, channelId))
        .must(QueryBuilders.termsQuery(FIELD_LEVEL + KEYWORD, Arrays
            .asList(KnowledgeLevel.ENTERPRISE.getName(), KnowledgeLevel.WORKSPACE.getName())));
    shouldBoolQueryBuilder.should(queryEnterpriseTeam);

    // Query according to enterpriseId ,level is ENTERPRISE
    BoolQueryBuilder queryEnterprise = QueryBuilders.boolQuery();
    queryEnterprise
        .must(StringUtils.isEmpty(enterpriseId)
            ? QueryBuilders.termsQuery(FieldNames.ENTERPRISE_ID + KEYWORD, Arrays.asList("", null))
            : QueryBuilders.termQuery(FieldNames.ENTERPRISE_ID + KEYWORD, enterpriseId))
        .mustNot(QueryBuilders.termQuery(FieldNames.TEAM_ID + KEYWORD, teamId))
        .mustNot(QueryBuilders.termQuery(FieldNames.CHANNEL_ID + KEYWORD, channelId))
        .must(QueryBuilders.termQuery(FIELD_LEVEL + KEYWORD, KnowledgeLevel.ENTERPRISE.getName())
            .boost(1.7f));
    shouldBoolQueryBuilder.should(queryEnterprise);

    boolQueryBuilder.must(shouldBoolQueryBuilder.minimumShouldMatch(1));
  }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 224,289评论 6 522
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 95,968评论 3 402
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 171,336评论 0 366
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 60,718评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 69,734评论 6 399
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,240评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,631评论 3 428
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,599评论 0 279
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,139评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,166评论 3 345
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,286评论 1 354
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,917评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,604评论 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,075评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,205评论 1 275
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,814评论 3 381
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,351评论 2 365