• [技术干货] 操作Elasticsearch
    1 Easy-Es 使用过Spring Data操作ES的小伙伴应该有所了解,它只能实现一些非常基本的数据管理工作,一旦遇到稍微复杂点的查询,基本都要依赖ES官方提供的RestHighLevelClient,Spring Data只是在其基础上进行了简单的封装。最近发现一款更优雅的ES ORM框架Easy-Es,使用它能像MyBatis-Plus一样操作ES  点击了解 SpringBoot 整合ElasticSearch  1.1 简介 Easy-Es(简称EE)是一款基于Elasticsearch(简称ES)官方提供的RestHighLevelClient打造的ORM开发框架,在RestHighLevelClient的基础上,只做增强不做改变,为简化开发、提高效率而生。EE和Mybatis-Plus(简称MP)的用法非常相似,如果你之前使用过MP的话,应该能很快上手EE。EE的理念是:把简单、易用、方便留给用户,把复杂留给框架。 官网地址:https://www.easy-es.cn/  EE的主要特性如下:  全自动索引托管:开发者无需关心索引的创建、更新及数据迁移等繁琐步骤,框架能自动完成。 屏蔽语言差异:开发者只需要会MySQL的语法即可使用ES。 代码量极少:与直接使用官方提供的RestHighLevelClient相比,相同的查询平均可以节省3-5倍的代码量。 零魔法值:字段名称直接从实体中获取,无需手写。 零额外学习成本: 开发者只要会国内最受欢迎的Mybatis-Plus用法,即可无缝迁移至EE。 1.2 MySQL与Easy-Es语法对比 首先我们来对MySQL、Easy-Es和RestHighLevelClient的语法做过对比,来快速学习下Easy-Es的语法。  MySQL    Easy-Es    es-DSL/es java api and    and    must or    or    should =    eq    term !=    ne    boolQueryBuilder.mustNot(queryBuilder) >    gt    QueryBuilders.rangeQuery(‘es field’).gt() >=    ge    QueryBuilders.rangeQuery(‘es field’).gte() <    lt    QueryBuilders.rangeQuery(‘es field’).lt() <=    le    QueryBuilders.rangeQuery(‘es field’).lte() like ‘%field%’    like    QueryBuilders.wildcardQuery(field,value) not like ‘%field%’    notLike    must not wildcardQuery(field,value) like ‘%field’    likeLeft    QueryBuilders.wildcardQuery(field,*value) like ‘field%’    likeRight    QueryBuilders.wildcardQuery(field,value*) between    between    QueryBuilders.rangeQuery(‘es field’).from(xx).to(xx) notBetween    notBetween    must not QueryBuilders.rangeQuery(‘es field’).from(xx).to(xx) is null    isNull    must not QueryBuilders.existsQuery(field) is notNull    isNotNull    QueryBuilders.existsQuery(field) in    in    QueryBuilders.termsQuery(" xx es field", xx) not in    notIn    must not QueryBuilders.termsQuery(" xx es field", xx) group by    groupBy    AggregationBuilders.terms() order by    orderBy    fieldSortBuilder.order(ASC/DESC) min    min    AggregationBuilders.min max    max    AggregationBuilders.max avg    avg    AggregationBuilders.avg sum    sum    AggregationBuilders.sum order by xxx asc    orderByAsc    fieldSortBuilder.order(SortOrder.ASC) order by xxx desc    orderByDesc    fieldSortBuilder.order(SortOrder.DESC) -    match    matchQuery -    matchPhrase    QueryBuilders.matchPhraseQuery -    matchPrefix    QueryBuilders.matchPhrasePrefixQuery -    queryStringQuery    QueryBuilders.queryStringQuery select *    matchAllQuery    QueryBuilders.matchAllQuery() -    highLight    HighlightBuilder.Field 1.3 集成及配置 1.3.1 pom.xml <dependency>     <groupId>cn.easy-es</groupId>     <artifactId>easy-es-boot-starter</artifactId>     <version>1.0.2</version> </dependency> 由于底层使用了ES官方提供的RestHighLevelClient,这里ES的相关依赖版本需要统一下,这里使用的ES客户端版本为7.14.0,ES版本为7.17.3;  <dependencyManagement>     <dependencies>         <dependency>             <groupId>org.elasticsearch.client</groupId>             <artifactId>elasticsearch-rest-high-level-client</artifactId>             <version>7.14.0</version>         </dependency>         <dependency>             <groupId>org.elasticsearch</groupId>             <artifactId>elasticsearch</artifactId>             <version>7.14.0</version>         </dependency>     </dependencies> </dependencyManagement> 1.3.2 配置 再修改配置文件application.yml对Easy-Es进行配置。  easy-es:   # 是否开启EE自动配置   enable: true   # ES连接地址+端口   address: localhost:9200   # 关闭自带banner   banner: false 添加Easy-Es的Java配置,使用@EsMapperScan配置好Easy-Es的Mapper接口和文档对象路径,如果使用了MyBatis-Plus的话,需要和它的扫描路径区分开来。  /**  * EasyEs配置类  */ @Configuration @EsMapperScan("com.test.easyes") public class EasyEsConfig { } 1.4 使用 Easy-Es集成和配置完成后,就可以开始使用了。  1.4.1 注解的使用 下面我们来学习下Easy-Es中注解的使用。 首先我们需要创建文档对象EsProduct,然后给类和字段添加上Easy-Es的注解;  /**  * 搜索商品的信息  */ @Data @EqualsAndHashCode @IndexName(value = "pms", shardsNum = 1, replicasNum = 0) public class EsProduct implements Serializable {     private static final long serialVersionUID = -1L;     @IndexId(type = IdType.CUSTOMIZE)     private Long id;     @IndexField(fieldType = FieldType.KEYWORD)     private String productSn;     private Long brandId;     @IndexField(fieldType = FieldType.KEYWORD)     private String brandName;     private Long productCategoryId;     @IndexField(fieldType = FieldType.KEYWORD)     private String productCategoryName;     private String pic;     @IndexField(fieldType = FieldType.TEXT, analyzer = "ik_max_word")     private String name;     @IndexField(fieldType = FieldType.TEXT, analyzer = "ik_max_word")     private String subTitle;     @IndexField(fieldType = FieldType.TEXT, analyzer = "ik_max_word")     private String keywords;     private BigDecimal price;     private Integer sale;     private Integer newStatus;     private Integer recommandStatus;     private Integer stock;     private Integer promotionType;     private Integer sort;     @IndexField(fieldType = FieldType.NESTED, nestedClass = EsProductAttributeValue.class)     private List<EsProductAttributeValue> attrValueList;     @Score     private Float score; }  /**  * 嵌套类型EsProductAttributeValue  * 搜索商品的属性信息  */ @Data @EqualsAndHashCode public class EsProductAttributeValue implements Serializable {     private static final long serialVersionUID = 1L;     @IndexField(fieldType = FieldType.LONG)     private Long id;     @IndexField(fieldType = FieldType.KEYWORD)     private Long productAttributeId;     //属性值     @IndexField(fieldType = FieldType.KEYWORD)     private String value;     //属性参数:0->规格;1->参数     @IndexField(fieldType = FieldType.INTEGER)     private Integer type;     //属性名称     @IndexField(fieldType=FieldType.KEYWORD)     private String name; } EsProduct 中的注解具体说明如下:  @IndexName:索引名注解,value是指定索引名;shardsNum:分片数;replicasNum:副本数 @IndexId:ES主键注解,type 指定注解类型,CUSTOMIZE 表示自定义 @IndexField:ES字段注解,fieldType 字段在索引中的类型,analyzer 索引文档时用的分词器,nestedClass 嵌套类 @Score:得分注解 decimalPlaces 得分保留小数位,实体类中被作为 ES 查询得分返回的字段使用 1.4.2 EsMapper接口 下面我们来实现几个简单的商品信息维护接口,包括商品信息的导入、创建和删除。 首先我们需要定义一个Mapper,继承BaseEsMapper;  /**  * 商品ES操作类  */ public interface EsProductMapper extends BaseEsMapper<EsProduct> {  } 然后在Service实现类中直接使用EsProductMapper内置方法实现即可  /**  * 搜索商品管理Service实现类  */ @Service public class EsProductServiceImpl implements EsProductService {     @Autowired     private EsProductDao productDao;     @Autowired     private EsProductMapper esProductMapper;     @Override     public int importAll() {         List<EsProduct> esProductList = productDao.getAllEsProductList(null);         return esProductMapper.insertBatch(esProductList);     }      @Override     public void delete(Long id) {         esProductMapper.deleteById(id);     }      @Override     public EsProduct create(Long id) {         EsProduct result = null;         List<EsProduct> esProductList = productDao.getAllEsProductList(id);         if (esProductList.size() > 0) {             result = esProductList.get(0);             esProductMapper.insert(result);         }         return result;     }      @Override     public void delete(List<Long> ids) {         if (!CollectionUtils.isEmpty(ids)) {             esProductMapper.deleteBatchIds(ids);         }     } } 1.4.3 简单搜索 下面我们来实现一个最简单的商品搜索,分页搜索商品名称、副标题、关键词中包含指定关键字的商品。 通过QueryWrapper来构造查询条件,然后使用Mapper中的方法来进行查询,使用过MyBatis-Plus的小伙伴应该很熟悉了 /**  * 搜索商品管理Service实现类  */ @Service public class EsProductServiceImpl implements EsProductService {     @Autowired     private EsProductMapper esProductMapper;     @Override     public PageInfo<EsProduct> search(String keyword, Integer pageNum, Integer pageSize) {         LambdaEsQueryWrapper<EsProduct> wrapper = new LambdaEsQueryWrapper<>();         if(StrUtil.isEmpty(keyword)){             wrapper.matchAllQuery();         }else{             wrapper.multiMatchQuery(keyword,EsProduct::getName,EsProduct::getSubTitle,EsProduct::getKeywords);         }         return esProductMapper.pageQuery(wrapper, pageNum, pageSize);     } } 在控制台输出查看生成的DSL语句 把DSL语句直接复制到 Kibana 中即可执行查看结果了,这和我们手写DSL语句没什么两样的。 1.5 使用案例 1.5.1 综合商品搜索 下面我们来实现一个复杂的商品搜索,涉及到过滤、不同字段匹配权重不同以及可以进行排序。 首先来说需求,按输入的关键字搜索商品名称(权重10)、副标题(权重5)和关键词(权重2),可以按品牌和分类进行筛选,可以有5种排序方式,默认按相关度进行排序 下面是使用Easy-Es的实现方式 /**  * 搜索商品管理Service实现类  */ @Service public class EsProductServiceImpl implements EsProductService {     @Autowired     private EsProductMapper esProductMapper;     @Override     public PageInfo<EsProduct> search(String keyword, Long brandId, Long productCategoryId, Integer pageNum, Integer pageSize,Integer sort) {         LambdaEsQueryWrapper<EsProduct> wrapper = new LambdaEsQueryWrapper<>();         //过滤         if (brandId != null || productCategoryId != null) {             if (brandId != null) {                 wrapper.eq(EsProduct::getBrandId,brandId);             }             if (productCategoryId != null) {                 wrapper.eq(EsProduct::getProductCategoryId,productCategoryId).enableMust2Filter(true);             }         }         //搜索         if (StrUtil.isEmpty(keyword)) {             wrapper.matchAllQuery();         } else {             wrapper.and(i -> i.match(EsProduct::getName, keyword, 10f)                     .or().match(EsProduct::getSubTitle, keyword, 5f)                     .or().match(EsProduct::getKeywords, keyword, 2f));         }         //排序         if(sort==1){             //按新品从新到旧             wrapper.orderByDesc(EsProduct::getId);         }else if(sort==2){             //按销量从高到低             wrapper.orderByDesc(EsProduct::getSale);         }else if(sort==3){             //按价格从低到高             wrapper.orderByAsc(EsProduct::getPrice);         }else if(sort==4){             //按价格从高到低             wrapper.orderByDesc(EsProduct::getPrice);         }else{             //按相关度             wrapper.sortByScore(SortOrder.DESC);         }         return esProductMapper.pageQuery(wrapper, pageNum, pageSize);     } } 1.5.2 相关商品推荐 当我们查看相关商品的时候,一般底部会有一些商品推荐,这里简单来实现下。  首先来说下需求,可以根据指定商品的ID来查找相关商品 这里我们的实现原理是这样的:首先根据ID获取指定商品信息,然后以指定商品的名称、品牌和分类来搜索商品,并且要过滤掉当前商品,调整搜索条件中的权重以获取最好的匹配度;  /**  * 搜索商品管理Service实现类  */ @Service public class EsProductServiceImpl implements EsProductService {     @Autowired     private EsProductMapper esProductMapper;     @Override     public PageInfo<EsProduct> recommend(Long id, Integer pageNum, Integer pageSize) {         LambdaEsQueryWrapper<EsProduct> wrapper = new LambdaEsQueryWrapper<>();         List<EsProduct> esProductList = productDao.getAllEsProductList(id);         if (esProductList.size() > 0) {             EsProduct esProduct = esProductList.get(0);             String keyword = esProduct.getName();             Long brandId = esProduct.getBrandId();             Long productCategoryId = esProduct.getProductCategoryId();             //用于过滤掉相同的商品             wrapper.ne(EsProduct::getId,id);             //根据商品标题、品牌、分类进行搜索             wrapper.and(i -> i.match(EsProduct::getName, keyword, 8f)                     .or().match(EsProduct::getSubTitle, keyword, 2f)                     .or().match(EsProduct::getKeywords, keyword, 2f)                     .or().match(EsProduct::getBrandId, brandId, 5f)                     .or().match(EsProduct::getProductCategoryId, productCategoryId, 3f));             return esProductMapper.pageQuery(wrapper, pageNum, pageSize);         }         return esProductMapper.pageQuery(wrapper, pageNum, pageSize);     } } 1.5.3 聚合搜索商品相关信息 在搜索商品时,经常会有一个筛选界面来帮助我们找到想要的商品,这里我们来简单实现下。 首先来说下需求,可以根据搜索关键字获取到与关键字匹配商品相关的分类、品牌以及属性 这里我们可以使用ES的聚合来实现,搜索出相关商品,聚合出商品的品牌、商品的分类以及商品的属性,只要出现次数最多的前十个即可;  由于Easy-Es目前只用groupBy实现了简单的聚合,对于我们这种有嵌套对象的聚合无法支持,所以需要使用RestHighLevelClient来实现,如果对照之前的Spring Data实现方式的话,可以发现用法差不多,看样子Spring Data只是做了简单的封装而已。  /**  * 搜索商品管理Service实现类  */ @Service public class EsProductServiceImpl implements EsProductService {     @Autowired     private EsProductMapper esProductMapper;     @Override     public EsProductRelatedInfo searchRelatedInfo(String keyword) {         SearchRequest searchRequest = new SearchRequest();         searchRequest.indices("pms_*");         SearchSourceBuilder builder = new SearchSourceBuilder();         //搜索条件         if (StrUtil.isEmpty(keyword)) {             builder.query(QueryBuilders.matchAllQuery());         } else {             builder.query(QueryBuilders.multiMatchQuery(keyword, "name", "subTitle", "keywords"));         }         //聚合搜索品牌名称         builder.aggregation(AggregationBuilders.terms("brandNames").field("brandName"));         //集合搜索分类名称         builder.aggregation(AggregationBuilders.terms("productCategoryNames").field("productCategoryName"));         //聚合搜索商品属性,去除type=1的属性         AbstractAggregationBuilder<NestedAggregationBuilder> aggregationBuilder = AggregationBuilders.nested("allAttrValues", "attrValueList")                 .subAggregation(AggregationBuilders.filter("productAttrs", QueryBuilders.termQuery("attrValueList.type", 1))                         .subAggregation(AggregationBuilders.terms("attrIds")                                 .field("attrValueList.productAttributeId")                                 .subAggregation(AggregationBuilders.terms("attrValues")                                         .field("attrValueList.value"))                                 .subAggregation(AggregationBuilders.terms("attrNames")                                         .field("attrValueList.name"))));         builder.aggregation(aggregationBuilder);         searchRequest.source(builder);         try {             SearchResponse searchResponse = esProductMapper.search(searchRequest, RequestOptions.DEFAULT);             return convertProductRelatedInfo(searchResponse);         } catch (IOException e) {             e.printStackTrace();         }         return null;     }      /**      * 将返回结果转换为对象      */     private EsProductRelatedInfo convertProductRelatedInfo(SearchResponse response) {         EsProductRelatedInfo productRelatedInfo = new EsProductRelatedInfo();         Map<String, Aggregation> aggregationMap = response.getAggregations().asMap();         //设置品牌         Aggregation brandNames = aggregationMap.get("brandNames");         List<String> brandNameList = new ArrayList<>();         for(int i = 0; i<((Terms) brandNames).getBuckets().size(); i++){             brandNameList.add(((Terms) brandNames).getBuckets().get(i).getKeyAsString());         }         productRelatedInfo.setBrandNames(brandNameList);         //设置分类         Aggregation productCategoryNames = aggregationMap.get("productCategoryNames");         List<String> productCategoryNameList = new ArrayList<>();         for(int i=0;i<((Terms) productCategoryNames).getBuckets().size();i++){             productCategoryNameList.add(((Terms) productCategoryNames).getBuckets().get(i).getKeyAsString());         }         productRelatedInfo.setProductCategoryNames(productCategoryNameList);         //设置参数         Aggregation productAttrs = aggregationMap.get("allAttrValues");         List<? extends Terms.Bucket> attrIds = ((ParsedStringTerms) ((ParsedFilter) ((ParsedNested) productAttrs).getAggregations().get("productAttrs")).getAggregations().get("attrIds")).getBuckets();         List<EsProductRelatedInfo.ProductAttr> attrList = new ArrayList<>();         for (Terms.Bucket attrId : attrIds) {             EsProductRelatedInfo.ProductAttr attr = new EsProductRelatedInfo.ProductAttr();             attr.setAttrId(Long.parseLong((String) attrId.getKey()));             List<String> attrValueList = new ArrayList<>();             List<? extends Terms.Bucket> attrValues = ((ParsedStringTerms) attrId.getAggregations().get("attrValues")).getBuckets();             List<? extends Terms.Bucket> attrNames = ((ParsedStringTerms) attrId.getAggregations().get("attrNames")).getBuckets();             for (Terms.Bucket attrValue : attrValues) {                 attrValueList.add(attrValue.getKeyAsString());             }             attr.setAttrValues(attrValueList);             if(!CollectionUtils.isEmpty(attrNames)){                 String attrName = attrNames.get(0).getKeyAsString();                 attr.setAttrName(attrName);             }             attrList.add(attr);         }         productRelatedInfo.setProductAttrs(attrList);         return productRelatedInfo;     } } 使用 Easy-Es 确实简单,但是对于复杂的聚合搜索功能,需要使用原生的 RestHighLevelClient 用法来实现。使用Easy-Es来操作ES确实足够优雅,它类似MyBatis-Plus的用法能大大降低我们的学习成本 ————————————————          原文链接:https://blog.csdn.net/u012060033/article/details/135918347 
  • [技术干货] SpringBoot整合Easy-Es
    目录 一、什么是Easy-Es  二、使用场景 2.1 检索类服务 2.2  问答类服务(本质上也是检索类)   2.3 地图类服务 三、spring boot整合 Easy-Es 3.1 pom.xml 3.2 配置文件 3.3 创建、删除、查询索引 3.4 创建一个实体类 3.5 新建Mapper类,类似Mybatis的dao 3.6 启动类扫描 dao 四、代码展示 五、 原生Api调用 5.1查看索引mapping关系 5.2查看某个文档,具体字段的分词  一、什么是Easy-Es     Easy-Es(简称EE)是一款基于ElasticSearch(简称Es)官方提供的RestHighLevelClient打造的ORM开发框架,在 RestHighLevelClient 的基础上,只做增强不做改变,为简化开发、提高效率而生,您如果有用过Mybatis-Plus(简称MP),那么您基本可以零学习成本直接上手EE,EE是MP的Es平替版,在有些方面甚至比MP更简单,同时也融入了更多Es独有的功能,助力您快速实现各种场景的开发.   二、使用场景 2.1 检索类服务 搜索文库 电商商品检索 海量系统日志检索 2.2  问答类服务(本质上也是检索类)  在线智能客服 机器人  2.3 地图类服务 打车app 外卖app 社区团购配送 陌生人社交  2.4 官网  https://www.easy-es.cn/  三、spring boot整合 Easy-Es 3.1 pom.xml 其中easy-es-boot-starter 安装最新版,可以复制easy-es-boot-starter到maven中央仓库搜索   <dependencies>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter</artifactId>         </dependency>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-web</artifactId>             <exclusions>                 <exclusion>                     <groupId>org.elasticsearch.client</groupId>                     <artifactId>elasticsearch-rest-high-level-client</artifactId>                 </exclusion>                 <exclusion>                     <groupId>org.elasticsearch</groupId>                     <artifactId>elasticsearch</artifactId>                 </exclusion>             </exclusions>         </dependency>           <dependency>             <groupId>cn.easy-es</groupId>             <artifactId>easy-es-boot-starter</artifactId>             <version>2.0.0-beta1</version>         </dependency>         <dependency>             <groupId>org.elasticsearch.client</groupId>             <artifactId>elasticsearch-rest-high-level-client</artifactId>             <version>7.14.0</version>         </dependency>         <dependency>             <groupId>org.elasticsearch</groupId>             <artifactId>elasticsearch</artifactId>             <version>7.14.0</version>         </dependency>       </dependencies>       <build>         <plugins>             <plugin>                 <groupId>org.springframework.boot</groupId>                 <artifactId>spring-boot-maven-plugin</artifactId>             </plugin>         </plugins>     </build>  3.2 配置文件 如果有账号密码自行加上  # Easy-Es配置部分 easy-es:   # 启用Easy-Es功能   enable: true   # 设置Elasticsearch服务器地址和端口   address: 192.168.23.27:9200   # 全局配置项,设置是否打印执行的DSL语句(便于调试)   global-config:     print-dsl: true Elasticsearch DSL (Domain Specific Language) 是一种专门设计用来与Elasticsearch搜索引擎进行交互的查询语言。它是一种基于JSON格式的查询语法,允许用户以结构化的方式来构建复杂的查询、过滤条件、聚合操作以及其他高级功能。  通过Elasticsearch DSL,开发人员可以灵活且高效地执行各种查询操作,包括全文本搜索、精确匹配、范围查询、布尔组合查询、排序、分页、高亮显示文本、统计计算、地理位置查询以及复杂的聚合分析等。  例如,一个简单的Elasticsearch DSL查询语句可能是查找索引my_index中field1字段包含关键词value1的文档  3.3 创建、删除、查询索引  分词&模糊匹配 | Easy-Es  {   "query": {     "match": {       "field1": "value1"     }   } } 3.4 创建一个实体类 @IndexName( aliasName = "es_product") public class EsProduct {       @IndexId(type = IdType.CUSTOMIZE)     private Integer id;     @IndexField( fieldType= FieldType.TEXT, analyzer = Analyzer.IK_MAX_WORD,searchAnalyzer = Analyzer.IK_MAX_WORD)     private String name;     @IndexField( fieldType= FieldType.INTEGER)     private Integer categoryId;     @IndexField( fieldType= FieldType.DOUBLE) // 12.56     private BigDecimal price;     @IndexField( fieldType= FieldType.TEXT, analyzer = Analyzer.IK_MAX_WORD)     private String brief;     @IndexField( fieldType= FieldType.KEYWORD)     private String img;     @IndexField( fieldType= FieldType.TEXT, analyzer = Analyzer.IK_MAX_WORD)     private List<String> tags;     @IndexField( fieldType= FieldType.INTEGER) //198     private Integer highOpinion;     @IndexField( fieldType= FieldType.INTEGER)     private Integer salesVolume;     @IndexField( fieldType= FieldType.DATE)     private LocalDateTime productionDate; }  3.5 新建Mapper类,类似Mybatis的dao   package com.by.dao;     import cn.easyes.core.core.BaseEsMapper; import com.by.model.EsProduct;   public interface EsProductMapper extends BaseEsMapper<EsProduct> {   } 3.6 启动类扫描 dao   @SpringBootApplication @EsMapperScan("com.by.dao") public class App {     public static void main(String[] args) {         SpringApplication.run(App.class, args);     }   } 四、代码展示    @Test     void insert() {         EsProduct esProduct = EsProduct.builder()                 .id(1)                 .name("小米")                 .img("图片地址")                 .brief("小米(MI)Redmi Note12 5G 120Hz OLED屏幕 骁龙4移动平台 5000mAh长续航 8GB+128GB子夜黑 小米红米")                 .price(new BigDecimal(18))                 .categoryId(1)                 .highOpinion(20)                 .productionDate(LocalDateTime.now())                 .salesVolume(99)                 .tags(CollUtil.newArrayList("120高刷","舒适护眼"))                 .build();         esMapper.insert(esProduct);     }     @Test     void insert2() {         delete();         List<EsProduct> esProducts = CollUtil.newArrayList();         for (int i = 1; i <= 10; i++) {             EsProduct esProduct = EsProduct.builder()                     .id(i)                     .name("小米"+i)                     .img("图片地址"+i)                     .brief("小米(MI)Redmi Note"+i+" 5G 120Hz OLED屏幕 骁龙4移动平台 5000mAh长续航 8GB+128GB子夜黑 小米红米")                     .price(new BigDecimal(500.36+i))                     .categoryId(1)                     .highOpinion(100+i)                     .productionDate(LocalDateTime.now())                     .salesVolume(200+i)                     .tags(CollUtil.newArrayList("12"+i+"高刷","舒适护眼"))                     .build();             esProducts.add(esProduct);                 }           esMapper.insertBatch(esProducts);     }       @Test     void update() {         EsProduct esProduct = EsProduct.builder()                 .id(1)                 .name("su7")                 .img("图片地址")                 .brief("小米汽车")                 .price(new BigDecimal(18))                 .categoryId(9)                 .highOpinion(20)                 .productionDate(LocalDateTime.now())                 .salesVolume(99)                 .tags(CollUtil.newArrayList("120高刷","舒适护眼"))                 .build();         Integer integer = esMapper.updateById(esProduct);     }     @Test     void delete() {         Integer batchIds = esMapper.deleteBatchIds(CollUtil.newArrayList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));     }       @Test     void select() {         LambdaEsQueryWrapper  queryWrapper = new LambdaEsQueryWrapper<EsProduct>();         queryWrapper.eq("id",1);         List list = esMapper.selectList(queryWrapper);     }     @Test     void select2() {         LambdaEsQueryWrapper  queryWrapper = new LambdaEsQueryWrapper<EsProduct>();         queryWrapper.queryStringQuery("汽车之家"); //所有字段都去匹配         List list = esMapper.selectList(queryWrapper);     }     @Test     void select3() {         LambdaEsQueryWrapper  queryWrapper = new LambdaEsQueryWrapper<EsProduct>();         queryWrapper.eq("categoryId",1);         List list = esMapper.selectList(queryWrapper);     }     @Test     void select4() {         LambdaEsQueryWrapper<EsProduct>  queryWrapper = new LambdaEsQueryWrapper<>();         queryWrapper.in(EsProduct::getCategoryId,1,9);                List list = esMapper.selectList(queryWrapper);     }       @Test     void select5() {         LambdaEsQueryWrapper<EsProduct>  queryWrapper = new LambdaEsQueryWrapper<>();         //queryWrapper.match(EsProduct::getBrief,"汽车",1.0F);         //queryWrapper.match(EsProduct::getName,"汽车",1.0F);         //queryWrapper.multiMatchQuery("汽车", Operator.OR, EsProduct::getName,EsProduct::getBrief);         queryWrapper.in("categoryId",1,9); //where categroyId in (1,9) and ( name like '%汽车%' or brief like '%汽车%')         queryWrapper.and(                 w->w.match(EsProduct::getBrief,"汽车",1.0F)                         .or().match(EsProduct::getName,"汽车",2.0F));         List list = esMapper.selectList(queryWrapper);         String dsl = esMapper.getSource(queryWrapper);         System.out.println(dsl);     }     @Test     void select6() {         LambdaEsQueryWrapper<EsProduct>  queryWrapper = new LambdaEsQueryWrapper<>();           queryWrapper.in("categoryId",1,9); //where categroyId in (1,9) and ( name like '%汽车%' or brief like '%汽车%')         queryWrapper.and(                 w->w.match(EsProduct::getBrief,"高刷",1.0F)                         .or().match(EsProduct::getName,"高刷",2.0F)                         .or().match(EsProduct::getTags,"高刷",1.0F));         queryWrapper.orderByDesc(EsProduct::getSalesVolume);         EsPageInfo<EsProduct> esProductEsPageInfo = esMapper.pageQuery(queryWrapper, 2, 3);           String dsl = esMapper.getSource(queryWrapper);         System.out.println(dsl);     }       @Test     void select7() {         LambdaEsQueryWrapper<EsProduct>  queryWrapper = new LambdaEsQueryWrapper<>();             queryWrapper.match(EsProduct::getName,"水汽车门");         List<EsProduct> esProducts = esMapper.selectList(queryWrapper);         }  五、 原生Api调用 5.1查看索引mapping关系 GET /es_product/_mapping  5.2查看某个文档,具体字段的分词 GET /product/_doc/2/_termvectors?fields=brief ————————————————               原文链接:https://blog.csdn.net/qq_65142821/article/details/137908103 
  • [技术干货] Spring Boot对调用REST服务的支持
    Spring Boot 提供了多种方便的调用远程 REST 服务的方法。如果你正在开发一个非阻塞的反应式应用程序并且使用的是 Spring WebFlux,那么可以使用 WebClient。如果你更喜欢阻塞式 API,那么可以使用 RestClient 或 RestTemplate。 WebClient 如果你的类路径中有 Spring WebFlux,建议使用 WebClient 来调用远程 REST 服务。WebClient 接口提供了函数式风格的 API,并且完全是反应式的。 提示:如果没有编写反应式的 Spring WebFlux 应用程序,可以使用 RestClient 而不是 WebClient。这提供了一个类似的功能性 API,但它是阻塞式的,而不是反应式的。 Spring Boot 会为你创建并预先配置一个原型 WebClient.Builder bean。强烈建议将其注入到组件中,并使用它来创建 WebClient 实例。Spring Boot 配置了该构建器以共享 HTTP 资源,并以与服务器设置相同的方式反映编解码器设置,等等。  以下代码展示了一个典型的示例:  import reactor.core.publisher.Mono;  import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient;  @Service public class MyService {      private final WebClient webClient;      public MyService(WebClient.Builder webClientBuilder) {         this.webClient = webClientBuilder.baseUrl("https://example.org").build();     }      public Mono<Details> someRestCall(String name) {         return this.webClient.get().uri("/{name}/details", name).retrieve().bodyToMono(Details.class);     }  }  WebClient 运行时 Spring Boot 将根据应用程序类路径上可用的库自动检测要使用哪个 ClientHttpConnector 来驱动 WebClient。按照优先级顺序,支持以下客户端:  Reactor Netty Jetty RS 客户端 Apache HttpClient JDK HttpClient 如果类路径上存在多个客户端,将使用首选的客户端。  spring-boot-starter-webflux starter 默认依赖于 io.projectreactor.netty:reactor-netty,该依赖同时提供了服务器和客户端的实现。如果选择使用 Jetty 作为反应式服务器,应该添加对 Jetty Reactive HTTP 客户端库 org.eclipse.jetty:jetty-reactive-httpclient 的依赖。使用相同的技术来构建服务器和客户端有其优势,因为它会自动在客户端和服务器之间共享 HTTP 资源。  开发人员可以通过提供一个自定义的 ReactorResourceFactory 或 JettyResourceFactory bean 来覆盖 Jetty 和 Reactor Netty 的资源配置——这将对客户端和服务器都生效。  如果希望覆盖客户端的选择,可以定义自己的 ClientHttpConnector bean,并完全控制客户端的配置。  WebClient 自定义 根据你希望自定义的适用范围有多广泛,WebClient 自定义主要有三种方法。  要使任何自定义的范围尽可能狭窄,请注入自动配置的 WebClient.Builder,然后按需调用其方法。WebClient.Builder 实例是有状态的:对构建器的任何更改都会反映在使用该构建器随后创建的所有客户端中。如果想使用相同的构建器创建多个客户端,还可以考虑使用 WebClient.Builder other = builder.clone(); 对构建器进行克隆。  要对所有 WebClient.Builder 实例进行全局、附加的自定义,可以声明 WebClientCustomizer bean,并在注入点本地更改 WebClient.Builder。  最后,可以回退到原始 API 并使用 WebClient.create()。在这种情况下,不会应用任何自动配置或 WebClientCustomizer。  WebClient SSL支持 如果你需要对 WebClient 所使用的 ClientHttpConnector 进行自定义的 SSL 配置,你可以注入一个 WebClientSsl 实例,该实例可以与构建器的 apply 方法一起使用。  WebClientSsl 接口提供了对你在 application.properties 或 application.yaml 文件中定义的任何 SSL bundle 的访问。  以下代码展示了一个典型的例子:  import reactor.core.publisher.Mono;  import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientSsl; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient;  @Service public class MyService {      private final WebClient webClient;      public MyService(WebClient.Builder webClientBuilder, WebClientSsl ssl) {         this.webClient = webClientBuilder.baseUrl("https://example.org").apply(ssl.fromBundle("mybundle")).build();     }      public Mono<Details> someRestCall(String name) {         return this.webClient.get().uri("/{name}/details", name).retrieve().bodyToMono(Details.class);     }  }  RestClient 如果你的应用程序中未使用 Spring WebFlux 或 Project Reactor,建议使用 RestClient 来调用远程 REST 服务。  RestClient 接口提供了一个功能性的阻塞式 API。  Spring Boot 会为你创建并预先配置一个原型 RestClient.Builder bean。强烈建议在组件中注入它,并使用它来创建 RestClient 实例。Spring Boot 会为该构建器配置 HttpMessageConverters 和适当的 ClientHttpRequestFactory。  以下代码展示了一个典型的例子:  import org.springframework.stereotype.Service; import org.springframework.web.client.RestClient;  @Service public class MyService {      private final RestClient restClient;      public MyService(RestClient.Builder restClientBuilder) {         this.restClient = restClientBuilder.baseUrl("https://example.org").build();     }      public Details someRestCall(String name) {         return this.restClient.get().uri("/{name}/details", name).retrieve().body(Details.class);     }  }  RestClient 自定义 RestClient 的自定义主要有三种方法,具体取决于你希望自定义的适用范围有多广泛。  要使任何自定义的范围尽可能狭窄,请注入自动配置的 RestClient.Builder,然后按需调用其方法。RestClient.Builder 实例是有状态的:对构建器的任何更改都会反映在使用该构建器随后创建的所有客户端中。如果想使用相同的构建器创建多个客户端,还可以考虑使用 RestClient.Builder other = builder.clone(); 对构建器进行克隆。  要对所有 RestClient.Builder 实例进行全局、附加的自定义,可以声明 RestClientCustomizer bean,并在注入点本地更改 RestClient.Builder。  最后,可以回退到原始 API 并使用 RestClient.create()。在这种情况下,不会应用任何自动配置或 RestClientCustomizer。  RestClient SSL支持 如果你需要对 RestClient 所使用的 ClientHttpRequestFactory 进行自定义的 SSL 配置,你可以注入一个 RestClientSsl 实例,该实例可以与构建器的 apply 方法一起使用。  RestClientSsl 接口提供了对你在 application.properties 或 application.yaml 文件中定义的任何 SSL bundle 的访问。  以下代码展示了一个典型的例子:  import org.springframework.boot.autoconfigure.web.client.RestClientSsl; import org.springframework.stereotype.Service; import org.springframework.web.client.RestClient;  @Service public class MyService {      private final RestClient restClient;      public MyService(RestClient.Builder restClientBuilder, RestClientSsl ssl) {         this.restClient = restClientBuilder.baseUrl("https://example.org").apply(ssl.fromBundle("mybundle")).build();     }      public Details someRestCall(String name) {         return this.restClient.get().uri("/{name}/details", name).retrieve().body(Details.class);     }  }  如果你需要在 SSL bundle 之外应用其它自定义设置,你可以使用 ClientHttpRequestFactorySettings 类与 ClientHttpRequestFactories 配合使用:  import java.time.Duration;  import org.springframework.boot.ssl.SslBundles; import org.springframework.boot.web.client.ClientHttpRequestFactories; import org.springframework.boot.web.client.ClientHttpRequestFactorySettings; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.stereotype.Service; import org.springframework.web.client.RestClient;  @Service public class MyService {      private final RestClient restClient;      public MyService(RestClient.Builder restClientBuilder, SslBundles sslBundles) {         ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS             .withReadTimeout(Duration.ofMinutes(2))             .withSslBundle(sslBundles.getBundle("mybundle"));         ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(settings);         this.restClient = restClientBuilder.baseUrl("https://example.org").requestFactory(requestFactory).build();     }      public Details someRestCall(String name) {         return this.restClient.get().uri("/{name}/details", name).retrieve().body(Details.class);     }  } RestTemplate Spring Framework 的 RestTemplate 类早于 RestClient,是许多应用程序用于调用远程 REST 服务的经典方式。当你有不想迁移到 RestClient 的现有代码,或者因为你已经熟悉 RestTemplate API 时,你可能会选择使用 RestTemplate。  由于 RestTemplate 实例在使用前通常需要进行自定义,因此 Spring Boot 不提供任何单个自动配置的 RestTemplate bean。但是,它确实自动配置了一个 RestTemplateBuilder,可以在需要时用于创建 RestTemplate 实例。自动配置的 RestTemplateBuilder 确保将合理的 HttpMessageConverters 和适当的 ClientHttpRequestFactory 应用到 RestTemplate 实例上。  以下代码展示了一个典型的例子:  import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate;  @Service public class MyService {      private final RestTemplate restTemplate;      public MyService(RestTemplateBuilder restTemplateBuilder) {         this.restTemplate = restTemplateBuilder.build();     }      public Details someRestCall(String name) {         return this.restTemplate.getForObject("/{name}/details", Details.class, name);     }  }  RestTemplateBuilder 包含许多有用的方法,可用于快速配置 RestTemplate。例如,要添加基本认证(BASIC authentication)支持,你可以使用 builder.basicAuthentication("user", "password").build()。  RestTemplate 自定义 RestTemplate 的自定义主要有三种方法,具体取决于你希望自定义的适用范围有多广泛。  要使任何自定义的范围尽可能狭窄,请注入自动配置的 RestTemplateBuilder,然后按需调用其方法。每次方法调用都会返回一个新的 RestTemplateBuilder 实例,因此这些自定义只影响该构建器的使用。  要进行全局的、附加的自定义,请使用 RestTemplateCustomizer bean。所有这样的 bean 都会自动注册到自动配置的 RestTemplateBuilder 中,并应用于使用它构建的任何模板。  以下示例展示了一个自定义器,它配置了对除 192.168.0.5 之外的所有主机使用代理:  import org.apache.hc.client5.http.classic.HttpClient; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner; import org.apache.hc.client5.http.routing.HttpRoutePlanner; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.protocol.HttpContext;  import org.springframework.boot.web.client.RestTemplateCustomizer; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.web.client.RestTemplate;  public class MyRestTemplateCustomizer implements RestTemplateCustomizer {      @Override     public void customize(RestTemplate restTemplate) {         HttpRoutePlanner routePlanner = new CustomRoutePlanner(new HttpHost("proxy.example.com"));         HttpClient httpClient = HttpClientBuilder.create().setRoutePlanner(routePlanner).build();         restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient));     }      static class CustomRoutePlanner extends DefaultProxyRoutePlanner {          CustomRoutePlanner(HttpHost proxy) {             super(proxy);         }          @Override         protected HttpHost determineProxy(HttpHost target, HttpContext context) throws HttpException {             if (target.getHostName().equals("192.168.0.5")) {                 return null;             }             return super.determineProxy(target, context);         }      }  } 置所做的那样,可以使用 RestTemplateBuilderConfigurer 进行配置。以下示例展示了一个 RestTemplateBuilder,它与 Spring Boot 的自动配置所做的相匹配,但还指定了自定义的连接和读取超时:  import java.time.Duration;  import org.springframework.boot.autoconfigure.web.client.RestTemplateBuilderConfigurer; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;  @Configuration(proxyBeanMethods = false) public class MyRestTemplateBuilderConfiguration {      @Bean     public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer configurer) {         return configurer.configure(new RestTemplateBuilder())             .setConnectTimeout(Duration.ofSeconds(5))             .setReadTimeout(Duration.ofSeconds(2));     }  }  最极端(且很少使用)的选项是创建自己的 RestTemplateBuilder bean,而不使用配置器。除了替换自动配置的构建器外,这还阻止了任何 RestTemplateCustomizer bean 的使用。  RestTemplate SSL支持 如果你需要在 RestTemplate 上进行自定义的 SSL 配置,你可以像这个示例中所示,将 SSL bundle 应用到 RestTemplateBuilder 上:  import org.springframework.boot.docs.io.restclient.resttemplate.Details; import org.springframework.boot.ssl.SslBundles; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate;  @Service public class MyService {      private final RestTemplate restTemplate;      public MyService(RestTemplateBuilder restTemplateBuilder, SslBundles sslBundles) {         this.restTemplate = restTemplateBuilder.setSslBundle(sslBundles.getBundle("mybundle")).build();     }      public Details someRestCall(String name) {         return this.restTemplate.getForObject("/{name}/details", Details.class, name);     }  }   RestClient和RestTemplate的HTTP客户端检测 Spring Boot会根据应用程序类路径上可用的库自动检测使用RestClient和RestTemplate的HTTP客户端。按照优先级顺序,支持以下客户端:  Apache HttpClient Jetty HttpClient OkHttp(已弃用) 简单的JDK客户端(HttpURLConnection) 如果类路径上存在多个客户端,将使用最优先的客户端。 ————————————————  原文链接:https://blog.csdn.net/panghuangang/article/details/136156769 
  • [技术干货] springboot系列八: springboot静态资源访问,Rest风格请求处理
    在本篇文章中,我们将探讨如何在 Spring Boot 中处理静态资源访问、实现 Rest 风格的请求处理以及使用接收参数相关的注解。这些功能将帮助您更高效地开发和管理 Spring Boot 应用程序。 本篇需要用到的项目:  WEB开发-静态资源访问 官方文档 在线文档: 基本介绍 1.只要静态资源放在类路径下: /static, /public, /resources, /META-INF/resources 可以被直接访问 - 对应文件 WebProperties.java private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { “classpath:/META-INF/resources/”, “classpath:/resources/”, “classpath:/static/”, “classpath:/public/” }; 2.常见静态资源: JS, CSS, 图片(.jpg, .png, .gif, .bmp, .svg), 字体文件(Fonts)等 3.访问方式: 默认: 项目根路径 / + 静态资源名, 比如 http://localhost:8080/1.jpg - 设置WebMvcProperties.java private String staticPathPattern = “/**”; 快速入门 1.创建SpringBoot项目springbootweb, 这里使用灵活配置方式来创建项目, 参考springboot快速入门 2.创建相关静态资源目录, 并放入测试图片, 没有目录, 自己创建即可, 浏览器访问 http://localhost:8080/4.png , 完成测试. 注意事项和细节 1.静态资源访问原理: 静态映射是 /** , 也就是对所有请求拦截. 请求进来, 先看Controller能不能处理, 不能处理的请求交给静态资源处理器, 如果静态资源找不到则响应404页面. 2.改变静态资源访问前缀, 比如我们希望 http://localhost:8080/image/1.png 去请求静态资源. 应用场景: http://localhost:8080/1.png, 静态资源访问前缀和控制器请求路径冲突 被Controller拦截 解决方案: 1)创建src/main/resources/application.yml spring:   mvc:     static-path-pattern: /image/** 2)启动, http://localhost:8080/image/1.png 测试 3.改变默认的静态资源路径, 比如希望在类路径下增加zzwimg目录 作为静态资源路径, 并完成测试. 1)如图所示 2)配置 application.yml, 增加路径 spring:   mvc:     static-path-pattern: /image/** #修改静态资源访问 路径/前缀   web:     resources:       #修改/指定 静态资源的访问路径/位置       static-locations: [classpath:/zzwimg/] #String[] staticLocations 3)测试, 浏览器输入 http://localhost:8080/image/6.png(没错, 是image/6.png, 不是image/zzwimg/6.png). 还有, 一定要保证工作目录target下 有 6.png, 如果没有, 请rebuild下项目, 再重启项目. 4)如果你配置 static-locations, 原来的访问路径就会被覆盖, 如果需要保留, 再指定一下即可.    web:     resources:       #修改/指定 静态资源的访问路径/位置 String[] staticLocations       static-locations: ["classpath:/zzwimg/", "classpath:/META-INF/resources/",                          "classpath:/resources/", "classpath:/static/", "classpath:/public/"] Rest风格请求处理 基本介绍 1.Rest风格支持 (使用HTTP请求方式来表示对资源的操作)  2.举例说明 请求方式: /monster GET-获取妖怪 DELETE-删除妖怪 PUT-修改妖怪 POST-保存妖怪  应用实例 1.创建src/main/java/com/zzw/springboot/controller/MonsterController.java  @RestController public class MonsterController {      //等价的写法     //@RequestMapping(value = "/monster", method= RequestMethod.GET)     @GetMapping("/monster")     public String getMonster() {         return "GET-查询妖怪";     }      //等价写法     //@RequestMapping(value = "/monster", method = RequestMethod.POST)     @PostMapping("/monster")     public String saveMonster() {         return "POST-保存妖怪";     }      //等价写法     @RequestMapping(value = "/monster", method = RequestMethod.PUT)     @PutMapping("/monster")     public String putMonster() {         return "PUT-修改妖怪";     }      //等价写法     @RequestMapping(value = "/monster", method = RequestMethod.DELETE)     @DeleteMapping("/monster")     public String deleteMonster() {         return "DELETE-删除妖怪";     } }  2.使用Postman完成测试, 请求url: http://localhost:8080/monster 注意事项和细节 在SpringMVC中我们学过,SpringMVC系列四: Rest-优雅的url请求风格 1.客户端是Postman 可以直接发送 Put, Delete等方式请求, 可不设置Filter. 2.如果要SpringBoot支持 页面表单的Rest功能, 则需要注意如下细节 1)Rest风格请求核心Filter: HiddenHttpMethodFilter, 表单请求会被HiddenHttpMethodFilter拦截, 获取到表单_method的值, 再判断是PUT/DELETE/PATCH(注意: PATCH方法是新引入的, 是对PUT方法的补充, 用来对已知资源进行局部更新, PATCH和PUT方法的区别)  2)如果要SpringBoot支持 页面表单的Rest功能, 需要在application.yml启用filter功能, 否则无效.  3)修改application.yml, 启用filter功能  spring:   mvc:     static-path-pattern: /image/** #修改静态资源访问 路径/前缀     hiddenmethod:       filter:         enabled: true #启动HiddenHttpMethodFilter, 支持rest 4)修改对应的页面, 然后测试.  <!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <title>rest</title> </head> <body> <h1>测试rest风格的url, 完成get请求</h1> <form action="/monster" method="get">   u: <input type="text" name="name"><br/>   <input type="submit" value="点击提交"> </form> <h1>测试rest风格的url, 完成post请求</h1> <form action="/monster" method="post">   u: <input type="text" name="name"><br/>   <input type="submit" value="点击提交"> </form> <h1>测试rest风格的url, 完成put请求</h1> <form action="/monster" method="post">     <!--通过隐藏域传递_method参数指定值-->     <input type="hidden" name="_method" value="PUT">   u: <input type="text" name="name"><br/>   <input type="submit" value="点击提交"> </form> <h1>测试rest风格的url, 完成delete请求</h1> <form action="/monster" method="post">     <input type="hidden" name="_method" value="delete">   u: <input type="text" name="name"><br/>   <input type="submit" value="点击提交"> </form> </body> </html> 原文链接:https://blog.csdn.net/qq_18817831/article/details/139912069
  • [技术干货] Easy-Es笔记
    一、Easy-ES概述 Easy-Es(简称EE)是一款由国内开发者打造并完全开源的ElasticSearch-ORM框架。在原生 RestHighLevelClient 的基础上,只做增强不做改变,为简化开发、提高效率而生。Easy-Es采用和MP一致的语法设计,降低ElasticSearch搜索引擎使用门槛,和额外学习成本,并大幅减少开发者工作量,帮助企业降本提效。在有些方面甚至比MP更简单,同时也融入了更多ElasticSearch独有的功能,助力您快速实现各种场景的开发。(也可以理解为是操作es的mp)  官网地址:https://www.easy-es.cn/ 项目地址:https://gitee.com/dromara/easy-es/ 开源社区:https://gitee.com/dromara/ EE特性: 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑。 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作 强大的 CRUD 操作:内置通用 Mapper,仅仅通过少量配置即可实现大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求。 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错段。 支持主自动生成:支持2 种主键策略,可自由配置,完美解决主键问题。 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作。 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )。 内置分页插件:基于RestHighLevelClient 物理分页,开发者无需关心具体操作,且无需额外配置插件,写分页等同于普通 List 查询,且保持和PageHelper插件同样的分页返回字段,无需担心命名影响。  MySQL功能全覆盖:MySQL中支持的功能通过EE都可以轻松实现。 支持ES高阶语法:支持高亮搜索,分词查询,权重查询,Geo地理位置查询,IP查询,聚合查询等高阶语法。 良好的拓展性:底层仍使用RestHighLevelClient,可保持其拓展性,开发者在使用EE的同时,仍可使用RestHighLevelClient的功能。  EE框架结构: EE与Spring Data ES技术对比: 二、Easy-ES快速入门 1、创建maven工程 2、引入依赖:  <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">     <modelVersion>4.0.0</modelVersion>      <!-- springboot工程 -->     <parent>         <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-starter-parent</artifactId>         <version>2.4.5</version>         <relativePath/> <!-- lookup parent from repository -->     </parent>      <groupId>cn.aopmin</groupId>     <artifactId>EE-demo</artifactId>     <version>1.0-SNAPSHOT</version>      <properties>         <maven.compiler.source>8</maven.compiler.source>         <maven.compiler.target>8</maven.compiler.target>         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>         <!--es相对稳定版本,esay-es会依赖这个版本的jar包-->         <elasticsearch.version>7.14.0</elasticsearch.version>     </properties>      <dependencies>         <!-- web,排除springboot内置的es依赖,以防和easy-es中的依赖冲突 -->         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-web</artifactId>             <exclusions>                 <exclusion>                     <groupId>org.elasticsearch.client</groupId>                     <artifactId>elasticsearch-rest-high-level-client</artifactId>                 </exclusion>                 <exclusion>                     <groupId>org.elasticsearch</groupId>                     <artifactId>elasticsearch</artifactId>                 </exclusion>             </exclusions>         </dependency>          <!-- elasticsearch -->         <dependency>             <groupId>org.elasticsearch.client</groupId>             <artifactId>elasticsearch-rest-high-level-client</artifactId>             <version>${elasticsearch.version}</version>         </dependency>         <dependency>             <groupId>org.elasticsearch</groupId>             <artifactId>elasticsearch</artifactId>             <version>${elasticsearch.version}</version>         </dependency>          <!-- easy-es -->         <dependency>             <groupId>org.dromara.easy-es</groupId>             <artifactId>easy-es-boot-starter</artifactId>             <version>2.0.0-beta3</version>         </dependency>          <!-- junit -->         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-test</artifactId>             <scope>test</scope>         </dependency>     </dependencies>      <!--打包插件-->     <build>         <plugins>             <plugin>                 <groupId>org.springframework.boot</groupId>                 <artifactId>spring-boot-maven-plugin</artifactId>             </plugin>         </plugins>     </build>  </project> 3、创建启动类:  package cn.aopmin;  import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;  @SpringBootApplication public class EEApplication {     public static void main(String[] args) {         SpringApplication.run(EEApplication.class, args);     } } 4、在application.yml中配置easy-es:  easy-es:    address: 192.168.150.123:9200   # username: XXXX #如果es无账号密码则可不配置此行   # password: XXXX #如果es无账号密码则可不配置此行 5、创建实体类:  package cn.aopmin.pojo;  import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.dromara.easyes.annotation.IndexField; import org.dromara.easyes.annotation.IndexId; import org.dromara.easyes.annotation.IndexName; import org.dromara.easyes.annotation.rely.Analyzer; import org.dromara.easyes.annotation.rely.FieldType; import org.dromara.easyes.annotation.rely.IdType;  /**  * ES实体类  *  * @author 白豆五  * @version 2023/07/26  * @since JDK8  */ @Data @AllArgsConstructor @NoArgsConstructor @IndexName("document") // 指定索引名称 public class Document {      /**      * es中的唯一id      */     @IndexId(type = IdType.CUSTOMIZE) //id生成策略为自定义     private String id;      /**      * 文档标题      */     @IndexField( fieldType = FieldType.TEXT, analyzer = Analyzer.IK_MAX_WORD, searchAnalyzer = Analyzer.IK_SMART)     private String title;      /**      * 文档内容      */     @IndexField( fieldType = FieldType.KEYWORD)     private String content; } 6、创建mapper接口:  package cn.aopmin.mapper;  import cn.aopmin.pojo.Document; import org.dromara.easyes.core.core.BaseEsMapper;  /**  * Mapper接口  *  * @author 白豆五  * @version 2023/07/26  * @since JDK8  */ public interface DocumentMapper extends BaseEsMapper<Document> { } 7、在启动类上配置包扫描:(@EsMapperScan)  package cn.aopmin;  import org.dromara.easyes.starter.register.EsMapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;  @EsMapperScan("cn.aopmin.mapper") // 指定Mapper接口所在的包名 @SpringBootApplication public class EEApplication {     public static void main(String[] args) {         SpringApplication.run(EEApplication.class, args);     } } 8、测试  示例1:创建索引库  package cn.aopmin.test;  import cn.aopmin.mapper.DocumentMapper; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest;  import javax.annotation.Resource;  /**  * EasyEs快速入门测试类  *  * @author 白豆五  * @version 2023/07/26  * @since JDK8  */ @SpringBootTest public class EasyEsTest {      @Resource     private DocumentMapper documentMapper;      /**      * 测试创建索引库      */     @Test     public void testCreateIndex() {         //创建索引,根据当前mapper对应实体类信息及其注解配置生成索引信息         Boolean success = documentMapper.createIndex();         System.out.println(success ? "创建索引成功" : "创建索引失败");     } }  示例2:添加文档  /**  * 测试添加文档  */ @Test public void testAddDocument() {     // 持久层添加数据跟mp类似也是insert方法     documentMapper.insert(new Document("1", "程序员的幽默", "为了让你开心,我特意编写了一段关于程序员的小笑话,希望你能喜欢。")); } 示例3:测试查询标题中包含程序员的文档列表(就连解析结果的活都帮我们做了)  /**  * 测试查询title中包含程序员的文档列表  */ @Test public void testSearch() {     // 条件构造器     LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();     wrapper.eq(Document::getTitle, "程序"); // 类似match查询     // 查询文档列表     List<Document> list = documentMapper.selectList(wrapper);     list.forEach(System.out::println); } ————————————————              原文链接:https://blog.csdn.net/qq_46921028/article/details/131945524 
  • [技术干货] es相关的官方客户端与spring客户端对比与介绍
     1. 客户端版本种类 es提供的  TransportClient 传统的客户端,基于TCP传输协议与Elasticsearch通信。 已经被弃用,不推荐使用。 适用于Elasticsearch 5.x及以前的版本 因为Elasticsearch 6.x及以上版本已不再支持TCP Transport协议,TransportClient无法连接Elasticsearch集群。 RestHighLevelClient 是一个高级的REST客户端,主要用于与Elasticsearch集群通信。 基于Java Low Level REST Client构建,提供更高级的API,隐藏底层细节。 主要用于Kibana和Logstash等工具。 RestHighLevelClient适用于Elasticsearch 6.2.0及以上版本。 spring提供的  ES存储库操作:简单的CRUD 特点 当你需要进行简单的CRUD操作(创建、读取、更新、删除文档)时,使用ES存储库操作是一个不错的选择。 这是因为Spring Data Elasticsearch会自动生成存储库接口的实现,从而使你的代码更简洁、易于理解。 ElasticsearchTemplate 基于ElasticsearchRestTemplate封装,提供更高级的API,如各种CRUD操作。 也需要依赖Spring Framework。 ElasticsearchRestTemplate 是一个Spring的RestTemplate的扩展,用于与Elasticsearch RESTful API交互。 基于Spring的RestTemplate,提供Elasticsearch的自动化JSON序列化和反序列化。 需要依赖Spring Framework。 ElasticsearchOperations:接口实现 使用选择 如果需要更多spring集成相关的功能,则使用ElasticsearchTemplate。 如果需要直接访问Elasticsearch RESTful接口,则使用ElasticsearchRestTemplate。 性能和功能上,ElasticsearchTemplate略胜一筹。 2. spring的相关操作客户端 2.1 ES存储库操作:简单的CRUD 特点  当你需要进行简单的CRUD操作(创建、读取、更新、删除文档)时,使用ES存储库操作是一个不错的选择。 这是因为Spring Data Elasticsearch会自动生成存储库接口的实现,从而使你的代码更简洁、易于理解。 核心  简单的crud操作 不支持复杂的查询检索 使用步骤  实体类  @Document(indexName = "encrypt_log", type = "encrypt_log") public class EncryptLogEntity {        //对应字段     private String srcIp;     private String dstIp;     //...          // getters and setters }  1 2 3 4 5 6 7 8 9 10 11 创建ElasticsearchRepository接口  public interface EncryptLogRepository extends ElasticsearchRepository<EncryptLogEntity, String> {      } 1 2 3 对应的服务中注入接口,并编写方法  @Autowired private MyDocumentRepository myDocumentRepository;  // 创建文档 myDocumentRepository.save(document);  // 读取文档 MyDocument document = myDocumentRepository.findById(id).orElse(null);  // 更新文档 myDocumentRepository.save(updatedDocument);  // 删除文档 myDocumentRepository.deleteById(id); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 2.2 ElasticsearchTemplate:复杂的操作:增删改查 特点  当你需要执行更复杂的查询、聚合操作,或者需要使用一些特定的Elasticsearch功能时,使用ElasticsearchTemplate可能是一个更好的选择。 基于 Transport Client与ES交互。 核心  更复杂的查询、聚合操作 -> ElasticsearchTemplate 需要更简单、方便地集成ES时,可以使用ElasticsearchTemplate。 使用  @Autowired private ElasticsearchTemplate elasticsearchTemplate;  // 构建查询 QueryBuilder queryBuilder = QueryBuilders.matchQuery("title", "example");  // 执行查询 SearchHits<MyDocument> searchHits = elasticsearchTemplate.search(new NativeSearchQueryBuilder()         .withQuery(queryBuilder)         .build(), MyDocument.class);  1 2 3 4 5 6 7 8 9 10 11 简单查询例子  @Autowired private ElasticsearchTemplate esTemplate;  List<Map<String, Object>> documents = ... esTemplate.bulkIndex(documents); 1 2 3 4 5 2.3 ElasticsearchRestTemplate:跨节点的搜索查询 特点  适用于更复杂的查询操作 情景  需要更高级ES功能时,使用ElasticsearchRestTemplate。 需要更高级ES功能时,使用ElasticsearchRestTemplate。 样例  @Autowired private ElasticsearchRestTemplate esRestTemplate;  List<Map<String, Object>> documents = ... esRestTemplate.bulkIndex(documents, Map.class);    SearchQuery searchQuery = new NativeSearchQueryBuilder()      .withQuery(matchQuery("name", "spring"))      .build(); List<Map> results = esRestTemplate.search(searchQuery, Map.class)      .getContent(); 1 2 3 4 5 6 7 8 9 10 11 2.4 ElasticsearchTemplate和ElasticsearchRestTemplate主要的区别在于 底层实现:  ElasticsearchTemplate 底层使用 Elasticsearch Java API 实现。 ElasticsearchRestTemplate 底层使用 Elasticsearch Rest API 实现。 使用范围:  ElasticsearchTemplate 只能在内部使用,不支持跨节点操作。 ElasticsearchRestTemplate 支持跨节点操作。 功能:  ElasticsearchTemplate 功能更全面,支持多种操作,如索引、搜索、删除等。 ElasticsearchRestTemplate 主要用于搜索。 性能:  ElasticsearchTemplate 性能更高,不需要经过 HTTP 请求。 ElasticsearchRestTemplate 需要通过 HTTP 请求,性能相对较低。 复杂度:  ElasticsearchTemplate 复杂度较高,需要深入理解 Elasticsearch Java API。 ElasticsearchRestTemplate 复杂度较低,只需要理解 Elasticsearch 的 REST API。 总的来说:  如果只在单个节点上操作 Elasticsearch,优先选择 ElasticsearchTemplate。 如果需要跨节点操作,或者为了简单起见,可以选择 ElasticsearchRestTemplate。 2.4 ElasticsearchOperations:接口 特点  ElasticsearchOperations是一个接口,代表了基础的Elasticsearch操作。 ElasticsearchTemplate是ElasticsearchOperations的一个实现。 功能  ElasticsearchOperations定义了基本的Elasticsearch操作,如插入、查询、删除等。 ElasticsearchTemplate实现了ElasticsearchOperations接口,提供了更多功能,例如: 批量操作 分页查询 对复杂对象进行转换 映射管理 索引管理 使用  一般情况下,程序直接使用ElasticsearchTemplate。  在测试中,可以使用ElasticsearchOperations接口,达到分离依赖的目的。  3. 不同版本的依赖及对应方法的选择 进入spring对应的官网  随便找一个api——找到历史文档及版本对应版本对应   然后找到对应的官方操作客户端方法   4. 总结 es客户端 TransportClient :已过时 RestHighLevelClient:es官方推荐的版本,但有时候在spring中被废弃 spring客户端 ES存储库 适用于简单的CRUD操作 可阅读性强,操作编写难度麻烦 ElasticsearchTemplate 复杂的增删改查操作 单节点中使用 ElasticsearchRestTemplate 多节点,和相对于ElasticsearchTemplate 更便捷的方法调用 更加多样的查询封装方法 ElasticsearchOperations 是一个接口,使用的较少 ————————————————               原文链接:https://blog.csdn.net/weixin_44245613/article/details/130619332 
  • [技术干货] ES快速开发,ElasticsearchRestTemplate基本使用以及ELK快速部署
    最近博主有一些elasticsearch的工作,所以更新的慢了些,现在就教大家快速入门,并对一些基本的查询、更新需求做一下示例,废话不多说开始:  1. ES快速上手 es下载:[https://elasticsearch.cn/download/]()这里关于es所需要的链接基本都有,可以快速下载使用 当你解压好了归档文件之后,Elasticsearch 已经准备好运行了  1 cd elasticsearch-<version> 2 ./bin/elasticsearch  es默认端口9200;transport端口是9300;transport端口主要用来java客户端操作,启动成功后,我们来安装一下kibana界面操作es的工具,也可以下载header,但是kibana界面比较友好并且最后部署ELK的时候也需要该工具,所以博主就直接安装kibana了  kibana下载:[https://elasticsearch.cn/download/]()还是跟es一样的链接,下载后解压并编辑config目录下编辑kibana.yml文件,主要配置如下:    1 server.port: 15601     1 server.host: "0.0.0.0"     1 elasticsearch.hosts: ["http://localhost:9200"]  只需要修改这几处配置就可以,前提是kibana的版本必须与es的版本是相同的,否则会包很多错误,并且启动失败,Linux启动时不能使用root用户启动,必须自己添加个人用户才可以,命令如下:  添加用户: 1 useradd testuser  设置密码: 1 passwd testuser  将我们的文件夹用户权限改变一下要不然启动时候老是提示没有权限: 1 chown -R testuser:testuser kibana  现在进入我们kibana的文件夹,以testuser启动kibana: 1 /bin/kibana  访问地址:http://localhost:15601 当看到这里的时候就已经OK了,我们终于可以开始使用es了。  我就不介绍es是干啥用的了,es具有分片的概念,分为主分片和副本分片,创建索引的时候一旦设置副本分片,必须有大于等于2台的机器,每个机器都有es,es之间的交互,需要自己在配置文件中作修改,否则不配置,永远只是单机,并且主分片在建索引的时候必须考虑清楚减多少个主分片,因为以后如果需要修改主分片,必须重新创建索引,你添加或则减少一个主分片,es往分片中存放数据的时候都会变,但是副本分片不一样,因为他是数据冗余的,一旦主分片宕机,副本会当选主分片,并且是要主分片存在,副本没有也可以,副本的作用就是提高数据的吞吐量。好了,开始实战: 点击kibana的Dev Tools按钮,就可以在面板里写语句操作索引了: 建立索引:shards主分片  replicas副本分片设置的数量,看你有几台机器-1  PUT /test {   "settings": {     "number_of_shards": 5,     "number_of_replicas": 1   },   "mappings": {     "_doc": {       "properties": {         "name": {           "type": "text",           "analyzer": "ik_max_word",           "search_analyzer": "ik_smart"         },         "age": {           "type":"integer"         }       }     }   } }   建立mappings做好字段类型,并且text类型中使用分词器,不要使用默认的分词器,默认的分词器一个汉字分一个,查询出来基本没啥价值,中文分词器是ik,可以上网搜一下下载到es里面。    大家上过语文课,都知道语句有歧义问题,就比如武汉市长江大桥,可以断成武汉市长、江大桥;武汉市、长江大桥;这就是分词器如何切分的问题,所以es有关键字查询term,进行完全匹配,不进行分词器query匹配,除了这些,中文还有同义词一说,比如苹果水果与苹果手机,大家搜索的时候基本都是输入苹果,但是出来的却是苹果手机,水果很少,这就是因为es也可以做同义词查询。但是需要配置同义词文件,具体操作可以自行上网解决,主要就是创建索引的时候,使用自己在config中编辑的文本文件,该文件中有自己要使用到的同义词,比如:iPhone,苹果手机;    我们现在再来进行实战开发,本人接触的是使用ElasticsearchRestTemplate进行开发,该类基本含括了大部分需求开发查询。下面开始举例:  搜索查询:   1 String[] includes = new String[] {  2                     "paperBaseId"  3                     ,"auditInfoStatus"  4             };  5             SourceFilter sourceFilter = new FetchSourceFilterBuilder().withIncludes(includes).build();  6             SearchQuery searchQuery = new NativeSearchQueryBuilder()  7                     .withSourceFilter(sourceFilter)  8                     // 添加查询条件  9                     .withQuery(QueryBuilders.termsQuery("paperBaseId",paperBaseId)) 10                     .build(); 11             List<EsPaperBase> esPaperBaseList = elasticsearchRestTemplate.queryForList(searchQuery,EsPaperBase.class);  1 //单索引匹配更新      2             Map<String, Object> params = new HashMap<String, Object>();  3             params.put("flag", deleteFlag);  4             //ctx._source即为该索引本身  5             String code = "ctx._source.deleteFlag=params.flag;";  6             ScriptType type = ScriptType.INLINE;  7             //使用脚本进行更新字段值  8             Script script = new Script(type, Script.DEFAULT_SCRIPT_LANG, code, params);  9  10             UpdateByQueryRequest updateByQueryRequest = new UpdateByQueryRequest(); 11             updateByQueryRequest.indices("exam_information");//设置索引 12             updateByQueryRequest.setDocTypes("doc");//设置文档,固定doc 13             updateByQueryRequest.setQuery(QueryBuilders.termsQuery("paperBaseId", paperBaseId));//设置查询 14             updateByQueryRequest.setScript(script);//如果有脚本,则添加 15             updateByQueryRequest.setConflicts("proceed"); // 设置版本冲突时继续 16             updateByQueryRequest.setRefresh(true);//请求结束后,对我们写入的索引进行调用刷新 17             this.elasticsearchTemplate.getClient().updateByQuery(updateByQueryRequest, RequestOptions.DEFAULT);//进行更新  1 //多索引匹配批量更新  2         Map<String,Object> updateMap = new HashMap<>();  3         updateMap.put("deleteFlag",deleteFlag);  4         updateMap.put("lastUpdateTime",currDatetime);  5         UpdateRequest doc = new UpdateRequest().doc(updateMap);  6         List<UpdateQuery> updateQuerys = new ArrayList<>();  7         //生成批量更新操作  8         paperBaseId.stream().forEach(id ->{  9             UpdateQuery build = new UpdateQueryBuilder() 10                     .withUpdateRequest(doc) 11                     .withDoUpsert(true) 12                     .withIndexName("paper_base") 13                     .withType("doc") 14                     .withId(id).build(); 15             updateQuerys.add(build); 16         }); 17         elasticsearchTemplate.bulkUpdate(updateQuerys,BulkOptions.builder().withRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).build()); 18           1 //查询操作  2         MatchQueryBuilder lastUpdateUser = QueryBuilders.matchQuery("personId", userId);  3         MatchQueryBuilder deleteflag = QueryBuilders.matchQuery("deleteFlag", BaseEntity.DEL_FLAG_DELETE);  4         //创建bool多条件查询  5         BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();  6         BoolQueryBuilder mustQuery = boolQueryBuilder.must(lastUpdateUser).must(deleteflag);  7         //嵌套索引,需要使用nest查询  8         mustQuery.must(QueryBuilders.nestedQuery("entityNodes", QueryBuilders.termQuery("entityNodes.node_type", recyclePaperDTO.getNodeType()), ScoreMode.None));  9         //可以使用should查询,不是必需条件 10         BoolQueryBuilder nodeQueryBuilder = QueryBuilders.boolQuery(); 11         nodeQueryBuilder.should(QueryBuilders.nestedQuery("entityNodes", QueryBuilders.wildcardQuery("entityNodes.parent_ids", "*," + recyclePaperDTO.getNodeId() + "*"), ScoreMode.None)); 12         nodeQueryBuilder.should(......); 13         mustQuery.must(nodeQueryBuilder); 14         //查询使用排序 15         SortBuilder order = new FieldSortBuilder("lastUpdateTime").order(SortOrder.DESC); 16         //可以使用高亮显示,就是html标签 17         HighlightBuilder highlightBuilder = new HighlightBuilder(); 18         highlightBuilder.preTags("<span class='highlighted'>") 19                 .postTags(</span>) 20                 .field("paperBaseName");//哪个字段高亮 21         //使用分页查询 22         SearchQuery nativeSearchQueryBuilder = new NativeSearchQueryBuilder() 23                 .withQuery(mustQuery).withSort(order).withHighlightBuilder(highlightBuilder) 24                 .withPageable(PageRequest.of(recyclePaperDTO.getPageNum()-1, recyclePaperDTO.getPageSize())).build(); 25         //进行查询,entityMapper使用默认的也可,EsPaperBase.class是需要自己映射的查询类 26         elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder, EsPaperBase.class, new HighlightResultMapper(entityMapper)); 27           1 @Data  2 @Builder  3 @NoArgsConstructor  4 @AllArgsConstructor  5 @Document(indexName = "paper_base", type = "doc")  6 @Setting(settingPath = "/elasticsearch/settings.json")//可设置主分片、副本分片、设置默认停用词等  7 public class EsPaperBase {  8   9     @Id 10     @Field(type = FieldType.Keyword, name = "paperBaseId") 11     private String paperBaseId; 12  13     /** 14      * 试卷名称 15      */ 16     @MultiField(mainField = @Field(type = FieldType.Text, analyzer = "standard" , name = "paperBaseName"), 17             otherFields = { 18                     @InnerField(suffix = "zh", type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart"), 19                     @InnerField(suffix = "en", type = FieldType.Text, analyzer = "english"), 20             }) 21     private String paperBaseName; 22  23     /** 24      * 共享级别名,可以使用分词器查询,模糊匹配 25      */ 26     @Field(type = FieldType.Text, name = "shareLevelName") 27     private String shareLevelName; 28  29  30     /** 31      * 创建人,不可使用分词器查询,精准匹配 32      */ 33     @Field(type = FieldType.Keyword, name = "personId") 34     private String personId; 35  36  37     /** 38      * 创建时间 39      */ 40     @Field(type = FieldType.Date, name = "createtime", format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss") 41     private String createtime; 42  43     /** 44      * 更新时间 45      */ 46     @Field(type = FieldType.Date, name = "lastUpdateTime", format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss") 47     private String lastUpdateTime; 48  49     /** 50      * 删除标识    0:未删除,1:已删除 51      */ 52     @Field(type = FieldType.Keyword, name = "deleteFlag") 53     private String deleteFlag; 54     /** 55      * 试卷推荐,内嵌字段 56      */ 57     @Field(type=FieldType.Nested,name="paperRecommends") 58     private List<EsPaperRecommend> paperRecommends; 59      60     ...... 61 }  1 {//setting.json  2     "index": {  3         "number_of_shards": "5",  4         "number_of_replicas": "2",  5         "refresh_interval": "1s",  6         "max_rescore_window": 10000000  7     },  8     "analysis": {  9       "filter": { 10         "spanish_stop": { 11           "type":        "stop", 12           "stopwords": [ "si", "esta", "el", "la" ]   13         }, 14         "light_spanish": {  15           "type":     "stemmer", 16           "language": "light_spanish" 17         } 18       }, 19       "analyzer": { 20         "my_spanish": { 21           "tokenizer": "spanish", 22           "filter": [ //顺序很重要 23             "lowercase", 24             "asciifolding", 25             "spanish_stop", 26             "light_spanish" 27           ] 28         } 29       } 30     } 31   }    现在很多公司基本使用分布式架构应用,公司每个应用模块都有好几台机器,看日志问题也就衍生而来,我们最笨的方法就是每个服务器后台都打开进行查看,效率低下,此时,我们就可以使用es、kibana、logstash;简称ELK进行查看分布式日志系统,但是本文不会进行安装logstash进行演示,因为只做日志查询的需求,我们使用ELK的变种EFK即可,filebeat轻量级做日志收集即可,最主要的就是看我们如何进行配置,然后使用kibana进行查询日志。    安装完logstash后,解压在config中新建my-logstash.conf,该配置中注意三大块,input、filter、output;其中input是作为吸取日志的以.log为后缀的日志文件,filter是过滤配置,不用管,output则是导入到哪个elasticsearch中;配置如下:   1 input {  2         file {  3                 type => "log"  4                 path => ["/apps/svr/server/*/log.file"]  5                 start_position => "end"  6                 ignore_older => 0  7                 codec=> multiline {  8                         pattern => "^\d{4}-\d{1,2}-\d{1,2}\s\d{1,2}:\d{1,2}:\d{1,2}"  9                         negate => true 10             auto_flush_interval => 5 11                         what => "previous" 12                 } 13         } 14         beats { 15             port => 5044 16         } 17 } 18 output { 19         if [type] == "log" { 20                 elasticsearch { 21                         hosts => ["http://127.0.0.1:19200"] 22                         index => "logstash-%{+YYYY.MM}" 23                         #user => es 24                         #password => es2018 25                 } 26         } 27 }   如果自己动手配置的话,最好自己手动输入,不要复制粘贴,很有可能会有特殊字符出现导致启动失败;启动命令:./bin/logstah -f my-logstash.conf     最终我们就可以这样使用kibana进行查询日志的操作了。简单的基本应用就到此为止了,工作中基础的应用是没有问题了; ————————————————              原文链接:https://blog.csdn.net/qq_40280043/article/details/140465060 
  • [技术干货] SpringBoot之ElasticsearchRestTemplate常用示例
    目录 1.引入pom依赖 2.application 配置 3.JavaBean配置以及ES相关注解 3.1 Student实体类 3.2 Teacher实体类 3.3 Headmaster 实体类 4. 启动类配置 5.elasticsearchRestTemplate 新增 ==5.1 createIndex && putMapping 创建索引及映射== 5.1.1 Controller层 5.1.2 service层 5.1.3 serviceimpl层 createIndex && putMapping 创建索引及映射测试结果: ==5.2 save 添加文档== 5.2.1 Controller层 5.2.2 service层 5.2.3 serviceimpl层 save 添加文档测试结果: 6.elasticsearchRestTemplate 删除 ==6.1 deleteIndex 删除索引== 6.1.1 Controller层 6.1.2 service层 6.1.3 serviceimpl层 deleteIndex 删除索引测试结果: ==6.2 delete 删除文档(通过主键删除)== 6.2.1 Controller层 6.2.2 service层 6.2.3 serviceimpl层 delete(通过主键删除) 删除文档测试结果: ==6.3 delete 删除文档(删除通过query所搜索到的所有数据)== 6.3.1 Controller层 6.3.2 service层 6.3.3 serviceimpl层 delete(删除通过query所搜索到的所有数据) 删除文档测试结果: 7.elasticsearchRestTemplate 修改 ==7.1 update 修改文档(根据主键修改)== 7.1.1 Controller层 7.1.2 service层 7.1.3 serviceimpl层 update 修改文档(根据主键修改)测试结果: 8.elasticsearchRestTemplate 查询 ==8.1 get 查询文档(根据主键查询)== 8.1.1 Controller层 8.1.2 service层 8.1.3 serviceimpl层 get 查询文档(根据主键查询)测试结果: ==8.2 exists 查询文档(判断主键id是否存在)== 8.2.1 Controller层 8.2.2 service层 8.2.3 serviceimpl层 exists 查询文档(判断主键id是否存在)测试结果: ==8.3 queryForPage 查询文档(分页查询)== 8.3.1 Controller层 8.3.2 service层 8.3.3 serviceimpl层 queryForPage 查询文档(分页查询)测试结果: ==8.4 AnalyzeRequest (根据入参内容返回分词后结果)== 8.4.1 Controller层 8.4.2 service层 8.4.3 serviceimpl层 AnalyzeRequest (根据入参内容返回分词后结果)测试结果: ==8.5 termQuery 查询文档(入参不分词,精确匹配)== 8.5.1 Controller层 8.5.2 service层 8.5.3 serviceimpl层 termQuery 查询文档(入参不分词,精确匹配)测试结果: ==8.6 match 查询文档(会对查询条件进行分词)== 8.6.1 Controller层 8.6.2 service层 8.6.3 serviceimpl层 match 查询文档(会对查询条件进行分词)测试结果: ==8.7 matchAllQuery 查询文档(查询此索引下所有文档)== 8.7.1 Controller层 8.7.2 service层 8.7.3 serviceimpl层 matchAllQuery 查询文档(查询此索引下所有文档)测试结果: ==8.8 wildcardQuery 查询文档(模糊查询)== 8.8.1 Controller层 8.8.2 service层 8.8.3 serviceimpl层 wildcardQuery 查询文档(模糊查询)测试结果: ==8.9 prefixQuery 查询文档(前缀查询)== 8.9.1 Controller层 8.9.2 service层 8.9.3 serviceimpl层 prefixQuery 查询文档(前缀查询)测试结果: ==8.10 regexpQuery 查询文档(正则表达式查询)== 8.10.1 Controller层 8.10.2 service层 8.10.3 serviceimpl层 regexpQuery 查询文档(正则表达式查询) 测试结果: ==8.11 rangeQuery 查询文档(范围查询)== 8.11.1 Controller层 8.11.2 service层 8.11.3 serviceimpl层 rangeQuery 查询文档(范围查询) 测试结果: ==8.12 Sort 查询文档 (排序)== 8.12.1 Controller层 8.12.2 service层 8.12.3 serviceimpl层 Sort 查询文档 (排序) 测试结果: ==8.13 queryStringQuery 查询文档 (多条件查询 一值多域)== 8.13.1 Controller层 8.13.2 service层 8.13.3 serviceimpl层 queryStringQuery 查询文档 (多条件查询 一值多域) 测试结果: ==8.14 boolQuery 查询文档 (对多个查询条件连接。连接方式:must)== 8.14.1 Controller层 8.14.2 service层 8.14.3 serviceimpl层 boolQuery 查询文档 (对多个查询条件连接。连接方式:must) 测试结果: ==8.15 boolQuery 查询文档 (对多个查询条件连接。连接方式:filter)== 8.15.1 Controller层 8.15.2 service层 8.15.3 serviceimpl层 boolQuery 查询文档 (对多个查询条件连接。连接方式:filter) 测试结果: ==8.16 boolQuery 查询文档 (对多个查询条件连接。连接方式:must_not)== 8.16.1 Controller层 8.16.2 service层 8.16.3 serviceimpl层 boolQuery 查询文档 (对多个查询条件连接。连接方式:must_not) 测试结果: ==8.17 boolQuery 查询文档 (对多个查询条件连接。连接方式:should)== 8.17.1 Controller层 8.17.2 service层 8.17.3 serviceimpl层 boolQuery 查询文档 (对多个查询条件连接。连接方式:should) 测试结果: ==8.18 HighlightBuilder 查询文档 (高亮)== 8.18.1 Controller层 8.18.2 service层 8.18.3 serviceimpl层 HighlightBuilder 查询文档 (高亮) 测试结果: ==8.19 AggregationBuilders 查询文档 (聚合查询)== 8.19.1 Controller层 8.19.2 service层 8.19.3 serviceimpl层 AggregationBuilders 查询文档 (聚合查询) 测试结果: ==8.20 SuggestBuilders查询文档 (completionSuggestion---搜索建议补全)== 8.20.1 Controller层 8.20.2 service层 8.20.3 serviceimpl层 SuggestBuilders查询文档 (completionSuggestion---搜索建议补全) 测试结果: ==8.21 SuggestBuilders查询文档 (termSuggestion---纠错补全)== 8.21.1 Controller层 8.21.2 service层 8.21.3 serviceimpl层 SuggestBuilders查询文档 (termSuggestion---纠错补全) 测试结果: ==~~8.22 SuggestBuilders查询文档 (phraseSuggestion---纠错补全)~~== ~~8.22.1 Controller层~~ ~~8.22.2 service层~~ ~~8.22.3 serviceimpl层~~ ~~SuggestBuilders查询文档 (termSuggestion---纠错补全) 测试结果:~~ 链接:[windows版本 Elasticsearch服务端-7.4.0以及kibana-7.4.0和ik分词器下载地址](https://mp.csdn.net/mp_download/manage/download/UpDetailed) 链接:[SpringBoot之ElasticsearchRestTemplate常用示例 源代码下载地址](https://download.csdn.net/download/JAVA_MHH/72555390) 1.引入pom依赖 主要引用 elasticsearch使用相关依赖 注意ES 服务端版本(7.4.0)要与客户端版本相容 注意 hutool的工具类 实体转map 版本      <!--引入springboot父工程依赖-->     <!--引入依赖作用:     可以省去version标签来获得一些合理的默认配置     -->     <parent>         <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-starter-parent</artifactId>         <version>2.3.2.RELEASE</version>     </parent>      <dependencies>          <!--elasticsearch使用相关依赖-->         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-data-elasticsearch</artifactId>         </dependency>          <!--引入提供Web开发场景依赖-->         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-web</artifactId>         </dependency>          <!--这里用到了类型转Map,这里用到hutool的工具类(版本高一些,低版本的 如果传的就是Map类型,在map转map时会报错)-->         <dependency>             <groupId>cn.hutool</groupId>             <artifactId>hutool-core</artifactId>             <version>5.7.13</version>         </dependency>          <!--用@Data 减少JavaBean get...set...方法-->         <dependency>             <groupId>org.projectlombok</groupId>             <artifactId>lombok</artifactId>         </dependency>          <!--两个用来做测试的jar包-->         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-test</artifactId>         </dependency>         <dependency>             <groupId>junit</groupId>             <artifactId>junit</artifactId>         </dependency>      </dependencies>  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49  2.application 配置  spring:   elasticsearch:     rest:       uris: 127.0.0.1:9200               #---ES的连接地址,多个地址用逗号分隔       username:                          #---用户名       password:                          #---密码       connection-timeout: 1000           #---连接超时时间(默认1s)       read-timeout: 1000                 #---读取超时时间(默认30s) 1 2 3 4 5 6 7 8 9  3.JavaBean配置以及ES相关注解 注解:@Document用来声明Java对象与ElasticSearch索引的关系              indexName 索引名称(是字母的话必须是小写字母)              type 索引类型              shards 主分区数量,默认5              replicas 副本分区数量,默认1              createIndex 索引不存在时,是否自动创建索引,默认true                         不建议自动创建索引(自动创建的索引 是按着默认类型和默认分词器) 注解:@Id 表示索引的主键 注解:@Field 用来描述字段的ES数据类型,是否分词等配置,等于Mapping描述              index 设置字段是否索引,默认是true,如果是false则该字段不能被查询              store 默认为no,被store标记的fields被存储在和index不同的fragment中,以便于快速检索。虽然store占用磁盘空间,但是减少了计算。              type 数据类型(text、keyword、date、object、geo等)              analyzer 对字段使用分词器,注意一般如果要使用分词器,字段的type一般是text。              format 定义日期时间格式,详细见 官方文档: https://www.elastic.co/guide/reference/mapping/date-format/. 注解:@CompletionField 定义关键词索引 要完成补全搜索              analyzer 对字段使用分词器,注意一般如果要使用分词器,字段的type一般是text。              searchAnalyzer 显示指定搜索时分词器,默认是和索引是同一个,保证分词的一致性。              maxInputLength:设置单个输入的长度,默认为50 UTF-16 代码点 常用数据类型:  简单类型:  字符串类型: - string(不再支持)可知string类型的field已经被移除了, 我们需要用text或keyword类型来代替string.                     text:会分词,不支持聚合                     keyword:不会分词,将全部内容作为一个词条,支持聚合  数字类型: 尽可能选择范围小的数据类型, 字段的长度越短, 索引和搜索的效率越高;(优先考虑使用带缩放因子的浮点类型)                 byte :            有符号的8位整数, 范围: [-128 ~ 127]                 short :           有符号的16位整数, 范围: [-32768 ~ 32767]                 integer :        有符号的32位整数, 范围: [−231 ~ 231-1]                 long :            有符号的64位整数, 范围: [−263 ~ 263-1]                 float :             32位单精度浮点数                 double :         64位双精度浮点数                 half_float :     16位半精度IEEE 754浮点类型                 scaled_float : 缩放类型的的浮点数, 比如price字段只需精确到分, 57.34缩放因子为100, 存储结果为5734  日期类型:date JSON没有日期数据类型, 所以在ES中, 日期可以是:   —   包含格式化日期的字符串 “2018-10-01”, 或"2018/10/01 12:10:30"    (可以通过 format属性 定义日期时间格式)DateFormat 官方文法.   —   代表时间毫秒数的长整型数字.   —   代表时间秒数的整数.  布尔类型: boolean 可以接受表示真、假的字符串或数字:   —  真值: true, “true”, “on”, “yes”, “1”…   —  假值: false, “false”, “off”, “no”, “0”, “”(空字符串), 0.0, 0  复杂类型:  数组[]: 由数组中第一个非空值决定数组类型(type = FieldType.Keyword) List集合: 由数组中第一个非空值决定数组类型(type = FieldType.Keyword) 嵌套类型: list里泛型是object形式的或自定义对象(type = FieldType.Nested) 对象:{ }object for single JSON objects 单个JSON对象(type = FieldType.Object)  3.1 Student实体类  import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.DateFormat; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType;  import java.util.Date; import java.util.List;  /*  * 学生  * */ @Data @AllArgsConstructor @NoArgsConstructor //indexName名字如果是字母那么必须是小写字母 @Document(indexName = "student", type = "_doc", replicas = 1, shards = 1, createIndex = true) public class Student {      @Id     @Field(index = true, store = true, type = FieldType.Keyword)     private String sId;      @Field(index = true, store = true, type = FieldType.Keyword)     private String sName;      @Field(index = true, store = true, type = FieldType.Text, analyzer = "ik_smart")     //Text可以分词 ik_smart=粗粒度分词 ik_max_word 为细粒度分词     private String sAddress;      @Field(index = false, store = true, type = FieldType.Integer)     private Integer sAge;      @Field(index = false, store = true, type = FieldType.Date, format = DateFormat.basic_date_time)     private Date sCreateTime;      @Field(index = false, store = true, type = FieldType.Object)     private Headmaster sHeadmaster;//班主任      @Field(index = true, store = false, type = FieldType.Keyword)     private String[] sCourseList; //数组类型 由数组中第一个非空值决定(这里数组和集合一个意思了)      @Field(index = true, store = false, type = FieldType.Keyword)     private List<String> sColorList; //集合类型 由数组中第一个非空值决定      @Field(index = true, store = false, type = FieldType.Nested)//嵌套类型list里泛型是object形式的或自定义对象     private List<Teacher> sTeacherList; //教所有科目的老师  }  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55  3.2 Teacher实体类  import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.*; import org.springframework.data.elasticsearch.core.completion.Completion;  import java.util.Date;  /*  * 科目老师  * */ @Data @NoArgsConstructor @AllArgsConstructor @Document(indexName = "teacher", type = "_doc", replicas = 1, shards = 1, createIndex = true) public class Teacher {      //主键id     @Id     @Field(index = true, store = true, type = FieldType.Keyword)//index:设置通过这个字段是否可以进行搜索     private String tId;      //姓名     @Field(index = true, store = true, type = FieldType.Keyword)     private String tName;      //英文姓名     @Field(index = true, store = true, type = FieldType.Keyword)     private String tEnglishName;      //班级     @Field(index = true, store = true, type = FieldType.Keyword)     private String tClassName;      //地址     @Field(index = true, store = true, type = FieldType.Text, analyzer = "ik_smart")     private String tAddress;      //至理名言     @Field(index = true, store = true, type = FieldType.Keyword)     private String tFamous;      //年龄     @Field(index = true, store = true, type = FieldType.Integer)     private Integer tAge;      //日期     @Field(index = true, store = true, type = FieldType.Date, format = DateFormat.basic_date_time)     private Date tCreateTime;      //定义关键词索引 要完成补全搜索,必须要用到特殊的数据类型completion,     //要汉字拼音都能补全,必须要使用自定义的ik+pinyin分词器     //                                                       maxInputLength:设置单个输入的长度,默认为50 UTF-16 代码点     @CompletionField(analyzer = "ik_smart", searchAnalyzer = "ik_smart", maxInputLength = 100)     private Completion completion;      public Teacher(String tId, String tName, String tEnglishName, String tClassName, String tAddress, Integer tAge, Date tCreateTime) {         this.tId = tId;         this.tName = tName;         this.tEnglishName = tEnglishName;         this.tClassName = tClassName;         this.tAddress = tAddress;         this.tAge = tAge;         this.tCreateTime = tCreateTime;     } } 3.3 Headmaster 实体类  import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; import java.util.Date; import java.util.List;  /*  * 班主任  * */ @Data @NoArgsConstructor @AllArgsConstructor @Document(indexName = "headmaster", type = "_doc", replicas = 1, shards = 1, createIndex = true) public class Headmaster {      @Field(index = true, store = false, type = FieldType.Keyword)     private String hId;      @Field(index = true, store = false, type = FieldType.Keyword)     private String hName;      @Field(index = true, store = false, type = FieldType.Keyword)     private String hAddress;      @Field(index = false, store = false, type = FieldType.Integer)     private Integer hSalary;      @Field(index = false, store = false, type = FieldType.Keyword)     private List<String> hClass;      @Field(index = true, store = true, type = FieldType.Date, format = DateFormat.basic_date_time)     private Date  hCreateTime; }  4. 启动类配置 import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;  /*  * 搜索引擎客户端启动类  * */ @SpringBootApplication public class ElasticsearchApplication {     public static void main(String[] args) {         SpringApplication.run(ElasticsearchApplication.class, args);     } }  5.elasticsearchRestTemplate 新增 5.1 createIndex && putMapping 创建索引及映射 createIndex :新增索引(数据库) putMapping :新增映射(表结构) 一定要通过这种方法创建,否则创建出来的映射都是默认的 createIndex && putMapping 创建索引及映射测试  入参classType : 对象是要被ES注解所引用的的(基于@Field和@Documen注解属性 创建索引和映射) 5.1.1 Controller层  import com.it.mhh.elasticsearch.service.ElasticsearchSaveService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 新增类  */ @RestController @RequestMapping("/elasticSearch/save") public class ElasticsearchSaveController {      @Autowired     private ElasticsearchSaveService elasticsearchSaveService;      /**      * @param classType : 要创建es的索引及映射(一定要通过这种方法创建,否则都是创建出来的映射都是默认的)基于传入对象中的@Document注解      * @return boolean      * @throws      * @Author Mhh      * @Date 2021/12/9 10:43      */     @PostMapping("createIndexAndMapping")     public boolean createIndexAndMapping(Class<?> classType) {         return elasticsearchSaveService.createIndexAndMapping(classType);     }  }  5.1.2 service层  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 新增  */ public interface ElasticsearchSaveService {      /**      * @param classType : 要创建es的索引及映射(一定要通过这种方法创建,否则都是创建出来的映射都是默认的)基于传入对象中的@Document注解      * @return boolean      * @throws      * @Author Mhh      * @Date 2021/12/9 10:43      */     boolean createIndexAndMapping(Class<?> classType); }  5.1.3 serviceimpl层  import com.it.mhh.elasticsearch.service.ElasticsearchSaveService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.stereotype.Service;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 新增  */ @Service public class ElasticsearchSaveServiceImp implements ElasticsearchSaveService {      @Autowired     private ElasticsearchRestTemplate elasticsearchRestTemplate;      /**      * @param classType : 要创建es的索引及映射(一定要通过这种方法创建,否则都是创建出来的映射都是默认的)基于传入对象中的@Document注解      * @return boolean      * @throws      * @Author Mhh      * @Date 2021/12/9 10:43      */     @Override     public boolean createIndexAndMapping(Class<?> classType) {         if (elasticsearchRestTemplate.indexExists(classType))             return true;         boolean index = elasticsearchRestTemplate.createIndex(classType);         boolean mapping = elasticsearchRestTemplate.putMapping(classType);         return index && mapping;     } }  createIndex && putMapping 创建索引及映射测试结果: 根据入参对象 ES注解 创建索引及映射  import com.it.mhh.elasticsearch.controller.ElasticsearchSaveController; import com.it.mhh.elasticsearch.entity.Headmaster; import com.it.mhh.elasticsearch.entity.Student; import com.it.mhh.elasticsearch.entity.Teacher; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.elasticsearch.core.completion.Completion; import org.springframework.test.context.junit4.SpringRunner; import java.util.*;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 测试  */ @SpringBootTest @RunWith(SpringRunner.class) public class ElasticsearchSaveControllerTest {      @Autowired     private ElasticsearchSaveController elasticsearchSaveController;      /**      * @param classType : 要创建es的索引及映射(一定要通过这种方法创建,否则都是创建出来的映射都是默认的)基于传入对象中的@Document注解      * @return boolean      * @throws      * @Author Mhh      * @Date 2021/12/9 10:43      */     @Test     public void createIndexAndMapping() {         boolean indexAndMapping = elasticsearchSaveController.createIndexAndMapping(Student.class);         System.err.println(indexAndMapping);     }  }  5.2 save 添加文档 save(Iterable entities):可传集合 批量添加(同种类对象集合) save(T entity) :单个对象 save(T… entities) : 可变参 save 添加文档测试  入参entities :有重载 可传集合 单个 或者 可变参 5.2.1 Controller层  import com.it.mhh.elasticsearch.service.ElasticsearchSaveService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 新增类  */ @RestController @RequestMapping("/elasticSearch/save") public class ElasticsearchSaveController {      @Autowired     private ElasticsearchSaveService elasticsearchSaveService;      /**      * @param entities : 入参对象(必须是被@Document注解注释的)【入参 可传单个 T,集合Iterable<T>,可变参 T... 】      * @return T  反参对象      * @explain : 添加文档      * @Author Mhh      * @Date 2021/12/27 10:55      */     @PostMapping("saveList")     public  <T> Iterable<T> save(@RequestBody Iterable<T> entities) {         return elasticsearchSaveService.save(entities);     }      /**      * @param t : 入参对象(必须是被@Document注解注释的)【入参 可传单个 T,集合Iterable<T>,可变参 T... 】      * @return T  反参对象      * @explain : 添加文档      * @Author Mhh      * @Date 2021/12/27 10:55      */     @PostMapping("save")     public <T> T save(@RequestBody T t) {         return elasticsearchSaveService.save(t);     }  }  5.2.2 service层  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 新增  */ public interface ElasticsearchSaveService {         /**      * @param entities : 入参对象(必须是被@Document注解注释的)【入参 可传单个 T,集合Iterable<T>,可变参 T... 】      * @return T  反参对象      * @explain : 添加文档      * @Author Mhh      * @Date 2021/12/27 10:55      */     public <T> Iterable<T> save(Iterable<T> entities);      /**      * @param t : 入参对象(必须是被@Document注解注释的)【入参 可传单个 T,集合Iterable<T>,可变参 T... 】      * @return T  反参对象      * @explain : 添加文档      * @Author Mhh      * @Date 2021/12/27 10:55      */      public <T> T save(T t); }  5.2.3 serviceimpl层  import com.it.mhh.elasticsearch.service.ElasticsearchSaveService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.stereotype.Service;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 新增  */ @Service public class ElasticsearchSaveServiceImp implements ElasticsearchSaveService {      @Autowired     private ElasticsearchRestTemplate elasticsearchRestTemplate;       /**      * @param entities : 入参对象(必须是被@Document注解注释的)【入参 可传单个 T,集合Iterable<T>,可变参 T... 】      * @return T  反参对象      * @explain : 添加文档      * @Author Mhh      * @Date 2021/12/27 10:55      */     @Override     public <T> Iterable<T> save(Iterable<T> entities) {         return elasticsearchRestTemplate.save(entities);      }      /**      * @param t : 入参对象(必须是被@Document注解注释的)【入参 可传单个 T,集合Iterable<T>,可变参 T... 】      * @return T  反参对象      * @explain : 添加文档      * @Author Mhh      * @Date 2021/12/27 10:55      */      @Override     public <T> T save(T t) {         return elasticsearchRestTemplate.save(t);     }  }  save 添加文档测试结果: 根据被ES注解引用的入参对象存入相应索引中  import com.it.mhh.elasticsearch.controller.ElasticsearchSaveController; import com.it.mhh.elasticsearch.entity.Headmaster; import com.it.mhh.elasticsearch.entity.Student; import com.it.mhh.elasticsearch.entity.Teacher; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.elasticsearch.core.completion.Completion; import org.springframework.test.context.junit4.SpringRunner; import java.util.*;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 测试  */ @SpringBootTest @RunWith(SpringRunner.class) public class ElasticsearchSaveControllerTest {      @Autowired     private ElasticsearchSaveController elasticsearchSaveController;      /**      * @param entities : 入参对象(必须是被@Document注解注释的)【入参 可传单个 T,集合Iterable<T>,可变参 T... 】      * @return T  反参对象      * @explain : 添加文档      * @Author Mhh      * @Date 2021/12/27 10:55      */     @Test     public void savesStudentES() {          Headmaster headmaster = new Headmaster("1", "小白主任", "山东", 7500, Arrays.asList("一班", "二班"), new Date());//班主任         List<String> colorList = new ArrayList<>();//颜色         colorList.add("red");         colorList.add("white");         colorList.add("black");          List<Teacher> teacherList = new ArrayList<>();//所有科目老师         teacherList.add(new Teacher("2", "小黑", "black", "一班", "山东省济南市历下区", 13, new Date()));         teacherList.add(new Teacher("2", "小蓝", "blue", "二班", "山东省菏泽市单县", 14, new Date()));          Student student = new Student("1", "mhh", "济南", 12, new Date(), headmaster, new String[]{"语文", "数学", "英语"}, colorList, teacherList);         Student save = elasticsearchSaveController.save(student);         System.out.println(save);     }      /**      * @param entities : 入参对象(必须是被@Document注解注释的)【入参 可传单个 T,集合Iterable<T>,可变参 T... 】      * @return T  反参对象      * @explain : 添加文档      * @Author Mhh      * @Date 2021/12/27 10:55      */     @Test     public void savesTeacherES() {          Teacher teacher = new Teacher("1", "小黑老师", "black", "一班", "河北省保定市莲池区", 14, new Date());         Teacher save = elasticsearchSaveController.save(teacher);         System.out.println(save);     }      /**      * @param entities : 入参对象(必须是被@Document注解注释的)【入参 可传单个 T,集合Iterable<T>,可变参 T... 】      * @return T  反参对象      * @explain : 添加文档      * @Author Mhh      * @Date 2021/12/27 10:55      */     @Test     public void savesTeacherListES() {          List<Teacher> teacherList = new ArrayList<>();         teacherList.add(new Teacher("1", "小黑老师", "black", "一班", "山东省泰安市岱岳区","且随疾风前行,身后亦须留心", 15, new Date(), new Completion(new String[]{"山东省泰安市岱岳区"})));         teacherList.add(new Teacher("2", "小白老师", "white", "二班", "山东省济南市历下区", "此剑之势,愈斩愈烈",17, new Date(), new Completion(new String[]{"山东省济南市历下区"})));         teacherList.add(new Teacher("3", "小黄老师", "yellow", "二班", "河北省保定市莲池区", "荣耀存于心,而非流于形",18, new Date(), new Completion(new String[]{"河北省保定市莲池区"})));         teacherList.add(new Teacher("5", "小蓝教授", "blue", "二班", "山东省济南市高新区", "吾之初心,永世不忘",21, new Date(), new Completion(new String[]{"山东省济南市高新区"})));         teacherList.add(new Teacher("4", "小绿教授", "green", "一班", "山西省太原市杏花岭区", "有些失误无法犯两次",24, new Date(), new Completion(new String[]{"山西省太原市杏花岭区"})));         Iterable<Teacher> save = elasticsearchSaveController.save(teacherList);         System.out.println(save);     }  }  6.elasticsearchRestTemplate 删除 6.1 deleteIndex 删除索引 deleteIndex :删除索引(数据库) deleteIndex 删除索引测试  入参classType : 删除的哪个索引(从传入对象中的@Document注解中indexName属性获取) 6.1.1 Controller层  import com.it.mhh.elasticsearch.service.ElasticsearchDeleteService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 删除类  */ @RestController @RequestMapping("/elasticSearch/delete") public class ElasticsearchDeleteController {      @Autowired     private ElasticsearchDeleteService elasticsearchDeleteService;      /**      * @param clazz : 删除的哪个索引(从传入对象中的@Document注解中indexName属性获取)      * @return java.lang.Boolean      * @explain : 删除索引      * @Author Mhh      * @Date 2021/12/27 14:27      */     @PostMapping("deleteIndex")     public Boolean deleteIndex(Class<?> clazz) {         return elasticsearchDeleteService.deleteIndex(clazz);     } }  6.1.2 service层  import org.springframework.data.elasticsearch.core.query.Query; /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 查询  */ public interface ElasticsearchDeleteService {      /**      * @param clazz : 删除的哪个索引(从传入对象中的@Document注解中indexName属性获取)      * @return java.lang.Boolean      * @explain : 删除索引      * @Author Mhh      * @Date 2021/12/27 14:27      */     public Boolean deleteIndex(Class<?> clazz); }  6.1.3 serviceimpl层  import com.it.mhh.elasticsearch.service.ElasticsearchDeleteService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.stereotype.Service;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述:  */ @Service public class ElasticsearchDeleteServiceImp implements ElasticsearchDeleteService {      @Autowired     private ElasticsearchRestTemplate elasticsearchRestTemplate;            /**      * @param clazz : 删除的哪个索引(从传入对象中的@Document注解中indexName属性获取)      * @return java.lang.Boolean      * @explain : 删除索引      * @Author Mhh      * @Date 2021/12/27 14:27      */      public Boolean deleteIndex(Class<?> clazz) {         return elasticsearchRestTemplate.deleteIndex(clazz);     }  }  deleteIndex 删除索引测试结果: 根据入参对象 ES@Document注解中indexName属性值删除索引 如果删除的 索引名不存在 返回false  import com.it.mhh.elasticsearch.controller.ElasticsearchDeleteController; import com.it.mhh.elasticsearch.entity.Teacher; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.TermsQueryBuilder; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; import org.springframework.test.context.junit4.SpringRunner;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述:  */ @SpringBootTest @RunWith(SpringRunner.class) public class ElasticsearchDeleteControllerTest {      @Autowired     private ElasticsearchDeleteController elasticsearchDeleteController;      /**      * @param clazz : 删除的哪个索引(从传入对象中的@Document注解中indexName属性获取)      * @return java.lang.Boolean      * @explain : 删除索引      * @Author Mhh      * @Date 2021/12/27 14:27      */      @Test     public void deleteIndex() {         Boolean aBoolean = elasticsearchDeleteController.deleteIndex(Teacher.class);         System.err.println(aBoolean);     } }  6.2 delete 删除文档(通过主键删除) delete(Object entity):通过入参对象主键删除 delete(String id, Class<?> entityType) id表示要删除的主键,entityType表示 被@Document注解且有indexName属性值(indexName属性值为索引名) delete(通过主键删除) 删除文档测试  两种入参形式 都是通过主键id删除,即使入参对象其它属性设置了值也是通过主键id删除 6.2.1 Controller层  import com.it.mhh.elasticsearch.service.ElasticsearchDeleteService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 删除类  */ @RestController @RequestMapping("/elasticSearch/delete") public class ElasticsearchDeleteController {      @Autowired     private ElasticsearchDeleteService elasticsearchDeleteService;      /**      * @param id         : 要删除的主键id      * @param entityType : 删除的哪个索引的数据(从传入对象中的@Document注解中indexName属性获取)      * @return java.lang.Boolean      * @throws      * @Author Mhh      * @Date 2021/12/9 10:40      */     @PostMapping("deleteById")     public Boolean delete(@RequestParam("id") String id, Class<?> entityType) {         return elasticsearchDeleteService.delete(id, entityType);     }      /**      * @param entity : 这个实体对象必须是被@Document注解且有indexName属性值) 以及主键必须有值,其它参数有无都没关系(和用主键id删除没区别)      * @return java.lang.Boolean      * @throws      * @Author Mhh      * @Date 2021/12/9 10:40      */     @PostMapping("delete")     public Boolean delete(Object entity) {         return elasticsearchDeleteService.delete(entity);     }  }   6.2.2 service层  import org.springframework.data.elasticsearch.core.query.Query; /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 查询  */ public interface ElasticsearchDeleteService {      /**      * @param id         : 要删除的主键id      * @param entityType : 删除的哪个索引的数据(从传入对象中的@Document注解中indexName属性获取)      * @return java.lang.Boolean      * @throws      * @Author Mhh      * @Date 2021/12/9 10:40      */     Boolean delete(String id, Class<?> entityType);      /**      * @param entity : 这个实体对象必须是被@Document注解且有indexName属性值) 以及主键必须有值,其它参数有无都没关系(和用主键id删除没区别)      * @return java.lang.Boolean      * @throws      * @Author Mhh      * @Date 2021/12/9 10:40      */      public Boolean delete(Object entity); }  6.2.3 serviceimpl层  import com.it.mhh.elasticsearch.service.ElasticsearchDeleteService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.stereotype.Service;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述:  */ @Service public class ElasticsearchDeleteServiceImp implements ElasticsearchDeleteService {      @Autowired     private ElasticsearchRestTemplate elasticsearchRestTemplate;      /**      * @param id         : 要删除的主键id      * @param entityType : 删除的哪个索引的数据(从传入对象中的@Document注解中indexName属性获取)      * @return java.lang.Boolean      * @throws      * @Author Mhh      * @Date 2021/12/9 10:40      */     @Override     public Boolean delete(String id, Class<?> entityType) {         String delete = elasticsearchRestTemplate.delete(id, entityType);         return true;     }      /**      * @param entity : 这个实体对象必须是被@Document注解且有indexName属性值) 以及主键必须有值,其它参数有无都没关系(和用主键id删除没区别)      * @return java.lang.Boolean      * @throws      * @Author Mhh      * @Date 2021/12/9 10:40      */     @Override     public Boolean delete(Object entity) {         String delete = elasticsearchRestTemplate.delete(entity);         return true;     } } import com.it.mhh.elasticsearch.controller.ElasticsearchSelectController; import com.it.mhh.elasticsearch.entity.Student; import com.it.mhh.elasticsearch.entity.Teacher; import org.elasticsearch.client.indices.AnalyzeResponse; import org.elasticsearch.index.query.Operator; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.Sort; import org.springframework.test.context.junit4.SpringRunner;  import java.io.IOException; import java.util.*;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述:  */ @SpringBootTest @RunWith(SpringRunner.class) public class ElasticsearchSelectControllerTest {      @Autowired     private ElasticsearchSelectController elasticsearchSelectController;//es的查询      /**      * @param name      : es里索引的域(字段名)      * @param classType : 返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引      * @param values    : 一域多值, 查询的值      * @return java.util.List<T>      * @explain : 聚合对数据进行分组的求和,求数,最大值,最小值,或者其它的自定义的统计功能,      * //            es对聚合有着不错的支持,需要注意的是,在对某字段进行聚合之后,需要开启这个字段的fielddata      * //            不要对text类型的数据进行分组,会失败      * @Author Mhh      * @Date 2021/12/22 14:29      */     @Test     public void aggregationBuilder() {         List<Teacher> teacherList = elasticsearchSelectController.aggregationBuilder(                 "tAddress",                 Teacher.class,                 "山东省");         teacherList.forEach(System.err::println);     }  }  8.20 SuggestBuilders查询文档 (completionSuggestion—搜索建议补全) SuggestBuilders.completionSuggestion(String fieldname).prefix(String prefix).skipDuplicates(boolean skipDuplicates)…                             fieldname : 使用fieldName入参进行标题联想(这里就是索引被@CompletionField注解标注的字段)                             prefix :   设置要为其提供补全的前缀。前缀由建议分析器进行分析。                             skipDuplicates :   是否应过滤重复项。默认为 { false} SuggestBuilders查询文档 (completionSuggestion—搜索建议补全) 测试  完成补全单词,输出如前半部分,补全整个单词 8.20.1 Controller层 import com.it.mhh.elasticsearch.service.ElasticsearchSelectService; import org.elasticsearch.client.indices.AnalyzeResponse; import org.elasticsearch.index.query.Operator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Sort; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;  import java.io.IOException; import java.util.List; import java.util.Map;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 查询类  */ @RestController @RequestMapping("/elasticSearch/select") public class ElasticsearchSelectController {      @Autowired     private ElasticsearchSelectService elasticsearchSelectService;      /**      * 速度快  输入的内容立即返回  对字段类型要求多节省存储空间  时间复杂度O(1),做建议不做纠错      * 感觉和prefixQuery 前缀查询 差不多.....      * <p>      * 搜索补全必须定义 这个属性(      * //                  @CompletionField(analyzer = "ik_smart", searchAnalyzer = "ik_smart", maxInputLength = 100)      * //                  private Completion completion;)      * //      给Completion属性赋值: new Completion(new String[]{"山东省泰安市岱岳区"}))      * //                  :里面的值就是被自动补全的值      *      * @param fieldName : 要用哪个字段进行标题联想(必须是这个@CompletionField注解所标注的类型为Completion的字段名)      * @param text      : 被补全的值(比如传的是山东 可能就能给补全为 山东省)      * @param classType : 返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引      * @return java.util.List<java.lang.String>      * @explain : 搜索补全功能 比如在输入框输入(天上)下面就自动补全 (天上人间)(天上边的彩霞)(....)      * @Author Mhh      * @Date 2021/12/22 16:51      */      @PostMapping("completionSuggestion")     public List<String> completionSuggestion(String fieldName, String text, Class<?> classType) {         return elasticsearchSelectService.completionSuggestion(fieldName, text, classType);     }  }  8.20.2 service层  import org.elasticsearch.client.indices.AnalyzeResponse; import org.elasticsearch.index.query.Operator; import org.springframework.data.domain.Sort;  import java.io.IOException; import java.util.List; import java.util.Map;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述: 查询  */ public interface ElasticsearchSelectService {      /**      * 速度要快  输入的内容立即返回  对字段类型要求多节省存储空间  时间复杂度O(1),做建议不做纠错      * 感觉和prefixQuery 前缀查询 差不多.....      * <p>      * 搜索补全必须定义 这个属性(      * //                  @CompletionField(analyzer = "ik_smart", searchAnalyzer = "ik_smart", maxInputLength = 100)      * //                  private Completion completion;)      * //      给Completion属性赋值: new Completion(new String[]{"山东省泰安市岱岳区"}))      * //                  :里面的值就是被自动补全的值      *      * @param fieldName : 要用哪个字段进行标题联想(必须是这个@CompletionField注解所标注的类型为Completion的字段名)      * @param text      : 被补全的值(比如传的是山东 可能就能给补全为 山东省)      * @param classType : 返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引      * @return java.util.List<java.lang.String>      * @explain : 搜索补全功能 比如在输入框输入(天上)下面就自动补全 (天上人间)(天上边的彩霞)(....)      * @Author Mhh      * @Date 2021/12/22 16:51      */     public List<String> completionSuggestion(String fieldName, String text, Class<?> classType);   }  8.20.3 serviceimpl层  import com.it.mhh.elasticsearch.service.ElasticsearchSelectService; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.client.indices.AnalyzeRequest; import org.elasticsearch.client.indices.AnalyzeResponse; import org.elasticsearch.index.query.*; import org.elasticsearch.search.aggregations.Aggregation; import org.elasticsearch.search.aggregations.AggregationBuilders; import org.elasticsearch.search.aggregations.bucket.terms.Terms; import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder; import org.elasticsearch.search.aggregations.metrics.*; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import org.elasticsearch.search.suggest.*; import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder; import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder; import org.elasticsearch.search.suggest.term.TermSuggestionBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.data.elasticsearch.core.SearchHit; import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.stereotype.Service;  import java.io.IOException; import java.lang.reflect.Field; import java.util.*; import java.util.stream.Collectors;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述:  */ @Service public class ElasticsearchSelectServiceImp implements ElasticsearchSelectService {      @Autowired     private ElasticsearchRestTemplate elasticsearchRestTemplate;       /**      * 速度要快  输入的内容立即返回  对字段类型要求多节省存储空间  时间复杂度O(1),做建议不做纠错      * 感觉和prefixQuery 前缀查询 差不多.....      * <p>      * 搜索补全必须定义 这个属性(      * //                  @CompletionField(analyzer = "ik_smart", searchAnalyzer = "ik_smart", maxInputLength = 100)      * //                  private Completion completion;)      * //      给Completion属性赋值: new Completion(new String[]{"山东省泰安市岱岳区"}))      * //                  :里面的值就是被自动补全的值      *      * @param fieldName : 要用哪个字段进行标题联想(必须是这个@CompletionField注解所标注的类型为Completion的字段名)      * @param text      : 被补全的值(比如传的是山东 可能就能给补全为 山东省)      * @param classType : 返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引      * @return java.util.List<java.lang.String>      * @explain : 搜索补全功能 比如在输入框输入(天上)下面就自动补全 (天上人间)(天上边的彩霞)(....)      * @Author Mhh      * @Date 2021/12/22 16:51      */     public List<String> completionSuggestion(String fieldName, String text, Class<?> classType) {         //定义反参容器         List<String> stringList = new ArrayList<>();         //构建搜索建议补全对象         CompletionSuggestionBuilder completionSuggestionBuilder = SuggestBuilders.                 completionSuggestion(fieldName). // 使用fieldName入参进行标题联想(这里就是索引被@CompletionField注解标注的字段)                 prefix(text).       // 关键字(参数传此)                 skipDuplicates(true)// 重复过滤                 //.size(100)       // 匹配数量                 ;         //创建搜索提示对象 进行封装搜索补全         SuggestBuilder suggestBuilder = new SuggestBuilder();         //  completionSuggestionBuilder:随便起的搜索补全的名字         suggestBuilder.addSuggestion("completionSuggestionBuilder", completionSuggestionBuilder);         //查询es并反参         SearchResponse searchResponse = elasticsearchRestTemplate.suggest(suggestBuilder, elasticsearchRestTemplate.getIndexCoordinatesFor(classType));         //获取反参中的搜索补全结果         Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> suggestionBuilder = searchResponse.getSuggest().getSuggestion("completionSuggestionBuilder");          // 处理返回         List<String> suggests = suggestionBuilder.getEntries().stream().map(x -> x.getOptions().stream().map(y -> y.getText().toString()).collect(Collectors.toList())).findFirst().get();         // 将搜索补全内容保存到容器返回         for (String suggest : suggests) {             stringList.add(suggest);             System.err.println("suggest = " + suggest);         }         return stringList;     }  }  SuggestBuilders查询文档 (completionSuggestion—搜索建议补全) 测试结果: 标题联想必须是被@CompletionField注解所标注的类型为Completion对象的属性。 定义对象关键词索引 要完成补全搜索,必须要用到特殊的数据类型completion。 建议补全的值都是在存储时保存在completion里的值。 import com.it.mhh.elasticsearch.controller.ElasticsearchSelectController; import com.it.mhh.elasticsearch.entity.Student; import com.it.mhh.elasticsearch.entity.Teacher; import org.elasticsearch.client.indices.AnalyzeResponse; import org.elasticsearch.index.query.Operator; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.Sort; import org.springframework.test.context.junit4.SpringRunner;  import java.io.IOException; import java.util.*;  /**  * @创建人: Mhh  * @创建时间: 2021/12/10  * @描述:  */ @SpringBootTest @RunWith(SpringRunner.class) public class ElasticsearchSelectControllerTest {      @Autowired     private ElasticsearchSelectController elasticsearchSelectController;//es的查询      /**      * @param name      : es里索引的域(字段名)      * @param classType : 返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引      * @param values    : 一域多值, 查询的值      * @return java.util.List<T>      * @explain : 聚合对数据进行分组的求和,求数,最大值,最小值,或者其它的自定义的统计功能,      * //            es对聚合有着不错的支持,需要注意的是,在对某字段进行聚合之后,需要开启这个字段的fielddata      * //            不要对text类型的数据进行分组,会失败      * @Author Mhh      * @Date 2021/12/22 14:29      */     @Test     public void aggregationBuilder() {         List<Teacher> teacherList = elasticsearchSelectController.aggregationBuilder(                 "tAddress",                 Teacher.class,                 "山东省");         teacherList.forEach(System.err::println);     }  }      /**      * https://www.cnblogs.com/Alexephor/p/11408446.html(Elasticsearch之建议器suggester)写的很详细      * 词组建议器(phraseSuggestion)      *      * @param fieldName : 从fieldName字段中获取候选建议的字段。这是一个必需的选项,需要全局设置或根据建议设置。Keyword字段      * @param text      : 建议文本,建议文本是必需的选项,可以通过全局(多个建议器中查询相同的内容)或者按照单个建议器的格式来。      * @param classType : 返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引      * @return java.util.List<java.lang.String>      * @explain : 词组建议器(phraseSuggestion)适合较长的字段,但是也不是万能的  做纠正(Keyword字段)      * @Author Mhh      * @Date 2021/12/23 15:40      */     @Test     public void phraseSuggestion() {         List<String> stringList = elasticsearchSelectController.phraseSuggestion(                 "tFamous",                 "吾之初心永世不完",                 Teacher.class);         stringList.forEach(System.err::println);     }  } ————————————————                  Q原文链接:https://blog.csdn.net/JAVA_MHH/article/details/122172506 
  • [技术干货] Springboot项目中使用Elasticsearch的RestClient
    一篇介绍了Elasticsearch的入门《5000字详说Elasticsearch入门(一)》,本篇介绍Springboot如何集成使用Elasticsearch。分为3步:配置properties文件、引入pom依赖、配置RestHighLevelClient类。  1、选择ES的Client API 我们知道Elasticsearch是一款Restful API风格的分布式搜索引擎。ES Client有两种连接方式:TransportClient 和 RestClient。TransportClient通过TCP方式访问ES,RestClient方式通过Http方式访问ES。ES在7.0中已经弃用TransportClient,在8.0中完全删除它,所以建议使用RestClient的方式。  RestClient方式有多种实现,比如:ES自带的RestHighLevelClient 、Springboot实现的ElasticsearchRestTemplate 。笔者建议使用RestHighLevelClient,原因有3个:  使用RestHighLevelClient比较灵活,可以直接使用ES的DSL语法,实现复杂查询,同时没有与其他部件绑定,所以版本可以自由选择。 由于ElasticsearchRestTemplate是spring-boot-starter-data-elasticsearch封装的工具类,虽然使用上稍微方便一些,但是失去了灵活性,出现问题时也不易排查。而且ElasticsearchRestTemplate本身与spring-boot-starter-data-elasticsearch紧密依赖。如果想升级ElasticsearchRestTemplate,那就必须连带升级项目的Springboot版本,这个风险就比较高了,一般项目的Springboot版本不会轻易升级。 还有些第三方的插件,将SQL转成ES的DSL,笔者也不建议使用。它们即不灵活,更新速度也慢。这里也再次吐槽下,ES的DSL设计确实有点反人类,不过也不用死记,可以通过归类记忆核心内容,其余使用细节直接查阅官方文档就好。 整理来说,用ES提供的RestHighLevelClient是最稳定最方便的。由于使用RestHighLevelClient,多少会涉及到ES的DSL语法,详见官网:Match phrase query | Elasticsearch Guide [7.17] | Elastic。  下文中的示例中使用RestHighLevelClient时,也会附带上对应的DSL语法。  2、配置properties文件 # ES集群机器 elasticsearch.hosts=10.20.1.29,10.20.0.91,10.20.0.93 # ES提供服务的端口号 elasticsearch.port=9200 3、引入pom依赖 <dependency>     <groupId>org.elasticsearch.client</groupId>     <artifactId>elasticsearch-rest-high-level-client</artifactId>     <version>7.10.2</version> </dependency> <dependency>     <groupId>org.elasticsearch.client</groupId>     <artifactId>elasticsearch-rest-client</artifactId>     <version>7.10.2</version> </dependency> <dependency>     <groupId>org.elasticsearch</groupId>     <artifactId>elasticsearch</artifactId>     <version>7.10.2</version> </dependency> 4、配置RestHighLevelClient @Data @Configuration @ConfigurationProperties(prefix = "elasticsearch") public class ElasticSearchConfig {       private String hosts;     private Integer port;       @Bean     public RestHighLevelClient restHighLevelClient() {         HttpHost[] httpHosts = Arrays.stream(hosts.split(","))                 .filter(e -> !StringUtils.isEmpty(e))                 .map(e -> new HttpHost(e, port, "http"))                 .toArray(HttpHost[]::new);           return new RestHighLevelClient(                 RestClient.builder(                         httpHosts                 )         );     } }  至此Springboot与Elasticsearch的集成已经结束,接下来就是使用了。  5、使用RestClient API 下文演示常规场景下的RestClient API的使用方式和对应的DSL语法,涉及到的相关完整代码见如下地址:  代码地址:GitHub - yclxiao/spring-elasticsearch: Springboot集成Elasticsearch 5.1、创建索引,指定Mapping 通过代码里调用RestClient的API也可以创建索引,但是笔者建议统一通过如下方式建立,字段和类型非常清晰可控,方便统一管理。比如创建一个goods索引:  PUT /goods {   "mappings": {     "properties": {       "brandName": {         "type": "keyword"       },       "categoryName": {         "type": "keyword"       },       "createTime": {         "type": "date",         "format": "yyyy-MM-dd HH:mm:ss"       },       "id": {         "type": "keyword"       },       "price": {         "type": "double"       },       "saleNum": {         "type": "integer"       },       "status": {         "type": "integer"       },       "stock": {         "type": "integer"       },       "title": {         "type": "text",         "analyzer": "ik_max_word",         "search_analyzer": "ik_smart"       }     }   } }    5.2、创建实例类 @Data @AllArgsConstructor @NoArgsConstructor public class Goods {       /**      * 商品编号      */     private Long id;       /**      * 商品标题      */     private String title;       /**      * 商品价格      */     private BigDecimal price;       /**      * 商品库存      */     private Integer stock;       /**      * 商品销售数量      */     private Integer saleNum;       /**      * 商品分类      */     private String categoryName;       /**      * 商品品牌      */     private String brandName;       /**      * 上下架状态      */     private Integer status;       /**      * 商品创建时间      */     @JSONField(format = "yyyy-MM-dd HH:mm:ss")     private Date createTime; }  5.3、使用API操作数据 下面介绍主流场景下API的使用,每个场景会配上对应的DSL语法。更多语法详见官方文档:Match phrase query | Elasticsearch Guide [7.17] | Elastic。  5.3.1、增加文档 // 创建索引请求对象 IndexRequest indexRequest = new IndexRequest("goods").id(goods.getId() + "").source(data, XContentType.JSON); // 执行增加文档 IndexResponse response = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT); DSL:  PUT goods/_doc/3 {   "id": 3,   "brandName": "华为",   "categoryName": "手机",   "createTime": "2023-10-23 19:12:56",   "price": 5999,   "saleNum": 1001,   "status": 1,   "stock": 900,   "title": "华为自研芯片Meta60测试机" } 5.3.2、更新文档 // 创建索引请求对象 UpdateRequest updateRequest = new UpdateRequest("goods", "4"); // 设置更新文档内容 updateRequest.doc(data, XContentType.JSON); // 执行更新文档 UpdateResponse response = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT); DSL:  POST goods/_update/3 {   "doc": {     "title":"华为自研芯片Meta60测试机111"   } } 5.3.3、删除文档 // 创建删除请求对象 DeleteRequest deleteRequest = new DeleteRequest("goods", "3"); // 执行删除文档 DeleteResponse response = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT); DSL:  DELETE goods/_doc/3 5.3.4、Match匹配查询 // 构建查询条件 (查询全部) MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery(); // 创建查询源构造器 SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(matchAllQueryBuilder); // 设置分页 searchSourceBuilder.from(0); // 从第几条开始,不包含它 searchSourceBuilder.size(3); // 要取多少条数据 // 设置排序 searchSourceBuilder.sort("price", SortOrder.ASC); // 设置源字段过虑,第一个参数结果集包括哪些字段,第二个参数表示结果集不包括哪些字段;查询的文档只包含哪些指定的字段 searchSourceBuilder.fetchSource(new String[]{"id", "title", "categoryName"}, new String[]{}); // 创建查询请求对象,将查询对象配置到其中 SearchRequest searchRequest = new SearchRequest("goods"); searchRequest.source(searchSourceBuilder); // 执行查询,然后处理响应结果 SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); // 根据状态和数据条数验证是否返回了数据 if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {     SearchHits hits = searchResponse.getHits();     for (SearchHit hit : hits) {         // 将 JSON 转换成对象         Goods goods = JSON.parseObject(hit.getSourceAsString(), Goods.class);         // 输出查询信息         log.info(goods.toString());     } }  DSL:  POST goods/_search {   "query": {     "match_all": {          }   } }   POST goods/_search {   "query": {     "match": {       "title": "移动多余"     }   } }  5.3.5、MatchPhrase查询 短语匹配会把查询文本看做短语,有顺序要求。  // 构建查询条件 SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); // title是 舒适轻巧 时,可以查到,改成 轻巧舒适 时,则查不到,因为短语匹配,会把查询文本看做短语,有顺序要求 searchSourceBuilder.query(QueryBuilders.matchPhraseQuery("title", "轻巧舒适")); // 创建查询请求对象,将查询对象配置到其中 SearchRequest searchRequest = new SearchRequest("goods"); searchRequest.source(searchSourceBuilder); // 执行查询,然后处理响应结果 SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); // 根据状态和数据条数验证是否返回了数据 if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {     SearchHits hits = searchResponse.getHits();     for (SearchHit hit : hits) {         // 将 JSON 转换成对象         Goods goods = JSON.parseObject(hit.getSourceAsString(), Goods.class);         // 输出查询信息         log.info(goods.toString());     } }  DSL:  POST goods/_search {   "query": {     "match_phrase": {       "title": "轻巧舒适"     }   } } 5.3.6、Term查询 Term查询时不对查询文本做分词。  // 构建查询条件(注意:termQuery 支持多种格式查询,如 boolean、int、double、string 等,这里使用的是 string 的查询) SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); // 表示检索title字段值为 李宁的跑鞋 的文档,如果是match则可以查到,term查询不到,因为term查询时不对查询文本做分词 searchSourceBuilder.query(QueryBuilders.termQuery("title", "李宁的跑鞋")); // 创建查询请求对象,将查询对象配置到其中 SearchRequest searchRequest = new SearchRequest("goods"); searchRequest.source(searchSourceBuilder); // 执行查询,然后处理响应结果 SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); // 根据状态和数据条数验证是否返回了数据 if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {     SearchHits hits = searchResponse.getHits();     for (SearchHit hit : hits) {         // 将 JSON 转换成对象         Goods goods = JSON.parseObject(hit.getSourceAsString(), Goods.class);         // 输出查询信息         log.info("=======" + goods.toString());     } }  DSL:  POST goods/_search {   "query": {     "term": {       "title": {         "value": "李宁的跑鞋"       }     }   } } 5.3.7、设置排序 // 设置排序 searchSourceBuilder.sort("price", SortOrder.ASC); DSL:  POST goods/_search {   "query": {     "match_all": {          }   },   "from": 6,   "size": 2,   "sort": [     {       "price": {         "order": "asc"       }     }   ] }  5.3.8、通配符查询 // 构建查询条件 SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); // 查询所有以 “鞋” 结尾的商品信息 searchSourceBuilder.query(QueryBuilders.wildcardQuery("title", "*鞋")); // 创建查询请求对象,将查询对象配置到其中 SearchRequest searchRequest = new SearchRequest("goods"); searchRequest.source(searchSourceBuilder); // 执行查询,然后处理响应结果 SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); // 根据状态和数据条数验证是否返回了数据 if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {     SearchHits hits = searchResponse.getHits();     for (SearchHit hit : hits) {         // 将 JSON 转换成对象         Goods goods = JSON.parseObject(hit.getSourceAsString(), Goods.class);         // 输出查询信息         log.info(goods.toString());     } }  DSL:  POST goods/_search {   "query": {     "wildcard": {       "title": {         "value": "*鞋"       }     }   } } 5.3.9、范围查询 // 构建查询条件 SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(QueryBuilders.rangeQuery("price").gte(1000)); DSL:  POST goods/_search {   "query": {     "bool": {       "must": [         {           "match": {             "title": "华为"           }         }       ],       "filter": [         {           "range": {             "price": {               "gte": 5000,               "lte": 10000             }           }         }       ]     }   } }  5.3.10、Scroll滚动查询 滚动查询可以用来解决深度分页查询问题,每次都要记住上一次的scrollId。  // 假设用户想获取第5页数据,其中每页1条 int pageNo = 3; int pageSize = 1;   // 定义请求对象 SearchRequest searchRequest = new SearchRequest("goods");   // 构建查询条件 SearchSourceBuilder builder = new SearchSourceBuilder(); searchRequest.source(builder.query(QueryBuilders.matchAllQuery()).sort("price", SortOrder.DESC).size(pageSize)); String scrollId = null; // 3、发送请求到ES SearchResponse scrollResponse = null; // 设置游标id存活时间 Scroll scroll = new Scroll(TimeValue.timeValueMinutes(2)); // 记录所有游标id List<String> scrollIds = new ArrayList<>(); for (int i = 0; i < pageNo; i++) {     try {         // 首次检索         if (i == 0) {             //记录游标id             searchRequest.scroll(scroll);             // 首次查询需要指定索引名称和查询条件             SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);             // 下一次搜索要用到该游标id             scrollId = response.getScrollId();         }         // 非首次检索         else {             // 不需要在使用其他条件,也不需要指定索引名称,只需要使用执行游标id存活时间和上次游标id即可,毕竟信息都在上次游标id里面呢             SearchScrollRequest searchScrollRequest = new SearchScrollRequest(scrollId);             searchScrollRequest.scroll(scroll);             scrollResponse = restHighLevelClient.scroll(searchScrollRequest, RequestOptions.DEFAULT);             // 下一次搜索要用到该游标id             scrollId = scrollResponse.getScrollId();         }         // 记录所有游标id         scrollIds.add(scrollId);     } catch (Exception e) {         e.printStackTrace();     } }   // 查询完毕,清除游标id ClearScrollRequest clearScrollRequest = new ClearScrollRequest(); clearScrollRequest.scrollIds(scrollIds); try {     restHighLevelClient.clearScroll(clearScrollRequest, RequestOptions.DEFAULT); } catch (IOException e) {     System.out.println("清除滚动查询游标id失败");     e.printStackTrace(); }   // 4、处理响应结果 System.out.println("滚动查询返回数据:"); assert scrollResponse != null; SearchHits hits = scrollResponse.getHits(); for (SearchHit hit : hits) {     // 将 JSON 转换成对象     Goods goods = JSON.parseObject(hit.getSourceAsString(), Goods.class);     // 输出查询信息     log.info(goods.toString()); }  DSL:  # 第一次使用 scroll API POST goods/_search?scroll=2m {   "query": {     "match_all": {}   },   "size": 2 } # 进行翻页 POST /_search/scroll                                                     {   "scroll" : "2m",      "scroll_id" : "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFkxBWkYwOGw2U1dPSF94aHZTelFkaWcAAAAAAAADHhZoU05ERFl3WFIycXM3M3JKMmRQVkJB"  } 5.3.11、组合查询 组合查询一般用到bool。  // 创建 Bool 查询构建器 BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); // 构建查询条件 boolQueryBuilder         .must(QueryBuilders.matchQuery("title", "李宁"))         .filter()         .add(QueryBuilders.rangeQuery("createTime").format("yyyyMMdd")                 .gte("20231023").lte("20231025")); // 构建查询源构建器 SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(boolQueryBuilder); searchSourceBuilder.size(100); // 创建查询请求对象,将查询对象配置到其中 SearchRequest searchRequest = new SearchRequest("goods"); searchRequest.source(searchSourceBuilder); // 执行查询,然后处理响应结果 SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); // 根据状态和数据条数验证是否返回了数据 if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {     SearchHits hits = searchResponse.getHits();     for (SearchHit hit : hits) {         // 将 JSON 转换成对象         Goods goods = JSON.parseObject(hit.getSourceAsString(), Goods.class);         // 输出查询信息         log.info(goods.toString());     } }  DSL:  POST goods/_search {   "query": {     "bool": {       "must": [         {           "match": {             "title": "李宁"           }         }       ],       "filter": [         {           "range": {             "price": {               "gte": 5000,               "lte": 10000             }           }         }       ]     }   } }  5.3.12、高亮查询 //查询条件(词条查询:对应ES query里的match) MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title", "华为手机");   //设置高亮三要素 // field: 你的高亮字段 // preTags :前缀 // postTags:后缀 HighlightBuilder highlightBuilder = new HighlightBuilder()         .field("title")         .preTags("<font color='blue'>")         .postTags("</font>");   // 构建查询源构建器 SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(matchQueryBuilder); searchSourceBuilder.highlighter(highlightBuilder); searchSourceBuilder.size(100); // 创建查询请求对象,将查询对象配置到其中 SearchRequest searchRequest = new SearchRequest("goods"); searchRequest.source(searchSourceBuilder); // 执行查询,然后处理响应结果 SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); // 根据状态和数据条数验证是否返回了数据 if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {     SearchHits hits = searchResponse.getHits();     for (SearchHit hit : hits) {         // 将 JSON 转换成对象         Goods goods = JSON.parseObject(hit.getSourceAsString(), Goods.class);           // 获取高亮的数据         HighlightField highlightField = hit.getHighlightFields().get("title");         System.out.println("高亮名称:" + highlightField.getFragments()[0].string());           // 替换掉原来的数据         Text[] fragments = highlightField.getFragments();         if (fragments != null && fragments.length > 0) {             StringBuilder title = new StringBuilder();             for (Text fragment : fragments) {                 //System.out.println(fragment);                 title.append(fragment);             }             goods.setTitle(title.toString());         }         // 输出查询信息         log.info(goods.toString());     } }  DSL:  POST goods/_search {   "query": {     "match": {       "title": "跑鞋"     }   },   "highlight": {     "fields": {       "body": {         "pre_tags": [           "<font color='red'>"         ],         "post_tags": [           "</font>"         ]       },       "title": {}     }   } }  5.3.13、聚合查询 // 构建查询条件 MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery(); // 创建查询源构造器 SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(matchAllQueryBuilder);   // 获取最贵的商品 AggregationBuilder maxPrice = AggregationBuilders.max("maxPrice").field("price"); searchSourceBuilder.aggregation(maxPrice); // 获取最便宜的商品 AggregationBuilder minPrice = AggregationBuilders.min("minPrice").field("price"); searchSourceBuilder.aggregation(minPrice);   // 创建查询请求对象,将查询对象配置到其中 SearchRequest searchRequest = new SearchRequest("goods"); searchRequest.source(searchSourceBuilder); // 执行查询,然后处理响应结果 SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); Aggregations aggregations = searchResponse.getAggregations(); ParsedMax max = aggregations.get("maxPrice"); log.info("最贵的价格:" + max.getValue()); ParsedMin min = aggregations.get("minPrice"); log.info("最便宜的价格:" + min.getValue());  DSL:  POST goods/_search {   "aggs": {     "max_price": {       "max": {         "field": "price"       }     }   } } 5.3.14、分组查询 // 构建查询条件 MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery(); // 创建查询源构造器 SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(matchAllQueryBuilder);   // 根据商品分类进行分组查询 TermsAggregationBuilder aggBrandName = AggregationBuilders         .terms("brandNameName")         .field("brandName"); searchSourceBuilder.aggregation(aggBrandName);   // 创建查询请求对象,将查询对象配置到其中 SearchRequest searchRequest = new SearchRequest("goods"); searchRequest.source(searchSourceBuilder); // 执行查询,然后处理响应结果 SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); Aggregations aggregations = searchResponse.getAggregations(); ParsedStringTerms aggBrandName1 = aggregations.get("brandNameName"); for (Terms.Bucket bucket : aggBrandName1.getBuckets()) {     // 分组名 ==== 数量     System.out.println(bucket.getKeyAsString() + "====" + bucket.getDocCount()); }  DSL:  POST goods/_search {   "aggs": {     "brandNameName": {       "terms": {         "field": "brandName"       }     }   } } 5.3.15、子查询 // 构建查询条件 MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery(); // 创建查询源构造器 SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(matchAllQueryBuilder);   // 根据商品分类进行分组查询,并且获取分类商品中的平均价格 TermsAggregationBuilder subAggregation = AggregationBuilders         .terms("brandNameName")         .field("brandName")         .subAggregation(AggregationBuilders.avg("avgPrice").field("price")); searchSourceBuilder.aggregation(subAggregation);   // 创建查询请求对象,将查询对象配置到其中 SearchRequest searchRequest = new SearchRequest("goods"); searchRequest.source(searchSourceBuilder); // 执行查询,然后处理响应结果 SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); Aggregations aggregations = searchResponse.getAggregations(); ParsedStringTerms aggBrandName1 = aggregations.get("brandNameName"); for (Terms.Bucket bucket : aggBrandName1.getBuckets()) {     // 获取聚合后的品牌的平均价格,注意返回值不是Aggregation对象,而是指定的ParsedAvg对象     ParsedAvg avgPrice = bucket.getAggregations().get("avgPrice");     // 分组名 ==== 平均价格     System.out.println(bucket.getKeyAsString() + "====" + avgPrice.getValueAsString()); }  DSL:  POST goods/_search {   "aggs": {     "brandNameName": {       "terms": {         "field": "brandName"       },       "aggs": {         "avgPrice": {           "avg": {             "field": "price"           }         }       }     }   } }  5.3.16、子查询下的子查询 子查询下的子查询有点绕。  // 构建查询条件 MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery(); // 创建查询源构造器 SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(matchAllQueryBuilder);   // 注意这里聚合写的位置不要写错,很容易搞混,错一个括号就不对了 TermsAggregationBuilder subAggregation = AggregationBuilders         .terms("categoryNameAgg").field("categoryName")         .subAggregation(AggregationBuilders.avg("categoryNameAvgPrice").field("price"))         .subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName")                 .subAggregation(AggregationBuilders.avg("brandNameAvgPrice").field("price"))); searchSourceBuilder.aggregation(subAggregation);   // 创建查询请求对象,将查询对象配置到其中 SearchRequest searchRequest = new SearchRequest("goods"); searchRequest.source(searchSourceBuilder); // 执行查询,然后处理响应结果 SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); //获取总记录数 System.out.println("totalHits = " + searchResponse.getHits().getTotalHits().value); // 获取聚合信息 Aggregations aggregations = searchResponse.getAggregations(); ParsedStringTerms categoryNameAgg = aggregations.get("categoryNameAgg"); //获取值返回 for (Terms.Bucket bucket : categoryNameAgg.getBuckets()) {     // 获取聚合后的分类名称     String categoryName = bucket.getKeyAsString();     // 获取聚合命中的文档数量     long docCount = bucket.getDocCount();     // 获取聚合后的分类的平均价格,注意返回值不是Aggregation对象,而是指定的ParsedAvg对象     ParsedAvg avgPrice = bucket.getAggregations().get("categoryNameAvgPrice");       System.out.println(categoryName + "======平均价:" + avgPrice.getValue() + "======数量:" + docCount);       ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brandNameAgg");     for (Terms.Bucket brandeNameAggBucket : brandNameAgg.getBuckets()) {         // 获取聚合后的品牌名称         String brandName = brandeNameAggBucket.getKeyAsString();           // 获取聚合后的品牌的平均价格,注意返回值不是Aggregation对象,而是指定的ParsedAvg对象         ParsedAvg brandNameAvgPrice = brandeNameAggBucket.getAggregations().get("brandNameAvgPrice");           System.out.println("     " + brandName + "======" + brandNameAvgPrice.getValue());     } }  DSL:  POST goods/_search {   "aggs": {     "categoryNameName": {       "terms": {         "field": "categoryName"       },       "aggs": {         "avgPrice": {           "avg": {             "field": "price"           }         },         "brandNameAgg": {           "terms": {             "field": "brandName"           },           "aggs": {             "brandNameAvg": {               "avg": {                 "field": "price"               }             }           }         }       }     }   } }  RestClient API使用的参考文献:重学Elasticsearch第6章 : SpringBoot整合RestHighLevelClient_elasticsearch_white camel-华为云开发者联盟  6、总结 本文主要介绍了Springboot与Elasticsearch集成和使用,重点内容如下:  选择RestHighLevelClient作为ES的RestClient。 核心步骤:增加properties配置、引入pom、配置conf类。 调用API、使用DSL语法。 ————————————————             原文链接:https://blog.csdn.net/baily_ycl/article/details/138240754 
  • [技术干货] SpringBoot中RestClient端的详解
     Spring Boot 提供了 RestTemplate 来辅助发起一个 REST 请求,默认通过 JDK 自带的 HttpURLConnection 来作为底层 HTTP 消息的发送方式,使用 JackSon 来序列化服务器返回的 JSON 数据。  RestTemplate 是核心类, 提供了所有访问 REST 服务的接口,尽管实际上可以使用 HTTP Client 类或者 java.net.URL来完成,但 RestTemplate 提供了阻STful 风格的 API。 Spring Boot 提供了 RestTemplateBuilder 来创建一个 RestTemplate。     RestTemplate定义11个基本操作方法,大致如下:  delete(): 在特定的URL上对资源执行HTTP DELETE操作 exchange(): 在URL上执行特定的HTTP方法,返回包含对象的ResponseEntity,这个对象是从响应体中 映射得到的       3.execute(): 在URL上执行特定的HTTP方法,返回一个从响应体映射得到的对象(所有的get、post、delete、put、options、head、exchange方法最终调用的都是excute方法),例如:  @Override  public <T> T getForObject(String url, Class<T> responseType, Object... urlVariables) throws RestClientException {  RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); HttpMessageConverterExtractor<T> responseExtractor = <span style="white-space:pre"> </span>new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger); return execute(url, HttpMethod.GET, requestCallback, responseExtractor, urlVariables);  }     4.getForEntity(): 发送一个HTTP GET请求,返回的ResponseEntity包含了响应体所映射成的对象      5.getForObject() :发送一个HTTP GET请求,返回的请求体将映射为一个对象      6.postForEntity() :POST 数据到一个URL,返回包含一个对象的ResponseEntity,这个对象是从响应体中映射得  到的     7.postForObject(): POST 数据到一个URL,返回根据响应体匹配形成的对象     8.headForHeaders(): 发送HTTP HEAD请求,返回包含特定资源URL的HTTP头     9.optionsForAllow(): 发送HTTP OPTIONS请求,返回对特定URL的Allow头信息    10.postForLocation() :POST 数据到一个URL,返回新创建资源的URL    11.put(): PUT 资源到特定的URL  实际上,由于Post 操作的非幂等性,它几乎可以代替其他的CRUD操作.  一、GET请求 在RestTemplate中,发送一个GET请求,我们可以通过如下两种方式:  第一种:getForEntity  getForEntity方法的返回值是一个ResponseEntity<T>,ResponseEntity<T>是Spring对HTTP请求响应的封装,包括了几个重要的元素,如响应码、contentType、contentLength、响应消息体等。其重载方法如下:  <T> ResponseEntity<T> getForObject(URI url, Class<T> responseType) throws RestClientException; <T> ResponseEntity<T> getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException; <T> ResponseEntity<T> getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException; 比如下面一个例子:  @Autowired  RestTemplateBuilder restTemplateBuilder;    @RequestMapping("/gethello")  public String getHello() {  RestTemplate client = restTemplateBuilder.build ();  ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class);  String body = responseEntity.getBody();  HttpStatus statusCode = responseEntity.getStatusCode();  int statusCodeValue = responseEntity.getStatusCodeValue();  HttpHeaders headers = responseEntity.getHeaders();  StringBuffer result = new StringBuffer(); result.append("responseEntity.getBody():").append(body).append("<hr>") .append("responseEntity.getStatusCode():").append(statusCode).append("<hr>") .append("responseEntity.getStatusCodeValue():").append(statusCodeValue).append("<hr>") .append("responseEntity.getHeaders():").append(headers).append("<hr>");  return result.toString();  } 有时候我在调用服务提供者提供的接口时,可能需要传递参数,有两种不同的方式,如下:  @RequestMapping("/sayhello")  public String sayHello() {  ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/sayhello?name={1}", String.class, "张三");  return responseEntity.getBody();  }    @RequestMapping("/sayhello2")  public String sayHello2() {  Map<String, String> map = new HashMap<>(); map.put("name", "李四");  ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/sayhello?name={name}", String.class, map);  return responseEntity.getBody();  } 可以用一个数字做占位符,最后是一个可变长度的参数,来一一替换前面的占位符 也可以前面使用name={name}这种形式,最后一个参数是一个map,map的key即为前边占位符的名字,map的value为参数值 第一个调用地址也可以是一个URI而不是字符串,这个时候我们构建一个URI即可,参数神马的都包含在URI中了,如下:  @RequestMapping("/sayhello3")  public String sayHello3() {  UriComponents uriComponents = UriComponentsBuilder.fromUriString("http://HELLO-SERVICE/sayhello?name={name}").build().expand("王五").encode();  URI uri = uriComponents.toUri();  ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class);    return responseEntity.getBody();  } 通过Spring中提供的UriComponents来构建Uri即可。  当然,服务提供者不仅可以返回String,也可以返回一个自定义类型的对象,比如我的服务提供者中有如下方法:  @RequestMapping(value = "/getbook1", method = RequestMethod.GET) public Book book1() {  return new Book("三国演义", 90, "罗贯中", "花城出版社");  } 对于该方法我可以在服务消费者中通过如下方式来调用:  @RequestMapping("/book1")  public Book book1() {      ResponseEntity<Book> responseEntity = restTemplate.getForEntity("http://HELLO-    SERVICE/getbook1", Book.class);      return responseEntity.getBody();  } 第二种:getForObject  getForObject函数实际上是对getForEntity函数的进一步封装,如果你只关注返回的消息体的内容,对其他信息都不关注,此时可以使用getForObject,重载方法如下:  <T> T getForObject(URI url, Class<T> responseType) throws RestClientException; <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException; <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;    二、POST请求 在RestTemplate中,POST请求可以通过如下三个方法来发起:  第一种:postForEntity、postForObject  POST请求有postForObject()和postForEntity()两种方法,和GET请求的getForObject()和getForEntity()方法类似。getForLocation()是POST请求所特有的。  <T> T postForObject(URI url, Object request, Class<T> responseType) throws RestClientException; <T> T postForObject(String url, Object request, Class<T> responseType, Object... uriVariables) throws RestClientException; <T> T postForObject(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException; 上面三个方法中,第一个参数都是资源要POST到的URL,第二个参数是要发送的对象,而第三个参数是预期返回的Java类型。在URL作为String类型的两个版本中,第四个参数指定了URL变量(要么是可变参数列表,要么是一个Map)。     该方法和get请求中的getForEntity方法类似,如下例子:  @RequestMapping("/book3")  public Book book3() {  Book book = new Book();  book.setName("红楼梦");  ResponseEntity<Book> responseEntity = restTemplate.postForEntity("http://HELLO-SERVICE/getbook2", book, Book.class);  return responseEntity.getBody();  }    第二种:postForLacation  postForLacation()会在POST请求的请求体中发送一个资源到服务器端,返回的不再是资源对象,而是创建资源的位置。  postForLocation(String url, Object request, Object... uriVariables) throws RestClientException; postForLocation(String url, Object request, Map<String, ?> uriVariables) throws RestClientException; postForLocation(URI url, Object request) throws RestClientException;    public String postSpitter(Spitter spitter) {  RestTemplate rest = new RestTemplate();  return rest.postForLocation("http://localhost:8080/Spitter/spitters", spitter).toString(); } postForLocation也是提交新资源,提交成功之后,返回新资源的URI,postForLocation的参数和前面两种的参数基本一致,只不过该方法的返回值为Uri,这个只需要服务提供者返回一个Uri即可,该Uri表示新资源的位置。     三、PUT请求 在RestTemplate中,对PUT请求可以通过put方法进行调用实现,比如:  RestTemplate restTemplate=new RestTemplate(); Longid=100011; User user=new User("didi",40);  restTemplate.put("http://USER-SERVICE/user/{l}",user,id);    put函数也实现了三种不同的重载方法:  put(String url,Object request,Object... urlVariables) put(String url,Object request,Map urlVariables) put(URI url,Object request) put函数为void类型,所以没有返回内容,也就没有其他函数定义的responseType参数,除此之外的其他传入参数定义与用法与postForObject基本一致。     四、DELETE请求 在RestTemplate中,对DELETE请求可以通过delete方法进行调用实现,比如:  RestTemplate restTemplate=new RestTemplate(); Longid=10001L; restTemplate.delete("http://USER-SERVICE/user/{1)",id);     delete函数也实现了三种不同的重载方法:  delete(String url,Object... urlVariables) delete(String url,Map urlVariables) delete(URI url) 由于我们在进行REST请求时,通常都将DELETE请求的唯一标识拼接在url中,所以DELETE请求也不需要request的body信息,就如put()方法一样,返回值类型为void。  说明:第三种重载方法,url指定DELETE请求的位置,urlVariables绑定url中的参数即可。  五、通用方法exchange() exchange方法可以在发送个服务器端的请求中设置头信息,其重载方法如下:  <T> ResponseEntity<T> exchange(URI url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType) throws RestClientException; <T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException; <T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException; MultiValueMap<String, String> headers = new LinkedMultiValueMap<String, String>(); headers.add("Accept", "application/json");  HttpEntity<Object> requestEntity = new HttpEntity<Object>(headers);  ResponseEntity<Spitter> response=rest.exchange("http://localhost:8080/Spitter/spitters/ {spitter}", HttpMethod.GET, requestEntity, Spitter.class, spitterId);    补充:         如果期望返回的类型是一个列表,如 List,不能简单调用 xxxForObject,因为存在泛型的类型擦除, RestTemplate 在反序列化的时候并不知道实际反序列化的类型,因此可以使用 ParameterizedTypeReference 来包含泛型类型,代码如下:  RestTemplate client= restTemplateBuilder.build();  //根据条件查询一纽订单  String uri = base+"/orders?offset={offset }";  Integer offset = 1;  //元参数  HttpEntity body = null;  ParameterizedTypeReference<List<Order> typeRef = new ParameterizedTypeReference<List<Order>(){};  ResponseEntity<List<Order> rs=client.exchange(uri, HttpMethod.GET, body, typeRef, offset); List<Order> order = rs.getBody() ;    注意到 typeRef 定义是用{}结束的,这里创建了一个 ParameterizedTypeReference 子类,依 据在类定义中的泛型信息保留的原则, typeRef保留了期望返回的泛型 List。  exchange 是一个基础的 REST 调用接口,除了需要指明 HTTP Method,调用方法同其他方法类似。  除了使用ParameterizedTypeReference子类对象,也可以先将返回结果映射成 json字符串,然后通过 ObjectMapper 来转为指定类型(笔记<SpringBoot中的JSON>中有介绍 )  ————————————————          原文链接:https://blog.csdn.net/fsy9595887/article/details/86420048 
  • [技术干货] Java GC(垃圾回收)机制详解-转载
    1、GC触发的条件 Java中,GC的触发主要有两种方式:  显式触发:通过程序调用System.gc()或Runtime.getRuntime().gc()方法,向JVM发出建议进行垃圾回收的请求。但请注意,这仅仅是建议,JVM可以忽略这个请求。  隐式触发:由JVM根据内部算法和内存使用情况自动决定。当堆内存中的对象空间不足以满足新对象分配时,JVM会自动触发GC以尝试回收内存。  2、GCRoots的对象类型 在Java中,GC Roots是对象图遍历的起始点,它们是在垃圾回收过程中,被JVM视为存活的对象。GC Roots主要包括以下几种:  虚拟机栈中引用的对象:包括局部变量和参数等。 方法区中类静态属性引用的对象:类的静态变量引用的对象。 方法区中常量引用的对象:如字符串常量池中的对象。 本地方法栈中JNI(Java Native Interface)引用的对象:由JNI调用本地方法时,本地方法栈中引用的对象。 ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/qq_41840843/article/details/140333907 
  • [技术干货] Java之类和对象-转载
    static关键字 1修饰属性 Java的静态属性和类相关, 和具体的实例无关. 换句话说, 同一个类的不同实例共用同一个静态属性. 举例 class TestDemo{     public int a;     public static int count; } public class Main{       public static void main(String[] args) {         TestDemo t1 = new TestDemo();         t1.a++;         TestDemo.count++;         System.out.println(t1.a);         System.out.println(TestDemo.count);         System.out.println("============");         TestDemo t2 = new TestDemo();         t2.a++;         TestDemo.count++;         System.out.println(t2.a);         System.out.println(TestDemo.count);    } }  上面的代码输出结果是: 1 1 ============ 为什么呢?-----count成员属性被static修饰后,属于类不属于具体的对象,而a属于具体的对象。  2修饰方法 如果在任何方法上应用 static 关键字,此方法称为静态方法。  静态方法属于类,而不属于类的对象。 可以直接调用静态方法,而无需创建类的实例。 静态方法可以访问静态数据成员,并可以更改静态数据成员的值。 静态方法不可以访问非静态数据成员。 举例 class TestDemo{     public int a;     public static int count;          public static void change() {         count = 100;         //a = 10; error 不可以访问非静态数据成员    } } public class Main{      public static void main(String[] args) {         TestDemo.change();//无需创建实例对象 就可以调用         System.out.println(TestDemo.count);       } }  final 被final修饰的成员属性叫做常量,属于对象,被final修饰后,后续不可被修改。 被final和static同时修饰的成员属性叫做静态常量,属于类本身,只有一份,后续不可被修改。 构造方法 基本语法 构造方法是一种特殊方法, 使用关键字new实例化新对象时会被自动调用, 用于完成初始化操作. new 执行过程 为对象分配内存空间 调用对象的构造方法 语法规则 1.方法名称必须与类名称相同 2.构造方法没有返回值类型声明 3.每一个类中一定至少存在一个构造方法(没有明确定义,则系统自动生成一个无参构造)。 注意事项 如果类中没有提供任何的构造函数,那么编译器会默认生成一个不带有参数的构造函数 若类中定义了构造方法,则默认的无参构造将不再生成. 构造方法支持重载. 规则和普通方法的重载一致 . this关键字 this表示当前对象引用(注意不是当前对象). 可以借助 this 来访问对象的字段和方法。 可以使用this调用构造函数,但是要放在第一行!!! 举例 class Person {       private String name;//实例成员变量      private int age;       private String sex;         //默认构造函数 构造对象      public Person() {           //this调用构造函数          this("bit", 12, "man");//必须放在第一行进行显示      }         //这两个构造函数之间的关系为重载。      public Person(String name,int age,String sex) {           this.name = name;           this.age = age;           this.sex = sex;       }       public void show() {           System.out.println("name: "+name+" age: "+age+" sex: "+sex);       }  }  public class Main{       public static void main(String[] args) {       Person person = new Person();//调用不带参数的构造函数      person.show();       } } // 输出 结果 name: bit age: 12 sex: man 代码块 字段的初始化方式有: 1. 就地初始化 2. 使用构造方法初始化 3.使用代码块初始化  下面将讲解使用第3种方式的初始化。 定义 使用 {} 定义的一段代码. 根据代码块定义的位置以及关键字,又可分为以下四种: 普通代码块 构造块 静态块 同步代码块 普通代码块 定义在方法中的代码块。 举例 public class Main{   public static void main(String[] args) {       { //直接使用{}定义,普通方法块          int x = 10 ;           System.out.println("x1 = " +x);       }       int x = 100 ;       System.out.println("x2 = " +x);   }  }  // 执行结果 x1 = 10  x2 = 100  构造代码块 定义在类中的代码块(不加修饰符),也称实例代码块。  构造代码块一般用于初始化实例成员变量。 举例 class Person{     private String name;//实例成员变量     private int age;     private String sex;       public Person() {         System.out.println("I am Person init()!");     }     //实例代码块     {         this.name = "bit";         this.age = 12;         this.sex = "man";         System.out.println("I am instance init()!");     }     public void show(){         System.out.println("name: "+name+" age: "+age+" sex: "+sex);     }   } public class Main {     public static void main(String[] args) {         Person p1 = new Person();         p1.show();     } } 运行结果: I am instance init()! I am Person init()! name: bit age: 12 sex: man 静态代码块 使用static定义的代码块。一般用于初始化静态成员属性。 静态代码块不管生成多少个对象,其只会执行一次,且是最先执行的。 静态代码块执行完毕后, 实例代码块(构造块)执行,再然后是构造函数执行。 举例 class Person{     private String name;//实例成员变量     private int age;     private String sex;     private static int count = 0;//静态成员变量 由类共享数据 方法区       public Person(){         System.out.println("I am Person init()!");     }       //实例代码块     {         this.name = "bit";         this.age = 12;         this.sex = "man";         System.out.println("I am instance init()!");     }       //静态代码块     static {         count = 10;//只能访问静态数据成员          System.out.println("I am static init()!");     }       public void show(){         System.out.println("name: "+name+" age: "+age+" sex: "+sex);     }   } public class Main {     public static void main(String[] args) {         Person p1 = new Person();         Person p2 = new Person();//静态代码块是否还会被执行?     } } 匿名对象 匿名只是表示没有名字的对象. 没有引用的对象称为匿名对象. 匿名对象只能在创建对象时使用. 如果一个对象只是用一次, 后面不需要用了, 可以考虑使用匿名对象. 举例 class Person {     private String name;     private int age;     public Person(String name,int age) {         this.age = age;         this.name = name;     }     public void show() {         System.out.println("name:"+name+" " + "age:"+age);     } } public class Main {     public static void main(String[] args) {         new Person("caocao",19).show();//通过匿名对象调用方法     } } toString class Person {     private String name;     private int age;     public Person(String name,int age) {         this.age = age;         this.name = name;     }     public void show() {         System.out.println("name:"+name+" " + "age:"+age);     }   } public class Test {     public static void main(String[] args) {         Person person = new Person("caocao",19);         person.show();         System.out.println(person);     } } 为什么第二行打印结果是这样的呢? 为了更好的打印,需要重写Object的toString方法,即:  class Person {     private String name;     private int age;     public Person(String name,int age) {         this.age = age;         this.name = name;     }     public void show() {         System.out.println("name:"+name+" " + "age:"+age);     }     //重写Object的toString方法     @Override     public String toString() {         return "Person{" +                 "name='" + name + '\'' +                 ", age=" + age +                 '}';     } } public class Test {     public static void main(String[] args) {         Person person = new Person("caocao",19);         person.show();         System.out.println(person);     } }  总结 一个类可以产生无数的对象,类就是模板,对象就是具体的实例。 类中定义的属性,大概分为几类:类属性,对象属性。其中被static所修饰的数据属性称为类属性, static修饰的方法称为类方法,特点是不依赖于对象,我们只需要通过类名就可以调用其属性或者方法。 静态代码块优先实例代码块执行,实例代码块优先构造函数执行。 this关键字代表的是当前对象的引用。并不是当前对象。 ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/wmh_1234567/article/details/140722526 
  • [专题汇总] 7月份技术合集它来了。速进
     大家好,本次7月合计带来的内容包括 .Sql Server,mysql,java,python,nginx多个内容供您查找学习,感谢1.Sql Server查询卡顿的排查方法【转】 https://bbs.huaweicloud.com/forum/thread-02113157814182541065-1-1.html  2.mysql WITH RECURSIVE语法的具体使用【转】 https://bbs.huaweicloud.com/forum/thread-02127157734079592051-1-1.html  3.MySQL文件权限存在的安全问题和解决方案【转】 https://bbs.huaweicloud.com/forum/thread-02127157814439433059-1-1.html  4.MySQL的表约束的具体使用【转】 https://bbs.huaweicloud.com/forum/thread-02119157814488009063-1-1.html  5.MySQL建表语句基础及示例详解【转】 https://bbs.huaweicloud.com/forum/thread-02113157815352821066-1-1.html  6.MySQL分表和分区分表的区别小结【转】 https://bbs.huaweicloud.com/forum/thread-02119157815516348065-1-1.html  7.Redission实现分布式锁lock()和tryLock()方法的区别小结【转】 https://bbs.huaweicloud.com/forum/thread-02119157816143144066-1-1.html  8.无法启动Redis打开redis-server闪退的问题解决办法【转】 https://bbs.huaweicloud.com/forum/thread-0220157816373247058-1-1.html  9.Nginx出现404 Not Found nginx/1.23.4的完美解决方案【转】 https://bbs.huaweicloud.com/forum/thread-0220157816562623059-1-1.html  10.Nginx上传文件出现“ 413 (499 502 404) Request Entity Too Large错误解决【转】 https://bbs.huaweicloud.com/forum/thread-02127157817937530061-1-1.html  11.Nginx代理MySQL实现通过域名连接数据库的详细教程【转】 https://bbs.huaweicloud.com/forum/thread-02127157818132517062-1-1.html  12.Nginx实现灰度发布的常见方法小结【转】 https://bbs.huaweicloud.com/forum/thread-02127157818398915063-1-1.html  13.Vscode中launch.json与tasks.json文件的详细介绍【转】 https://bbs.huaweicloud.com/forum/thread-02127157818683109058-1-1.html  14.Python中网络请求的12种方式【转】 https://bbs.huaweicloud.com/forum/thread-02113157818977531068-1-1.html  15. pip install过程中出现error: subprocess-exited-with-error错误的解决办法【转】 https://bbs.huaweicloud.com/forum/thread-0286157819160557055-1-1.html  16.在Python代码中执行Linux命令的详细用法教程【转】 https://bbs.huaweicloud.com/forum/thread-02127157819336403064-1-1.html  17.ava 集合去重的三种方法【转】 https://bbs.huaweicloud.com/forum/thread-0220157820152566061-1-1.html 
  • [技术干货] ava 集合去重的三种方法【转】
    在Java中,如果你有一个集合(如List、Set等),其中包含自定义对象,并且你希望根据某个特定属性去重,只保留每个具有唯一属性值的对象的一个实例,你可以使用以下几种方法:方法1:使用HashSet如果对象的类实现了equals()和hashCode()方法,并且这两个方法是基于你想去重的那个属性来实现的,那么可以直接将列表转换为HashSet以达到去重的目的。123List<MyObject> list = ...; // 假设这是你的原始列表Set<MyObject> uniqueSet = new HashSet<>(list);List<MyObject> uniqueList = new ArrayList<>(uniqueSet);方法2:Stream API (Java 8及以上版本)通过Java 8引入的Stream API可以更方便地处理这种情况,尤其是当你想基于对象的某个属性进行去重时:1234567891011List<MyObject> list = ...;List<MyObject> uniqueList = list.stream()    .collect(Collectors.collectingAndThen(        Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(MyObject::getProperty))),        ArrayList::new    ));// 或者简化版本,如果MyObject直接或间接实现了Comparable接口:List<MyObject> uniqueList = list.stream()    .distinct()    .collect(Collectors.toList());上述代码中,我们首先将列表转换为流,然后使用Collectors.toCollection()收集到一个TreeSet中。TreeSet会自动根据传入的比较器对元素排序并去除重复项。这里假设getProperty()是获取对象属性的方法。最后将TreeSet转换回ArrayList。方法3:手动遍历并使用Map如果你想根据对象的某个属性保持第一个出现的对象,可以使用toMap方法:12345678List<MyObject> list = ...;Map<String, MyObject> map = list.stream()    .collect(Collectors.toMap(        MyObject::getProperty, // key extractor function        Function.identity(),   // value mapping function        (o1, o2) -> o1       // merge function - 如果有冲突则保留第一个对象    ));List<MyObject> uniqueList = new ArrayList<>(map.values());这个方法会根据对象的属性值作为键存入Map中,由于Map不允许键重复,所以结果自然就是唯一的。通过merge函数指定当遇到相同键时保留第一个对象。请注意,这些示例假定MyObject是一个代表具体业务实体的类,而getProperty()是返回该类中用于判断是否重复的属性值的方法。根据实际情况调整类名和方法名。
  • [知识分享] java导出word 动态填充数据
    在Java中,使用 poi-tl 插件可以非常方便地生成和操作Word文档,并且能够动态填充数据。要根据导出数据的类别数量动态显示两列或三列,可以按照以下步骤进行:1. 导入依赖首先,在您的项目中添加 poi-tl 和 poi 的依赖。以Maven为例,您需要在 pom.xml 文件中添加以下内容: <dependency><groupId>com.deepoove</groupId><artifactId>poi-tl</artifactId><version>1.9.0</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>4.1.2</version></dependency>2. 创建模板创建一个 Word 模板(例如 template.docx),其中包含占位符。在这里,我们假设有一个表格区域用来放置动态列的数据。示例模板(template.docx){{table}}3. Java 代码实现编写Java代码,根据数据的类别动态生成两列或三列的表格,并将数据填充到Word文档中。动态填充数据的Java示例 import com.deepoove.poi.XWPFTemplate;import com.deepoove.poi.data.RowRenderData;import com.deepoove.poi.data.TableRenderData;import java.io.FileOutputStream;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;public class DynamicWordExport {public static void main(String[] args) throws Exception {// 假设这是要填充的数据List<Map<String, String>> data = new ArrayList<>();data.add(new HashMap<String, String>() {{put("Name", "John");put("Age", "30");put("Gender", "Male");}});data.add(new HashMap<String, String>() {{put("Name", "Jane");put("Age", "28");put("Gender", "Female");}});// 根据数据动态生成 TableRenderDataTableRenderData tableData = generateTableData(data);// 填充模板Map<String, Object> templateData = new HashMap<>();templateData.put("table", tableData);XWPFTemplate template = XWPFTemplate.compile("template.docx").render(templateData);// 输出到文件FileOutputStream out = new FileOutputStream("output.docx");template.write(out);out.close();template.close();}private static TableRenderData generateTableData(List<Map<String, String>> data) {List<RowRenderData> rows = new ArrayList<>();// 表头行List<String> headers = new ArrayList<>(data.get(0).keySet());RowRenderData headerRow = RowRenderData.build(headers.toArray(new String[0]));rows.add(headerRow);// 数据行for (Map<String, String> entry : data) {List<String> rowValues = new ArrayList<>();for (String key : headers) {rowValues.add(entry.get(key));}RowRenderData row = RowRenderData.build(rowValues.toArray(new String[0]));rows.add(row);}return new TableRenderData(rows);}}4. 运行程序执行上述 Java 程序,将会生成一个包含动态数据的 Word 文档 output.docx。5. 根据列数调整表格布局如果要根据数据类别的多少动态显示两列或三列,可以在 generateTableData 方法中根据 headers 的数量进行条件判断: private static TableRenderData generateTableData(List<Map<String, String>> data) {List<RowRenderData> rows = new ArrayList<>();// 表头行List<String> headers = new ArrayList<>(data.get(0).keySet());RowRenderData headerRow = RowRenderData.build(headers.toArray(new String[0]));rows.add(headerRow);// 数据行for (Map<String, String> entry : data) {List<String> rowValues = new ArrayList<>();for (String key : headers) {rowValues.add(entry.get(key));}RowRenderData row = RowRenderData.build(rowValues.toArray(new String[0]));// 根据列数调整表格布局if (headers.size() == 2) {row.setCellWidth(Arrays.asList("50%", "50%"));} else if (headers.size() == 3) {row.setCellWidth(Arrays.asList("33%", "33%", "33%"));}rows.add(row);}return new TableRenderData(rows);}通过这种方法,可以根据数据的类别数量动态调整 Word 文档中的表格列数和布局。
总条数:739 到第
上滑加载中