SpringData+ElasticSearch实现模糊查询、高亮显示

  • Post author:
  • Post category:其他

原文链接:https://blog.csdn.net/zhang33565417/article/details/99406387

  1. 环境搭建
    1.1 引入依赖

核心依赖如下(其他依赖自行引入)

org.springframework.boot spring-boot-starter-parent 2.0.6.RELEASE
org.springframework.boot spring-boot-starter-web
org.springframework.boot spring-boot-starter-data-elasticsearch
org.springframework.boot spring-boot-starter-test test
org.projectlombok lombok 1.16.20 provided 注意:低版本的springboot 不支持springboot data

1.2 编写yml配置信息

spring:
  data:
    elasticsearch:
      cluster-nodes: 172.16.251.142:9300   ##指定连接ip地址和端口号
  1. 代码实现
    2.1 创建实体类Product

@Document(indexName = “shopping”,type = “product”)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {

@Id
private String id;

@Field(type = FieldType.Text,analyzer = "ik_max_word", searchAnalyzer = "ik_max_word")
private String name;

@Field(type = FieldType.Double)
private Double marketPrice;

@Field(type = FieldType.Double)
private Double shopPrice;

@Field(type = FieldType.Text)
private String image;

@Field(type = FieldType.Text,analyzer = "ik_max_word", searchAnalyzer = "ik_max_word")
private String description;

@Field(type = FieldType.Integer)
private Integer isHot;

@Field(type = FieldType.Integer)
private Integer cId;

}

@Document: 代表一个文档记录

​ indexName: 用来指定索引名称

​ type: 用来指定索引类型

@Id: 用来将对象中id和ES中_id映射

@Field: 用来指定ES中的字段对应Mapping

​ type: 用来指定ES中存储类型

​ analyzer: 用来指定使用哪种分词器
2.2 编写简单Repository接口类 接口中可以什么都不写使用一些基础的方法,或者根据命名规则自定义方法并实现,或者如2.3下的自定义复杂接口

/**

  • 根据需要自定义接口方法
  • 定义方法方法时需注意命名规范
    */
    public interface ProductRepository extends ElasticsearchRepository<Product,String> {
    }
    2.3 自定义复杂方法接口CustomProductRepository及实现CustomProductRepositoryImpl

//自定义复杂方法不需要继承ElasticsearchRepository
public interface CustomProductRepository {
//价格查询并分页
List findByPageable(Double marketprice, Integer page,Integer size);

//价格查询结果的总数
Integer findByPrice(Double marketprice);

//term查询高亮(name)
List<Product> findByNameAndHighlightAdnPageable(String name,int page,int size);

//term 查询结果的数量
Integer findCountByName(String name);

//分页查询所有
List<Product> findAll(Integer page, Integer size);

}
@Component
public class CustomProductRepositoryImpl implements CustomProductRepository {

//实现类需要注入elasticsearchTemplates
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;


@Override
//分页查所有  es默认从0页开始,JqGrid默认传的page是1,所以-1
public List<Product> findAll(Integer page, Integer size) {
    NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
            .withQuery(QueryBuilders.matchAllQuery())
            .withPageable(PageRequest.of(page-1, size))
            .build();
    //默认只查出来10条,不知道怎么改
    List<Product> products = elasticsearchTemplate.queryForList(searchQuery, Product.class);
    return products;
}

@Override
//价格分页查询
public List<Product> findByPageable(Double marketprice, Integer page, Integer size) {
    System.out.println(marketprice);
    NativeSearchQuery queryBuilder = new NativeSearchQueryBuilder()
            .withQuery(QueryBuilders.termQuery("marketPrice", marketprice))
            .withPageable(PageRequest.of(page - 1, size)).build();
    List<Product> products = elasticsearchTemplate.queryForList(queryBuilder, Product.class);
    return products;
}

@Override
//价格查询结果的总数
public Integer findByPrice(Double marketprice) {
    NativeSearchQuery queryBuilder = new NativeSearchQueryBuilder()
            .withQuery(QueryBuilders.termQuery("marketPrice", marketprice))
            .build();

    List<Product> products = elasticsearchTemplate.queryForList(queryBuilder, Product.class);
    return products.size();
}

@Override
//term查询高亮(多字段查询)
public List<Product> findByNameAndHighlightAdnPageable(String name, int page,int size) {

    HighlightBuilder.Field nameField = new HighlightBuilder
            .Field("*")
            .preTags("<span style='color:red'>")
            .postTags("</span>").requireFieldMatch(false);

    //多字段查询,可同时在name和description查询 对应实体类中的属性名
    NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
            .withQuery(QueryBuilders.multiMatchQuery(name, "name", "description"))
            .withPageable(PageRequest.of(page - 1, size))
            .withHighlightFields(nameField)
            .build();

    AggregatedPage<Product> products = elasticsearchTemplate.
            queryForPage(nativeSearchQuery, Product.class, new SearchResultMapper() {
                @Override
                public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
                    SearchHits searchHits = response.getHits();
                    SearchHit[] hits = searchHits.getHits();
                    ArrayList<Product> products = new ArrayList<Product>();
                    for (SearchHit hit : hits) {
                        Product product = new Product();
                        //原始map
                        Map<String, Object> sourceAsMap = hit.getSourceAsMap();
                        product.setId(sourceAsMap.get("id").toString());
                        product.setName(sourceAsMap.get("name").toString());
                        product.setMarketPrice(Double.parseDouble(sourceAsMap.get("marketPrice").toString()));
                        product.setShopPrice(Double.parseDouble(sourceAsMap.get("shopPrice").toString()));
                        product.setImage(sourceAsMap.get("image").toString());
                        product.setDescription(sourceAsMap.get("description").toString());
                        //高亮
                        Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                        System.out.println(highlightFields);
                        if (highlightFields.get("name") != null) {
                            String nameHighlight = highlightFields.get("name").getFragments()[0].toString();
                            product.setName(nameHighlight);
                        }
                        if (highlightFields.get("description") != null) {
                            String contentHighlight = highlightFields.get("description").getFragments()[0].toString();
                            product.setDescription(contentHighlight);
                        }
                        products.add(product);
                    }
                    return new AggregatedPageImpl<T>((List<T>) products);
                }
            });
    return products.getContent();
}

