栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 前沿技术 > 大数据 > 大数据系统

五.全文检索ElasticSearch经典入门-ElasticSearch Java实战

五.全文检索ElasticSearch经典入门-ElasticSearch Java实战

前言

今天开始我们学习如何使用Java来操作ES,这里会讲两种操作方式,一是使用ES提供的jar包来操作,二是使用SpringBootData来操作,第二种方式是企业里面使用的较多的方式了。

Java 操作ES 环境集成

创建一个普通的Java工程,首先我们需要导入ES的jar包


   org.elasticsearch.client
    transport
    6.8.6


    junit
    junit
    4.12
    compile

然后创建ES的客户端

public class ESClientUtil {

    public static TransportClient getClient(){
        TransportClient client = null;
        Settings settings = Settings.builder().put("cluster.name", "elasticsearch").build();
        try {
            client = new PreBuiltTransportClient(settings).addTransportAddress(
            new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300));
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        return client;
    }

}
添加文档

编写测试类 ,往指定的索引库中添加文档

@Test
public void testAdd() {
  //获取客户端对象
  TransportClient client = ESClientUtil.getClient();

  //创建索引
  IndexRequestBuilder indexRequestBuilder = client.prepareIndex("orders", "_doc", "1");
  Map data = new HashMap<>();
  data.put("id",1);
  data.put("title","买了一个鼠标");
  data.put("count",1);
  data.put("amount",100.00);
  data.put("status",1);
  //获取结果
  IndexResponse indexResponse = indexRequestBuilder.setSource(data).get();
  //打印给过
  System.out.println(indexResponse);
  client.close();
}

打印结果如下 :

IndexResponse[index=orders,type=_doc,id=1,version=18,result=updated,seqNo=19,primaryTerm=3,shards={“total”:2,“successful”:1,“failed”:0}]

获取一个文档
@Test
public void testGet() {
	//获取客户端对象
	TransportClient client = ESClientUtil.getClient();
	//获取文档
	GetResponse response = client.prepareGet("orders", "_doc", "1").get();
	//打印结果
	System.out.println(response);
}

打印结果如下:{amount=100.0, count=1, id=1, title=买了一个鼠标, status=1}

更新一个文档
@Test

public void testUpdate(){
    //获取客户端对象
    TransportClient client = ESClientUtil.getClient();

    //修改索引
    UpdateRequestBuilder updateRequestBuilder = client.prepareUpdate("orders", "_doc", "1");
    Map data = new HashMap<>();
    data.put("id",1);
    data.put("title","买了一个鼠标");
    data.put("count",2);
    data.put("amount",100.00);
    //data.put("status",1);
    //获取结果设置修改内容
    UpdateResponse updateResponse = updateRequestBuilder.setDoc(data).get();

    System.out.println(updateResponse);
    client.close();
}

打印结果如下:UpdateResponse[index=orders,type=_doc,id=1,version=19,seqNo=20,primaryTerm=3,result=updated,shards=ShardInfo{total=2, successful=1, failures=[]}]

注意:这种修改方式是不会影响到未修改的字段的,比如上面注释掉status字段,status字段不会被修改。也就是说这种方式是局部修改。

删除一个文档
 @Test
 public void testDelete() {
      //获取客户端对象
      TransportClient client = ESClientUtil.getClient();
      //执行删除
      DeleteResponse response = client.prepareDelete("orders", "_doc", "1").get();
      //打印结果
      System.out.println(response);
  }

打印结果:DeleteResponse[index=orders,type=_doc,id=1,version=20,result=deleted,shards=ShardInfo{total=2, successful=1, failures=[]}]

批量操作

同时提交多个操作

@Test
public void testBuilkAdd(){
   //获取客户端对象
   TransportClient client = ESClientUtil.getClient();

   BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();

   Map data1 = new HashMap<>();
   data1.put("id",1);
   data1.put("title","买了一个鼠标");
   data1.put("count",2);
   data1.put("amount",100.00);

   //添加操作
   bulkRequestBuilder.add(client.prepareIndex("orders", "_doc", "1").setSource(data1));

   Map data2 = new HashMap<>();
   data2.put("id",2);
   data2.put("title","买了一个鼠标");
   data2.put("count",2);
   data2.put("amount",100.00);
   //添加操作
   bulkRequestBuilder.add(client.prepareIndex("orders", "_doc", "2").setSource(data2));

   Map data3 = new HashMap<>();
   data3.put("id",2);
   data3.put("title","买了一个鼠标");
   data3.put("count",2);
   data3.put("amount",200.00);

   //修改操作
   bulkRequestBuilder.add(client.prepareUpdate("orders", "_doc", "2").setDoc(data3));


   BulkResponse bulkItemResponses = bulkRequestBuilder.get();
   Iterator iterator = bulkItemResponses.iterator();
   while(iterator.hasNext()){
       BulkItemResponse next = iterator.next();
       System.out.println(next.getResponse());
   }
   client.close();
 }

