-
目录 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
-
一篇介绍了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
-
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
-
【问题来源】黑龙江农信社【问题简要】开发 vxml 版本自助语音流程时,使用grammer 标签 的 src 属性上送语法文件是,流程跟踪日志中,提示异常【问题类别】vxml 版本自主语音流程开发【AICC解决方案版本】AICC 版本:AICC 22.200.0【问题现象描述】页面中通过 grammar 标签 读取项目中一个 grammar.xml 文件流程文件中提示异常 There was a DEFINE_GRAMMAR failure 语法文件内容如下
-
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
-
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月合计带来的内容包括 .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
-
在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中,使用 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 文档中的表格列数和布局。
-
介绍七夕魔方照片墙是一款创意应用,旨在让用户通过一个3D魔方展示他们的照片。每个面都可以显示不同的图片,用户可以旋转和翻转魔方来浏览所有的照片。这种方式不仅仅是简单地展示照片,而是将视觉效果与互动体验结合起来,使得照片展示更加生动有趣。应用使用场景个人相册展示: 用户可以将自己的旅游照片、家庭照片等按日期或主题放在魔方上展示。企业产品宣传: 企业可以将产品图片制作成魔方,用于展会或者网站上进行互动展示。艺术作品展示: 艺术家可以将自己的作品集成在魔方上,观众可以通过旋转魔方查看每一个细节。教育培训: 可以用于教学内容的展示,比如展示几何图形的各个面。原理解释七夕魔方照片墙基于三维图形渲染技术,将多个二维图像拼接在一个立方体的六个面上。通过用户的交互(如鼠标拖拽、触摸屏滑动),实现立方体的旋转变化,从而展现不同的面上的照片。算法原理流程图及解释+----------------+| Load Images |+--------+-------+ | v+--------+----------+| Initialize Cube || - Create 3D model || - Map images |+--------+----------+ | v+--------+-----------+| Render Cube || - Set up camera || - Apply textures |+--------+-----------+ | v+--------+-----------+| User Interaction || - Detect input || - Update rotation |+--------+-----------+ | v+--------+-----------+| Update Display || - Re-render cube |+--------------------+加载图片:从本地或远程资源加载用户选择的图片。初始化魔方:创建一个3D模型,并将图片映射到魔方的六个面上。渲染魔方:设置摄像机视角,将已贴好图像的3D魔方渲染到屏幕上。用户交互:检测用户输入(如鼠标拖拽、手指滑动等),并根据输入更新魔方的旋转状态。更新显示:重新渲染魔方,以反映最新的旋转状态。应用场景代码示例实现以下是一个利用 Java 和 JavaFX 实现七夕魔方照片墙的简单示例:import javafx.application.Application;import javafx.scene.*;import javafx.scene.image.Image;import javafx.scene.paint.PhongMaterial;import javafx.scene.shape.Box;import javafx.stage.Stage;import javafx.scene.transform.Rotate;public class PhotoCube extends Application { private static final double CUBE_SIZE = 200.0; private static final String[] IMAGES = { "image1.jpg", "image2.jpg", "image3.jpg", "image4.jpg", "image5.jpg", "image6.jpg" }; @Override public void start(Stage primaryStage) throws Exception { Group root = new Group(); Scene scene = new Scene(root, 800, 600, true); PerspectiveCamera camera = new PerspectiveCamera(true); camera.setTranslateZ(-500); Box cube = createPhotoCube(); root.getChildren().add(cube); scene.setOnMouseDragged(event -> { cube.getTransforms().add(new Rotate(event.getSceneX(), Rotate.Y_AXIS)); cube.getTransforms().add(new Rotate(event.getSceneY(), Rotate.X_AXIS)); }); primaryStage.setTitle("七夕魔方照片墙"); primaryStage.setScene(scene); primaryStage.show(); } private Box createPhotoCube() { Box box = new Box(CUBE_SIZE, CUBE_SIZE, CUBE_SIZE); PhongMaterial material = new PhongMaterial(); material.setDiffuseMap(new Image(IMAGES[0])); box.setMaterial(material); return box; } public static void main(String[] args) { launch(args); }}部署测试场景开发环境: 安装 JDK 和 JavaFX SDK。确保你的开发环境可以编译和运行 JavaFX 项目。构建项目: 使用 IDE(如 IntelliJ IDEA 或 Eclipse)创建并构建该项目。运行测试: 在本地运行应用程序,测试魔方的旋转和图片展示功能。材料链接JavaFX 官方文档JavaFX 示例项目Java 图形编程入门教程总结七夕魔方照片墙通过3D图形渲染和用户交互,为照片展示提供了一种创新和有趣的方式。它不仅适用于个人照片展示,还可以用于企业宣传、教育培训等多个领域。通过详细的算法解释和代码实现,我们了解了其工作原理和实现方法。未来展望未来,可以考虑加入更多的交互功能,例如:动画效果:增加自动旋转和缩放的动画效果,使展示更具动态性。多媒体支持:除了图片外,还可以支持视频和音频展示,增加内容的丰富性。云端存储和分享:集成云存储服务,让用户可以方便地上传和分享自己的魔方照片墙。这些改进将进一步提升用户体验和应用的实用性。
-
在开发过程中,我们可能会遇到需要生成word,或者通过模板word替换相应内容的需求。但在文档中插入图片时,如果段落格式设置不对,就会导致图片只显示一点点或者不显示。接下来就介绍一下java编辑word和插入图片需怎么处理。1.引入依赖首先我们在项目中引入Apache POI,用于读取和操作word,这里我使用的版本是4.1.2,版本可以根据项目需求自己选择。12345<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>4.1.2</version></dependency> 2.编辑word这里是通过模板加入占位符,然后替换占位符的内容首先我们打开word模板文件12345678910String path = "***.docx";File file = new File(path);try { XWPFDocument template = new XWPFDocument(new FileInputStream(file)); // 替换内容 XWPFDocument outWord = PoiWordUtil.replaceWithPlaceholder(template, list); return outWord;} catch (IOException e) { log.error("读取模板文件失败", e);}替换相应内容1// 这里我定义了Placeholder来封装替换数据public static XWPFDocument replaceTextAndImage(XWPFDocument document, List<Placeholder> list) { for (XWPFParagraph xwpfParagraph : document.getParagraphs()) { String paragraphText = xwpfParagraph.getText(<em id="__mceDel"> if (StringUtils.isEmpty(paragraphText)) contin</em><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"> for (Placeholder placeholder : list) {</em></em></em></em></em></em><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"> String key = placeholder.getKey();</em></em></em></em></em></em></em><em><em><em><em><em><em><em> if (paragraphText.contains(key)) {<br> for (XWPFRun cellRun : xwpfParagraph.getRuns()) {<br> String text = cellRun.getText(0);<br> if (text != null && text.contains(key)) {<br> //获取占位符类型<br> String type = placeholder.getType();<br> //获取对应key的value<br> String value = placeholder.getValue();<br> if("0".equals(type)){<br> //把文本的内容,key替换为value<br> text = text.replace(key, value);<br> //把替换好的文本内容,保存到当前这个文本对象<br> cellRun.setText(text, 0);<br> }else {<br> text = text.replace(key, "");<br> cellRun.setText(text, 0);<br> if (StringUtils.isEmpty(value)) </em></em></em></em></em></em></em><em id="__mceDel"><em id="__mceDel">continue;</em></em><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"> try {<br> // 获取段落行距模式<br> int rule = xwpfParagraph.getSpacingLineRule().getValue();<br> // 如果段落行距为固定值,会导致图片显示不全,所以需要改成其他模式<br> if (LineSpacingRule.EXACT.getValue() == rule) {<br> // 设置段落行距为单倍行距<br> xwpfParagraph.setSpacingBetween(1);<br> }<br> // 获取文件流<br> InputStream imageStream = ImageUtils.getFile(value);</em></em></em></em></em></em></em><em id="__mceDel"> if (imageStream == null) continue;<br> // 通过BufferedImage获取图片信息<br> BufferedImage bufferedImage = ImageIO.read(imageStream);<br> int height = bufferedImage.getHeight();<br> int width = bufferedImage.getWidth();<br> // 这里需要重新获取流,之前的流已经被BufferedImage使用掉了<br> cellRun.addPicture(ImageUtils.getFile(value), XWPFDocument.PICTURE_TYPE_JPEG, "", Units.toEMU(width), Units.toEMU(height));</em> } catch (Exception e) {<br> e.printStackTrace();<br> }<br> }<br> }<br> }<br> }<br> }<br> }<br> return document;<br>}在插入图片时,如果段落的行距设置成了固定值,那么在显示图片时只能显示行距大小的部分,所以当插入图片的段落行距为固定值时,我们需要修改为其他模式,这样图片就能正常大小显示。然后我们使用cellRun.addPicture()来插入图片,这里我们可以通过BufferedImage来获取图片的尺寸大小。这样就解决了插入图片显示异常的问题了。转载自https://www.cnblogs.com/qq545505061/p/18302543
-
原子性的意义原子性特别是在并发编程领域,是一个极其重要的概念,原子性指的是一个操作或一组操作要么全部执行成功,要么全部不执行,不会出现部分执行的情况。这意味着原子性操作是不可分割的,它们在执行过程中不会被其他操作中断或干扰。原子性的意义在于它保证了数据的一致性和程序的正确性。在多线程或多进程的环境中,当多个操作同时访问和修改共享数据时,如果没有原子性保证,可能会导致数据不一致或不确定的结果。例如,如果一个线程在读取某个数据时,另一个线程同时修改了这个数据,那么第一个线程读取到的数据可能是不正确的。通过确保操作的原子性,可以避免这种情况,从而维护数据的完整性和程序的正确执行。了解了上面的原子性的重要概念后,接下来一起聊一聊 volatile 关键字。volatile 关键字在 Java 中用于确保变量的更新对所有线程都是可见的,但它并不保证复合操作的原子性。这意味着当多个线程同时访问一个 volatile 变量时,可能会遇到读取不一致的问题,尽管它们不会看到部分更新的值。Volatile 的限制不保证原子性:volatile 变量的单个读写操作是原子的,但复合操作(如自增或同步块)不是原子的。不保证顺序性:volatile 变量的读写操作不会与其他操作(如非 volatile 变量的读写)发生重排序。一个例子用一个示例来解释会更清楚点,假如我们有一段代码是这样的:class Counter { private volatile int count = 0; void increment() { count++; } int getCount() { return count; } } 尽管 count 是 volatile 变量,但 increment 方法中的复合操作 count++(读取-增加-写入)不是原子的。因此,在多线程环境中,多个线程可能会同时读取相同的初始值,然后增加它,导致最终值低于预期。volatile 不保证原子性的代码验证以下是一个简单的 Java 程序,演示了 volatile 变量在多线程环境中不保证复合操作原子性的问题:public class VolatileTest { private static volatile int counter = 0; public static void main(String[] args) throws InterruptedException { int numberOfThreads = 10000; Thread[] threads = new Thread[numberOfThreads]; for (int i = 0; i < numberOfThreads; i++) { threads[i] = new Thread(() -> { for (int j = 0; j < 100; j++) { counter++; } }); threads[i].start(); } for (int i = 0; i < numberOfThreads; i++) { threads[i].join(); } System.out.println("Expected count: " + (numberOfThreads * 100)); System.out.println("Actual count: " + counter); } }在这个例子中:counter 是一个 volatile 变量。每个线程都会对 counter 执行 100 次自增操作。理论上,如果 counter++ 是原子的,最终的 counter 值应该是 10000 * 100。然而,由于 counter++ 包含三个操作:读取 counter 的值、增加 1、写回 counter 的值,这些操作不是原子的。因此,在多线程环境中,最终的 counter 值通常会小于预期值,这证明了 volatile 变量不保证复合操作的原子性。解决方案1. 使用 synchronized 方法或块:将访问 volatile 变量的方法或代码块声明为 synchronized,确保原子性和可见性。class Counter { private volatile int count = 0; synchronized void increment() { count++; } synchronized int getCount() { return count; } }2. 使用 AtomicInteger 类:java.util.concurrent.atomic 包中的 AtomicInteger 提供了原子操作,可以替代 volatile 变量。import java.util.concurrent.atomic.AtomicInteger; class Counter { private AtomicInteger count = new AtomicInteger(0); void increment() { count.incrementAndGet(); } int getCount() { return count.get(); } }3. 使用锁(如 ReentrantLock):使用显式锁(如 ReentrantLock)来同步访问 volatile 变量的代码块。import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class Counter { private volatile int count = 0; private final Lock lock = new ReentrantLock(); void increment() { lock.lock(); try { count++; } finally { lock.unlock(); } } int getCount() { lock.lock(); try { return count; } finally { lock.unlock(); } } }使用volatile变量的正确使用场景如果操作是简单的读写,并且你只需要保证可见性,可以使用 volatile。但对于复合操作,可以使用上述其他方法来实现,通过这些方法,可以确保在多线程环境中对共享资源的正确同步和可见性。转载自https://www.cnblogs.com/wgjava/p/18311697
-
介绍在Spring Boot开发的动态世界中,确保数据完整性和跟踪变化是至关重要的。实现这一目标的一个强大工具是@Audited注解。本文深入探讨了该注解的复杂性、其目的、实现步骤以及如何利用其功能进行有效的实体审计。理解@AuditedSpring Boot中的@Audited注解用于审计实体,提供对数据随时间变化的详细记录。这在需要跟踪修改、用户操作或合规要求的情况下非常有价值。实现步骤1. 依赖项:要包含@Audited,需要在项目中添加spring-data-envers依赖。确保你的pom.xml或build.gradle反映这一添加。<!-- Maven Dependency --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-envers</artifactId> </dependency>spring-boot-starter-data-jpa依赖包含了使用Spring Data JPA进行数据访问所需的组件。然而,如果你特别想在Spring Boot中使用@Audited注解启用实体审计,还需要包含hibernate-envers依赖。该依赖添加了对Hibernate Envers的支持,这是负责实体版本控制和审计的工具。2. 实体配置:将@Audited注解应用于你想要审计的实体类。import org.hibernate.envers.Audited; @Entity @Audited public class YourEntity { // 你的实体字段和方法 }3. application.yml配置:确保你的application.yml或application.properties包含Hibernate Envers所需的配置。spring: data: jpa: repositories: enabled: true auditing: enabled: true4. 审计表字段:Hibernate Envers生成的审计表通常包括REV(修订号)、REVTYPE(修订类型)、AUDIT_TIMESTAMP(审计时间戳)等字段。这些字段共同存储对审计实体的历史更改。Spring Boot会自动创建审计表(例如,‘YourEntity_AUD’)以存储元数据。探索审计表中的字段:– REV:修订号(递增)– REVTYPE:修订类型(插入、更新、删除)– AUDITEDFIELD:审计字段值– MODIFIEDBY:进行更改的用户– MODIFIEDDATE:修改日期和时间5. 检索审计数据:使用Spring Data JPA仓库查询审计历史。import org.springframework.data.repository.history.RevisionRepository; import org.springframework.data.history.Revision; import java.util.List; public interface YourEntityAuditRepository extends RevisionRepository<YourEntity, Long, Integer> { List<Revision<Integer, YourEntity>> findRevisionsById(Long entityId); } 在这个例子中:– YourEntityAuditRepository扩展了RevisionRepository,这是一个处理修订的Spring Data JPA接口。– findRevisionsById方法允许你检索具有指定ID的实体的所有修订。然后,你可以在服务或控制器中使用此仓库查询审计历史:import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class AuditService { private final YourEntityAuditRepository entityAuditRepository; @Autowired public AuditService(YourEntityAuditRepository entityAuditRepository) { this.entityAuditRepository = entityAuditRepository; } public List<Revision<Integer, YourEntity>> getEntityRevisions(Long entityId) { return entityAuditRepository.findRevisionsById(entityId); } }另一个例子使用Hibernate Envers查询具有给定ID的特定实体的审计历史List<YourEntity_AUD> revisions = auditReader.findRevisions(YourEntity.class, entityld);– auditReader:一个AuditReader实例,由Hibernate Envers提供。它允许你与实体的审计历史进行交互。– findRevisions:Hibernate Envers提供的方法,用于检索具有指定ID的给定实体的所有修订。– YourEntity.class:你想要检索审计历史的实体类。– entityId:你想要获取修订的实体的特定ID。– List<YourEntity_AUD>:结果是一个审计实体(YourEntity_AUD)的列表,列表中的每个条目代表实体的一个修订。在Hibernate Envers中,当你为一个实体启用审计时,它会生成一个带有“_AUD”后缀的相应审计实体(默认情况下)。这个审计实体会跟踪原始实体随时间变化的所有更改。因此,这行代码本质上是在查询具有给定ID的实体的所有修订的审计历史,并将结果存储在一个审计实体列表中。然后,可以使用此列表分析或显示实体在不同修订中的更改。转载自https://www.cnblogs.com/didispace/p/18322448
-
前言在Spring框架中,@PostConstruct注解用于在Bean初始化完成后立即执行某些方法。这个注解的作用是保证在依赖注入完成后,执行一些初始化工作。诞生背景@PostConstruct注解的诞生是为了提供一种标准化的、简单的方法来进行对象初始化工作。1. 简化初始化逻辑在传统的Java开发中,进行对象初始化通常需要在构造函数中完成。然而,构造函数的局限性使得一些初始化操作变得复杂,例如:构造函数不能依赖于容器注入的资源,因为这些资源在对象实例化时可能还没有准备好。复杂的初始化逻辑会导致构造函数变得冗长且难以维护。@Component public class MyService { private final SomeDependency dependency; public MyService(SomeDependency dependency) { this.dependency = dependency; // 这里不能进行复杂的初始化逻辑,因为依赖可能还没有完全准备好 } }@PostConstruct提供了一种更清晰的方式来处理初始化逻辑。依赖注入完成后,容器会自动调用标注了 @PostConstruct的方法,使得开发者可以在这时安全地访问所有依赖资源。简化逻辑代码示例import javax.annotation.PostConstruct; import org.springframework.stereotype.Component; @Component public class MyService { private final SomeDependency dependency; public MyService(SomeDependency dependency) { this.dependency = dependency; // 这里不能进行复杂的初始化逻辑,因为依赖可能还没有完全准备好 } @PostConstruct public void init() { // 在这里进行初始化逻辑 // 这时依赖已经完全准备好,可以安全地使用 dependency.setup(); System.out.println("MyService 初始化完成"); } // 其他业务方法 public void performTask() { System.out.println("执行任务"); } }2. 标准化的生命周期管理在Java EE和Spring这样的依赖注入框架中,Bean的生命周期管理是一个重要的特性。@PostConstruct和@PreDestroy注解是Java EE(JSR-250)标准的一部分,为生命周期管理提供了标准化的解决方案。这使得开发者可以在不同的框架中使用相同的注解,而不用依赖于特定框架的API。3. 提高代码的可读性和可维护性@PostConstruct注解可以将初始化逻辑从构造函数中分离出来,使代码更具可读性和可维护性。开发者可以更清晰地看到哪些方法是用于初始化的,哪些方法是用于业务逻辑的。这种分离还可以使单元测试更加容易,因为初始化逻辑可以被单独测试。4. 与依赖注入框架的结合在依赖注入框架(如Spring)中,Bean的依赖关系是由容器管理的。在Bean实例化和依赖注入完成之后,框架会自动调用 @PostConstruct方法。这种机制确保了所有依赖都已准备就绪,开发者可以安全地在 @PostConstruct方法中进行进一步的初始化操作。生命周期的详细介绍Bean实例化:Spring容器根据配置文件或注解扫描创建Bean实例。依赖注入:Spring容器进行依赖注入,将所需的依赖对象注入到Bean中。调用@PostConstruct方法:依赖注入完成后,Spring容器会调用标注了@PostConstruct的方法。这个方法通常用于执行初始化操作,比如配置资源、进行检查或执行一些准备工作。Bean可用:在调用了@PostConstruct方法后,Bean被标记为可用,并准备好接受请求或执行其预定的功能。Bean销毁(可选,使用@PreDestroy):当Spring容器关闭时,可以通过@PreDestroy注解来指定一个方法,在Bean被销毁之前执行清理工作。生命周期的代码示例import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import org.springframework.stereotype.Component; @Component public class MyBean { // 构造方法 public MyBean() { System.out.println("MyBean实例化"); } // @PostConstruct注解的方法 @PostConstruct public void init() { System.out.println("MyBean初始化 - @PostConstruct方法调用"); // 这里可以执行一些初始化操作 } // @PreDestroy注解的方法 @PreDestroy public void cleanup() { System.out.println("MyBean销毁之前 - @PreDestroy方法调用"); // 这里可以执行一些清理操作 } // 其他业务方法 public void performTask() { System.out.println("MyBean执行任务"); } }关键点总结依赖注入后执行:@PostConstruct方法在Bean的依赖注入完成之后执行,确保所有依赖都已经准备就绪。只执行一次:@PostConstruct方法在Bean的整个生命周期中只会执行一次。用于初始化逻辑:适合用来执行一些初始化逻辑,比如检查配置、初始化资源、启动辅助线程等。与Spring结合:@PostConstruct是JavaEE规范中的一部分,但在Spring中也得到了广泛应用,主要用于简化Bean的初始化工作。注意事项只适用于单个方法:一个类中只能有一个 @PostConstruct方法。方法签名:@PostConstruct方法不能有任何参数,也不能有返回值。异常处理:@PostConstruct方法如果抛出异常,会阻止Bean的创建,Spring容器会在启动时抛出异常。转载自https://www.cnblogs.com/hyg0513/p/18314560
-
扑克算牌公式及Java和C++软件制作要实现一个扑克的算牌公式,并开发相应的Java和C++程序,首先需要定义一些基本的步骤和概念。具体来说,我们需要:定义扑克数据结构:表示扑克牌和其属性。历史数据导入:从外部文件或数据库中导入历史数据。概率计算:基于历史数据计算剩余牌的出现次数和概率。界面及交互:提供用户接口来发牌并显示结果。1. 数据结构定义我们用简单的数据结构来表示扑克牌。在Java和C++中,可以分别使用类和结构体来实现。Java代码public class Card { public enum Suit {HEARTS, DIAMONDS, CLUBS, SPADES} private Suit suit; private int value; public Card(Suit suit, int value) { this.suit = suit; this.value = value; } public Suit getSuit() { return suit; } public int getValue() { return value; } @Override public String toString() { return suit.toString() + value; } }C++代码#include <string> #include <iostream> struct Card { enum Suit { HEARTS, DIAMONDS, CLUBS, SPADES } suit; int value; std::string toString() const { static const char* suitNames[] = {"Hearts", "Diamonds", "Clubs", "Spades"}; return suitNames[suit] + std::to_string(value); } };2. 历史数据导入假设历史数据存储在一个CSV文件中,我们可以编写函数来读取这些数据。Java代码import java.io.*; import java.util.*; public class HistoryData { public static List<List<Card>> loadHistory(String filename) throws IOException { List<List<Card>> history = new ArrayList<>(); BufferedReader br = new BufferedReader(new FileReader(filename)); String line; while ((line = br.readLine()) != null) { String[] parts = line.split(","); List<Card> hand = new ArrayList<>(); for (String part : parts) { String[] cardParts = part.split(" "); Card.Suit suit = Card.Suit.valueOf(cardParts[0].toUpperCase()); int value = Integer.parseInt(cardParts[1]); hand.add(new Card(suit, value)); } history.add(hand); } br.close(); return history; } }C++代码#include <vector> #include <fstream> #include <sstream> #include <iostream> std::vector<std::vector<Card>> loadHistory(const std::string& filename) { std::vector<std::vector<Card>> history; std::ifstream file(filename); std::string line; while (std::getline(file, line)) { std::istringstream ss(line); std::vector<Card> hand; std::string part; while (std::getline(ss, part, ',')) { std::istringstream cardStream(part); std::string suitStr; int value; cardStream >> suitStr >> value; Card::Suit suit; if (suitStr == "Hearts") suit = Card::HEARTS; else if (suitStr == "Diamonds") suit = Card::DIAMONDS; else if (suitStr == "Clubs") suit = Card::CLUBS; else if (suitStr == "Spades") suit = Card::SPADES; hand.push_back({suit, value}); } history.push_back(hand); } return history; }3. 概率计算基于导入的历史数据进行概率计算。Java代码import java.util.HashMap; import java.util.List; import java.util.Map; public class ProbabilityCalculator { public static Map<Card, Double> calculateProbabilities(List<List<Card>> history, List<Card> givenCards) { Map<Card, Integer> counts = new HashMap<>(); for (List<Card> hand : history) { boolean match = true; for (Card card : givenCards) { if (!hand.contains(card)) { match = false; break; } } if (match) { for (Card card : hand) { counts.put(card, counts.getOrDefault(card, 0) + 1); } } } int totalHands = history.size(); Map<Card, Double> probabilities = new HashMap<>(); for (Map.Entry<Card, Integer> entry : counts.entrySet()) { probabilities.put(entry.getKey(), entry.getValue() / (double) totalHands); } return probabilities; } }C++代码#include <unordered_map> #include <vector> std::unordered_map<Card, double, CardHash> calculateProbabilities( const std::vector<std::vector<Card>>& history, const std::vector<Card>& givenCards) { std::unordered_map<Card, int, CardHash> counts; int totalHands = 0; for (const auto& hand : history) { bool match = true; for (const auto& card : givenCards) { if (std::find(hand.begin(), hand.end(), card) == hand.end()) { match = false; break; } } if (match) { totalHands++; for (const auto& card : hand) { counts[card]++; } } } std::unordered_map<Card, double, CardHash> probabilities; for (const auto& entry : counts) { probabilities[entry.first] = entry.second / static_cast<double>(totalHands); } return probabilities; }4. 主程序及交互将所有部分整合到主程序中,并提供交互界面。Java代码import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Scanner; public class Main { public static void main(String[] args) throws IOException { Scanner scanner = new Scanner(System.in); System.out.println("Enter the path to the historical data file:"); String filepath = scanner.nextLine(); List<List<Card>> history = HistoryData.loadHistory(filepath); System.out.println("Enter the three cards (e.g., 'HEARTS 2', 'CLUBS 5', 'HEARTS 10'):"); List<Card> givenCards = new ArrayList<>(); for (int i = 0; i < 3; i++) { String input = scanner.nextLine(); String[] parts = input.split(" "); Card.Suit suit = Card.Suit.valueOf(parts[0].toUpperCase()); int value = Integer.parseInt(parts[1]); givenCards.add(new Card(suit, value)); } Map<Card, Double> probabilities = ProbabilityCalculator.calculateProbabilities(history, givenCards); System.out.println("Probabilities of remaining cards:"); for (Map.Entry<Card, Double> entry : probabilities.entrySet()) { System.out.printf("%s: %.2f%%\n", entry.getKey(), entry.getValue() * 100); } } }C++代码#include <iostream> #include <vector> #include <unordered_map> int main() { std::cout << "Enter the path to the historical data file:" << std::endl; std::string filepath; std::cin >> filepath; auto history = loadHistory(filepath); std::cout << "Enter the three cards (e.g., 'HEARTS 2', 'CLUBS 5', 'HEARTS 10'):" << std::endl; std::vector<Card> givenCards; for (int i = 0; i < 3; ++i) { std::string suitStr; int value; std::cin >> suitStr >> value; Card::Suit suit; if (suitStr == "Hearts") suit = Card::HEARTS; else if (suitStr == "Diamonds") suit = Card::DIAMONDS; else if (suitStr == "Clubs") suit = Card::CLUBS; else if (suitStr == "Spades") suit = Card::SPADES; givenCards.push_back({suit, value}); } auto probabilities = calculateProbabilities(history, givenCards); std::cout << "Probabilities of remaining cards:" << std::endl; for (const auto& entry : probabilities) { std::cout << entry.first.toString() << ": " << entry.second * 100 << "%" << std::endl; } return 0; }总结通过上述步骤,我们实现了一个扑克算牌工具,可以根据给定的三张牌计算后续牌的出现概率。该工具使用Java和C++分别实现了数据结构、历史数据导入、概率计算以及用户交互。
上滑加载中
推荐直播
-
华为云码道Agent集成与鸿蒙实战2026/08/11 周二 19:00-21:00
王一男-华为云码道产品规划专家;李炎-华为云码道产品专家;彭江敏-华为云鸿蒙端云一体化开发专家
本次直播带你解读华为云码道7月份产品新特性、新功能。更有专家演示码道Agent Space × 钉钉机器集成实战,从0到1打通消息通道;码道鸿蒙端云一体化实战,快速搭建员工签到系统。
回顾中 -
华为云开发者AI素养直播课·第五期2026/09/04 周五 16:00-18:00
林华鼎-华为云AI开发者运营负责人;蒋春阳-华为云AI开发者案例开发专家
本期直播内容: AI工具体验营 · 第5-8课连讲。Agent-Team 多智能体协作完成毕业设计实践
回顾中 -
华为云开发者AI素养ClassRoom·第六期2026/09/08 周二 19:00-20:00
樊渊-2026华为软件挑战赛冠军
高手来了:看软挑高手解析二维排样问题—从工业难题到算法突破
回顾中
热门标签