@Override
//term 高亮查询结果的数量
public Integer findCountByName(String name) {
    NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
            .withQuery(QueryBuilders.multiMatchQuery(name, "name","description"))
            .build();
    return elasticsearchTemplate.queryForList(nativeSearchQuery, Product.class).size();

}

}
2.4 Controller层实现 前提是相关数据已经存入es中,可以在添加内容的时候存进去

@RestController
@RequestMapping(“product”)
public class ProductController {

@Autowired
private ProductService productService;

@Autowired
private ProductRepository productRepository;

@Autowired
private CustomProductRepository customProductRepository;

//查所有
@RequestMapping("findByPage")
public Map<String,Object> findByPage(Integer rows,Integer page){
    List<Product> products = productService.findByPage(rows, page);
    //总条数
    List<Product> all = productService.findAll();
    int records = all.size();
    //总页数
    Integer total = null;
    if(records%rows == 0){
        total = records/rows;
    }else{
        total = records/rows+1;
    }
    //存入Map集合
    Map<String,Object> map =  new HashMap<>();
    map.put("rows",products);
    map.put("page",page);
    map.put("total",total);
    map.put("records",records);

    return map;
}

//使用es搜索
@RequestMapping("findBySearch")
public Map<String,Object> findBySearch(Integer rows,Integer page,String opt,String content){
    Map<String,Object> map = new HashMap<>();
    //按商品描述搜索
    if("description".equals(opt) && content != ""){
        List<Product> products = customProductRepository.findByNameAndHighlightAdnPageable(content, page, rows);
        //总数量
        Integer records = customProductRepository.findCountByName(content);
        //总页数
        Integer total = null;
        if(records % rows == 0){
            total = records/rows;
        }else{
            total = records/rows+1;
        }
        //存入map集合中
        map.put("rows",products);
        map.put("page",page);
        map.put("records",records);
        map.put("total",total);
     //按商品价格搜索
    }else if("marketPrice".equals(opt) && content != ""){
        double marketPrice = Double.parseDouble(content);
        List<Product> products = customProductRepository.findByPageable(marketPrice, page, rows);
        //总数量
        Integer records = customProductRepository.findByPrice(marketPrice);
        //总页数
        Integer total = null;
        if(records % rows == 0){
            total = records/rows;
        }else{
            total = records/rows+1;
        }
        //存入map集合中
        map.put("rows",products);
        map.put("page",page);
        map.put("records",records);
        map.put("total",total);
    }else{
        List<Product> products = customProductRepository.findAll(page, rows);
        //这样获取总数量
        long count = productRepository.count();
        int records = Math.toIntExact(count);
        //Integer records = products.size(); 这样获取的不是总数量, porducts是每次查询出来一页的数据,
        //总页数
        Integer total = null;
        if(records % rows == 0){
            total = records/rows;
        }else{
            total = records/rows+1;
        }
        //存入map集合中
        map.put("rows",products);
        map.put("page",page);
        map.put("records",records);
        map.put("total",total);
    }
    return map;
}

}
2.5 部分前台代码

前台这里用的是BootStrap+JqGrid实现的表格

    $(function () {
        $("#sub").click(function () {
            //获取查询条件
            var opt = $("#opt").val();
            var content = $("#content").val();
            //清空表中数据
            $("#tt").jqGrid("clearGridData");
            //重新接收表中数据
            $("#tt").jqGrid("setGridParam",{
                url:"${pageContext.request.contextPath}/product/findBySearch?opt="+opt+"&content="+content+"",
                dataType:"json",
                type:"post"
            }).trigger("reloadGrid");
        });
    });

注意: 搜索输入框标签,如果什么都不填,content默认的是空字符串而不是null

HTML-input文本框如果不输入任何内容提交过后是一个空字符串还是null ?

text类型取得空字符串,radio,checkbox未选中提交则是null

3.效果展示

注意:springboot的新版本对JSP热部署配置文件的格式有所改变
老版本格式:

server:
port: 8081
jsp-servlet:
init-parameters:
development: true
新版本格式:

server:
port: 8081
servlet:
jsp:
init-parameters:
development: true