打印结果
IndexResponse[index=orders,type=_doc,id=1,version=1,result=created,seqNo=22,primaryTerm=3,shards={“total”:2,“successful”:1,“failed”:0}]
IndexResponse[index=orders,type=_doc,id=2,version=4,result=updated,seqNo=3,primaryTerm=3,shards={“total”:2,“successful”:1,“failed”:0}]
UpdateResponse[index=orders,type=_doc,id=2,version=5,seqNo=4,primaryTerm=3,result=updated,shards=ShardInfo{total=2, successful=1, failures=[]}]

DSL查询

案例:查询订单标题中包含:鼠标,价格在10-2000之间,状态为1,按照价格正排序

@Test
    public void testSearch(){
        //获取客户端对象
        TransportClient client = ESClientUtil.getClient();

        SearchRequestBuilder searchRequestBuilder = client.prepareSearch("orders");
        //设置分页
        searchRequestBuilder.setFrom(0);
        searchRequestBuilder.setSize(10);
        searchRequestBuilder.addSort("amount", SortOrder.ASC);

        //查询条件
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //dsl查询
        List must = boolQueryBuilder.must();
        must.add(QueryBuilders.matchQuery("title" , "鼠标"));

        //dsl过滤
        List filter = boolQueryBuilder.filter();
        filter.add(QueryBuilders.rangeQuery("amount").lte(2000).gte(10));
        filter.add(QueryBuilders.termQuery("status",1));

        searchRequestBuilder.setQuery(boolQueryBuilder);

        SearchResponse searchResponse = searchRequestBuilder.get();

        SearchHits hits = searchResponse.getHits();

        System.out.println("条数:"+hits.getTotalHits());
        for (SearchHit hit : hits.getHits()) {
            System.out.println(hit.getSourceAsMap());

        }

        client.close();
    }
SpringBootData操作ES 环境集成

第一步:导入SpringBoot提整合ES的依赖 spring-boot-starter-data-elasticsearch


    org.springframework.boot
   spring-boot-starter-parent
   2.2.5.RELEASE



	
	    org.springframework.boot
	    spring-boot-starter-data-elasticsearch
	
	
	    org.springframework.boot
	     spring-boot-starter-test
	 
	  
            org.projectlombok
            lombok
        

第二步:然后需要在yml中对ES进行配置 , 如果是集群配置增加uri即可,单个配置如下:

spring:
  elasticsearch:
    rest:
      uris:
        - http://localhost:9200

第三步:编写启动类

@SpringBootApplication
public class SearchStart {

    public static void main(String[] args) {
        SpringApplication.run(SearchStart.class);
    }
}

创建document对象

第三步:编写document对象 ,该对象是对存储到ES中的数据的封装,同时文档映射也是通过它来实现

//标记该对象是ES的文档对象
//indexName 索引库
//type 类型
@document(indexName = "orders",type = "_doc")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OrderDoc {

    //标记为文档ID,该ID的值会作为document的id值
    @Id
    private Long id;
    
    @Field(type = FieldType.Text,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
    //@Field(type = FieldType.Keyword)
    private String title;

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

    
    @Field(type = FieldType.Double)
    private BigDecimal amount;

}
创建Repository

第四步:SpringBootData提供了ElasticsearchRepository 来操作ES,该接口中包含了针对ES的CRUD方法,我们编写接口集成它即可使用

@Repository
public interface OrderRepository extends ElasticsearchRepository {
}

注意:这里的泛型是当前Repository所要管理的实体类,也就是OrderDoc,Long是实体类ID的类型

创建索引和映射

编写一个基于SpringBoot的测试类如下:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = SearchStart.class)
public class OrderESTest {
	//操作ES的template模板
    @Autowired
    private ElasticsearchRestTemplate template;
    
    @Autowired
    private OrderRepository orderRepository;

    @Test
    public void test(){
   		//创建索引
        template.createIndex(CourseDoc.class);
        //创建映射
        template.putMapping(CourseDoc.class);
    }
}

这里我注入了一个 ElasticsearchRestTemplate 工具类,通过它的 createIndex 可以创建索引库,SpringBoot指定解析 CourseDoc上的@document注解中的index来确定索引库的名字。创建映射也是同样的道理。

基础CRUD

在SpringBoot中对ES的Crud显得极其简单,直接调用repository内置方法即可完成

@Test
    public void testAdd(){
        orderRepository.save(new OrderDoc(1L,"买了一个表",1,1,new BigDecimal("200")));
    }

    @Test
    public void testGet(){
        Optional optional = orderRepository.findById(1l);
        System.out.println(optional.get());
    }

    @Test
    public void testDelete(){
        orderRepository.deleteById(1L);
    }

注意:这里并没有update方法,因为save对象的时候如果ID已经存在,就会执行update操作。repository中还有很多的方法可以使用,你可以自己去调试。

DSL查询

在SpringBoot中我们通过 NativeSearchQueryBuilder 来构建查询条件,调用repository.search来执行查询。
案例:查询标题中包含鼠标 ,状态为1,金额在10-2000之间,按照金额倒排,查询第2页数据,每页10条

 @Test
public void testSearch(){

    //查询构建器
    NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();

    //设置分页: 第2页 (0开始), 每页10数
    builder.withPageable(PageRequest.of(1,10));
    //设置排序 : 金额倒排
    builder.withSort(SortBuilders.fieldSort("amount").order(SortOrder.DESC));

    //构建组合查询
    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

    //标题包含鼠标
    boolQuery.must(QueryBuilders.matchQuery("title","鼠标"))
            //状态值查询
            .filter(QueryBuilders.termQuery("status",1))
            //金额范围查询
            .filter(QueryBuilders.rangeQuery("amount").gte(10).lte(2000));

    //添加查询条件
    builder.withQuery(boolQuery);

    //执行搜索
    Page page = orderRepository.search(builder.build());
    //获取条数
    System.out.println("总元素个数:"+page.getTotalElements());
    //打印列表
    page.getContent().forEach(System.out::print);

}

这里需要根据业务需求组装查询条件,我们之前见过的查询条件都可以通过它来构建,如:

实现高亮

在关键字搜索的时候,结果列表通常会把包含关键字的内容进行高亮,比如在京东商品列表搜搜“娃娃”效果如下

使用ES可以轻松做到高亮功能,第一步:增加结果处理器,在集成SpringBoot之后高亮功能失效,需要做高亮结果处理。

@Component
public class HighlightResultMapper implements SearchResultMapper {

    @Override
    public  AggregatedPage mapResults(SearchResponse response, Class aClass, Pageable pageable) {
        // 记录总条数
        long totalHits = response.getHits().getTotalHits();
        // 记录列表(泛型) - 构建Aggregate使用
        List list = Lists.newArrayList();
        // 获取搜索结果(真正的的记录)
        SearchHits hits = response.getHits();
        for (SearchHit hit : hits) {
            if(hits.getHits().length <= 0){
                return null;
            }
            // 将原本的JSON对象转换成Map对象
            Map map = hit.getSourceAsMap();
            // 获取高亮的字段Map
            Map highlightFields = hit.getHighlightFields();
            for (Map.Entry highlightField : highlightFields.entrySet()) {
                // 获取高亮的Key
                String key = highlightField.getKey();
                // 获取高亮的Value
                HighlightField value = highlightField.getValue();
                // 实际fragments[0]就是高亮的结果,无需遍历拼接
                Text[] fragments = value.getFragments();
                StringBuilder sb = new StringBuilder();
                for (Text text : fragments) {
                    sb.append(text);
                }
                // 因为高亮的字段必然存在于Map中,就是key值
                // 可能有一种情况,就是高亮的字段是嵌套Map,也就是说在Map里面还有Map的这种情况,这里没有考虑
                map.put(key, sb.toString());
            }
            // 把Map转换成对象
            T item = JSON.parseObject(JSONObject.toJSONString(map),aClass);
            list.add(item);
        }
        // 返回的是带分页的结果
        return new AggregatedPageImpl<>(list, pageable, totalHits,response.getAggregations());
    }

    public  T mapSearchHit(SearchHit searchHit, Class aClass) {
        return null;
    }
}

第二步:给搜索的字段设置高亮

...省略...

//给title字段设置高亮,使用color:red来高亮
HighlightBuilder.Field  highlightField = new HighlightBuilder.Field("title")
                .preTags("")
                .postTags("");

builder.withHighlightFields(highlightField);

第三步:修改查询方式,使用template查询,且指定结果映射器

 @Autowired
HighlightResultMapper highlightResultMapper;

@Test
public void testSearch(){

   ...省略...

   //设置高亮
   HighlightBuilder.Field  highlightField = new HighlightBuilder.Field("title")
           .preTags("")
           .postTags("");

   builder.withHighlightFields(highlightField);

   //执行搜索
   //Page page = orderRepository.search(builder.build());
   Page page = template.queryForPage(builder.build(), OrderDoc.class, highlightResultMapper);

   //获取条数
   System.out.println("总元素个数:"+page.getTotalElements());
   //打印列表
   page.getContent().forEach(System.out::print);

}

打印结果如下:
总元素个数:3
OrderDoc(id=2, title=买了一个鼠标, count=1, status=1, amount=200)
OrderDoc(id=1, title=买了一个鼠标, count=1, status=1, amount=200)
OrderDoc(id=3, title=买了一个鼠标, count=1, status=1, amount=200)

我们看到 鼠标 说明高亮成功


文章结束,如果对你有所帮助,请点赞收藏加评论

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/780794.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号