You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

README.md 18 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. # spring-boot-demo-elasticsearch
  2. > 此 demo 主要演示了 Spring Boot 如何集成 `spring-boot-starter-data-elasticsearch` 完成对 ElasticSearch 的高级使用技巧,包括创建索引、配置映射、删除索引、增删改查基本操作、复杂查询、高级查询、聚合查询等。
  3. ## 注意
  4. 作者编写本demo时,ElasticSearch版本为 `6.5.3`,使用 docker 运行,下面是所有步骤:
  5. 1. 下载镜像:`docker pull elasticsearch:6.5.3`
  6. 2. 运行容器:`docker run -d -p 9200:9200 -p 9300:9300 --name elasticsearch-6.5.3 elasticsearch:6.5.3`
  7. 3. 进入容器:`docker exec -it elasticsearch-6.5.3 /bin/bash`
  8. 4. 安装 ik 分词器:`./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.5.3/elasticsearch-analysis-ik-6.5.3.zip`
  9. 5. 修改 es 配置文件:`vi ./config/elasticsearch.yml
  10. ```yaml
  11. cluster.name: "docker-cluster"
  12. network.host: 0.0.0.0
  13. # minimum_master_nodes need to be explicitly set when bound on a public IP
  14. # set to 1 to allow single node clusters
  15. # Details: https://github.com/elastic/elasticsearch/pull/17288
  16. discovery.zen.minimum_master_nodes: 1
  17. # just for elasticsearch-head plugin
  18. http.cors.enabled: true
  19. http.cors.allow-origin: "*"
  20. ```
  21. 6. 退出容器:`exit`
  22. 7. 停止容器:`docker stop elasticsearch-6.5.3`
  23. 8. 启动容器:`docker start elasticsearch-6.5.3`
  24. ## pom.xml
  25. ```xml
  26. <?xml version="1.0" encoding="UTF-8"?>
  27. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  28. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  29. <modelVersion>4.0.0</modelVersion>
  30. <artifactId>spring-boot-demo-elasticsearch</artifactId>
  31. <version>1.0.0-SNAPSHOT</version>
  32. <packaging>jar</packaging>
  33. <name>spring-boot-demo-elasticsearch</name>
  34. <description>Demo project for Spring Boot</description>
  35. <parent>
  36. <groupId>com.xkcoding</groupId>
  37. <artifactId>spring-boot-demo</artifactId>
  38. <version>1.0.0-SNAPSHOT</version>
  39. </parent>
  40. <properties>
  41. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  42. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  43. <java.version>1.8</java.version>
  44. </properties>
  45. <dependencies>
  46. <dependency>
  47. <groupId>org.springframework.boot</groupId>
  48. <artifactId>spring-boot-starter</artifactId>
  49. </dependency>
  50. <dependency>
  51. <groupId>org.springframework.boot</groupId>
  52. <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
  53. </dependency>
  54. <dependency>
  55. <groupId>org.springframework.boot</groupId>
  56. <artifactId>spring-boot-starter-test</artifactId>
  57. <scope>test</scope>
  58. </dependency>
  59. <dependency>
  60. <groupId>org.projectlombok</groupId>
  61. <artifactId>lombok</artifactId>
  62. <optional>true</optional>
  63. </dependency>
  64. <dependency>
  65. <groupId>cn.hutool</groupId>
  66. <artifactId>hutool-all</artifactId>
  67. </dependency>
  68. <dependency>
  69. <groupId>com.google.guava</groupId>
  70. <artifactId>guava</artifactId>
  71. </dependency>
  72. </dependencies>
  73. <build>
  74. <finalName>spring-boot-demo-elasticsearch</finalName>
  75. <plugins>
  76. <plugin>
  77. <groupId>org.springframework.boot</groupId>
  78. <artifactId>spring-boot-maven-plugin</artifactId>
  79. </plugin>
  80. </plugins>
  81. </build>
  82. </project>
  83. ```
  84. ## Person.java
  85. > 实体类
  86. >
  87. > @Document 注解主要声明索引名、类型名、分片数量和备份数量
  88. >
  89. > @Field 注解主要声明字段对应ES的类型
  90. ```java
  91. /**
  92. * <p>
  93. * 用户实体类
  94. * </p>
  95. *
  96. * @author yangkai.shen
  97. * @date Created in 2018-12-20 17:29
  98. */
  99. @Document(indexName = EsConsts.INDEX_NAME, type = EsConsts.TYPE_NAME, shards = 1, replicas = 0)
  100. @Data
  101. @NoArgsConstructor
  102. @AllArgsConstructor
  103. public class Person {
  104. /**
  105. * 主键
  106. */
  107. @Id
  108. private Long id;
  109. /**
  110. * 名字
  111. */
  112. @Field(type = FieldType.Keyword)
  113. private String name;
  114. /**
  115. * 国家
  116. */
  117. @Field(type = FieldType.Keyword)
  118. private String country;
  119. /**
  120. * 年龄
  121. */
  122. @Field(type = FieldType.Integer)
  123. private Integer age;
  124. /**
  125. * 生日
  126. */
  127. @Field(type = FieldType.Date)
  128. private Date birthday;
  129. /**
  130. * 介绍
  131. */
  132. @Field(type = FieldType.Text, analyzer = "ik_smart")
  133. private String remark;
  134. }
  135. ```
  136. ## PersonRepository.java
  137. ```java
  138. /**
  139. * <p>
  140. * 用户持久层
  141. * </p>
  142. *
  143. * @author yangkai.shen
  144. * @date Created in 2018-12-20 19:00
  145. */
  146. public interface PersonRepository extends ElasticsearchRepository<Person, Long> {
  147. /**
  148. * 根据年龄区间查询
  149. *
  150. * @param min 最小值
  151. * @param max 最大值
  152. * @return 满足条件的用户列表
  153. */
  154. List<Person> findByAgeBetween(Integer min, Integer max);
  155. }
  156. ```
  157. ## TemplateTest.java
  158. > 主要测试创建索引、映射配置、删除索引
  159. ```java
  160. /**
  161. * <p>
  162. * 测试 ElasticTemplate 的创建/删除
  163. * </p>
  164. *
  165. * @author yangkai.shen
  166. * @date Created in 2018-12-20 17:46
  167. */
  168. public class TemplateTest extends SpringBootDemoElasticsearchApplicationTests {
  169. @Autowired
  170. private ElasticsearchTemplate esTemplate;
  171. /**
  172. * 测试 ElasticTemplate 创建 index
  173. */
  174. @Test
  175. public void testCreateIndex() {
  176. // 创建索引,会根据Item类的@Document注解信息来创建
  177. esTemplate.createIndex(Person.class);
  178. // 配置映射,会根据Item类中的id、Field等字段来自动完成映射
  179. esTemplate.putMapping(Person.class);
  180. }
  181. /**
  182. * 测试 ElasticTemplate 删除 index
  183. */
  184. @Test
  185. public void testDeleteIndex() {
  186. esTemplate.deleteIndex(Person.class);
  187. }
  188. }
  189. ```
  190. ## PersonRepositoryTest.java
  191. > 主要功能,参见方法上方注释
  192. ```java
  193. /**
  194. * <p>
  195. * 测试 Repository 操作ES
  196. * </p>
  197. *
  198. * @author yangkai.shen
  199. * @date Created in 2018-12-20 19:03
  200. */
  201. @Slf4j
  202. public class PersonRepositoryTest extends SpringBootDemoElasticsearchApplicationTests {
  203. @Autowired
  204. private PersonRepository repo;
  205. /**
  206. * 测试新增
  207. */
  208. @Test
  209. public void save() {
  210. Person person = new Person(1L, "刘备", "蜀国", 18, DateUtil.parse("1990-01-02 03:04:05"), "刘备(161年-223年6月10日),即汉昭烈帝(221年-223年在位),又称先主,字玄德,东汉末年幽州涿郡涿县(今河北省涿州市)人,西汉中山靖王刘胜之后,三国时期蜀汉开国皇帝、政治家。\n刘备少年时拜卢植为师;早年颠沛流离,备尝艰辛,投靠过多个诸侯,曾参与镇压黄巾起义。先后率军救援北海相孔融、徐州牧陶谦等。陶谦病亡后,将徐州让与刘备。赤壁之战时,刘备与孙权联盟击败曹操,趁势夺取荆州。而后进取益州。于章武元年(221年)在成都称帝,国号汉,史称蜀或蜀汉。《三国志》评刘备的机权干略不及曹操,但其弘毅宽厚,知人待士,百折不挠,终成帝业。刘备也称自己做事“每与操反,事乃成尔”。\n章武三年(223年),刘备病逝于白帝城,终年六十三岁,谥号昭烈皇帝,庙号烈祖,葬惠陵。后世有众多文艺作品以其为主角,在成都武侯祠有昭烈庙为纪念。");
  211. Person save = repo.save(person);
  212. log.info("【save】= {}", save);
  213. }
  214. /**
  215. * 测试批量新增
  216. */
  217. @Test
  218. public void saveList() {
  219. List<Person> personList = Lists.newArrayList();
  220. personList.add(new Person(2L, "曹操", "魏国", 20, DateUtil.parse("1988-01-02 03:04:05"), "曹操(155年-220年3月15日),字孟德,一名吉利,小字阿瞒,沛国谯县(今安徽亳州)人。东汉末年杰出的政治家、军事家、文学家、书法家,三国中曹魏政权的奠基人。\n曹操曾担任东汉丞相,后加封魏王,奠定了曹魏立国的基础。去世后谥号为武王。其子曹丕称帝后,追尊为武皇帝,庙号太祖。\n东汉末年,天下大乱,曹操以汉天子的名义征讨四方,对内消灭二袁、吕布、刘表、马超、韩遂等割据势力,对外降服南匈奴、乌桓、鲜卑等,统一了中国北方,并实行一系列政策恢复经济生产和社会秩序,扩大屯田、兴修水利、奖励农桑、重视手工业、安置流亡人口、实行“租调制”,从而使中原社会渐趋稳定、经济出现转机。黄河流域在曹操统治下,政治渐见清明,经济逐步恢复,阶级压迫稍有减轻,社会风气有所好转。曹操在汉朝的名义下所采取的一些措施具有积极作用。\n曹操军事上精通兵法,重贤爱才,为此不惜一切代价将看中的潜能分子收于麾下;生活上善诗歌,抒发自己的政治抱负,并反映汉末人民的苦难生活,气魄雄伟,慷慨悲凉;散文亦清峻整洁,开启并繁荣了建安文学,给后人留下了宝贵的精神财富,鲁迅评价其为“改造文章的祖师”。同时曹操也擅长书法,唐朝张怀瓘在《书断》将曹操的章草评为“妙品”。"));
  221. personList.add(new Person(3L, "孙权", "吴国", 19, DateUtil.parse("1989-01-02 03:04:05"), "孙权(182年-252年5月21日),字仲谋,吴郡富春(今浙江杭州富阳区)人。三国时代孙吴的建立者(229年-252年在位)。\n孙权的父亲孙坚和兄长孙策,在东汉末年群雄割据中打下了江东基业。建安五年(200年),孙策遇刺身亡,孙权继之掌事,成为一方诸侯。建安十三年(208年),与刘备建立孙刘联盟,并于赤壁之战中击败曹操,奠定三国鼎立的基础。建安二十四年(219年),孙权派吕蒙成功袭取刘备的荆州,使领土面积大大增加。\n黄武元年(222年),孙权被魏文帝曹丕册封为吴王,建立吴国。同年,在夷陵之战中大败刘备。黄龙元年(229年),在武昌正式称帝,国号吴,不久后迁都建业。孙权称帝后,设置农官,实行屯田,设置郡县,并继续剿抚山越,促进了江南经济的发展。在此基础上,他又多次派人出海。黄龙二年(230年),孙权派卫温、诸葛直抵达夷州。\n孙权晚年在继承人问题上反复无常,引致群下党争,朝局不稳。太元元年(252年)病逝,享年七十一岁,在位二十四年,谥号大皇帝,庙号太祖,葬于蒋陵。\n孙权亦善书,唐代张怀瓘在《书估》中将其书法列为第三等。"));
  222. personList.add(new Person(4L, "诸葛亮", "蜀国", 16, DateUtil.parse("1992-01-02 03:04:05"), "诸葛亮(181年-234年10月8日),字孔明,号卧龙,徐州琅琊阳都(今山东临沂市沂南县)人,三国时期蜀国丞相,杰出的政治家、军事家、外交家、文学家、书法家、发明家。\n早年随叔父诸葛玄到荆州,诸葛玄死后,诸葛亮就在襄阳隆中隐居。后刘备三顾茅庐请出诸葛亮,联孙抗曹,于赤壁之战大败曹军。形成三国鼎足之势,又夺占荆州。建安十六年(211年),攻取益州。继又击败曹军,夺得汉中。蜀章武元年(221年),刘备在成都建立蜀汉政权,诸葛亮被任命为丞相,主持朝政。蜀后主刘禅继位,诸葛亮被封为武乡侯,领益州牧。勤勉谨慎,大小政事必亲自处理,赏罚严明;与东吴联盟,改善和西南各族的关系;实行屯田政策,加强战备。前后六次北伐中原,多以粮尽无功。终因积劳成疾,于蜀建兴十二年(234年)病逝于五丈原(今陕西宝鸡岐山境内),享年54岁。刘禅追封其为忠武侯,后世常以武侯尊称诸葛亮。东晋政权因其军事才能特追封他为武兴王。\n诸葛亮散文代表作有《出师表》《诫子书》等。曾发明木牛流马、孔明灯等,并改造连弩,叫做诸葛连弩,可一弩十矢俱发。诸葛亮一生“鞠躬尽瘁、死而后已”,是中国传统文化中忠臣与智者的代表人物。"));
  223. Iterable<Person> people = repo.saveAll(personList);
  224. log.info("【people】= {}", people);
  225. }
  226. /**
  227. * 测试更新
  228. */
  229. @Test
  230. public void update() {
  231. repo.findById(1L).ifPresent(person -> {
  232. person.setRemark(person.getRemark() + "\n更新更新更新更新更新");
  233. Person save = repo.save(person);
  234. log.info("【save】= {}", save);
  235. });
  236. }
  237. /**
  238. * 测试删除
  239. */
  240. @Test
  241. public void delete() {
  242. // 主键删除
  243. repo.deleteById(1L);
  244. // 对象删除
  245. repo.findById(2L).ifPresent(person -> repo.delete(person));
  246. // 批量删除
  247. repo.deleteAll(repo.findAll());
  248. }
  249. /**
  250. * 测试普通查询,按生日倒序
  251. */
  252. @Test
  253. public void select() {
  254. repo.findAll(Sort.by(Sort.Direction.DESC, "birthday"))
  255. .forEach(person -> log.info("{} 生日: {}", person.getName(), DateUtil.formatDateTime(person.getBirthday())));
  256. }
  257. /**
  258. * 自定义查询,根据年龄范围查询
  259. */
  260. @Test
  261. public void customSelectRangeOfAge() {
  262. repo.findByAgeBetween(18, 19).forEach(person -> log.info("{} 年龄: {}", person.getName(), person.getAge()));
  263. }
  264. /**
  265. * 高级查询
  266. */
  267. @Test
  268. public void advanceSelect() {
  269. // QueryBuilders 提供了很多静态方法,可以实现大部分查询条件的封装
  270. MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("name", "孙权");
  271. log.info("【queryBuilder】= {}", queryBuilder.toString());
  272. repo.search(queryBuilder).forEach(person -> log.info("【person】= {}", person));
  273. }
  274. /**
  275. * 自定义高级查询
  276. */
  277. @Test
  278. public void customAdvanceSelect() {
  279. // 构造查询条件
  280. NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
  281. // 添加基本的分词条件
  282. queryBuilder.withQuery(QueryBuilders.matchQuery("remark", "东汉"));
  283. // 排序条件
  284. queryBuilder.withSort(SortBuilders.fieldSort("age").order(SortOrder.DESC));
  285. // 分页条件
  286. queryBuilder.withPageable(PageRequest.of(0, 2));
  287. Page<Person> people = repo.search(queryBuilder.build());
  288. log.info("【people】总条数 = {}", people.getTotalElements());
  289. log.info("【people】总页数 = {}", people.getTotalPages());
  290. people.forEach(person -> log.info("【person】= {},年龄 = {}", person.getName(), person.getAge()));
  291. }
  292. /**
  293. * 测试聚合,测试平均年龄
  294. */
  295. @Test
  296. public void agg() {
  297. // 构造查询条件
  298. NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
  299. // 不查询任何结果
  300. queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
  301. // 平均年龄
  302. queryBuilder.addAggregation(AggregationBuilders.avg("avg").field("age"));
  303. log.info("【queryBuilder】= {}", JSONUtil.toJsonStr(queryBuilder.build()));
  304. AggregatedPage<Person> people = (AggregatedPage<Person>) repo.search(queryBuilder.build());
  305. double avgAge = ((InternalAvg) people.getAggregation("avg")).getValue();
  306. log.info("【avgAge】= {}", avgAge);
  307. }
  308. /**
  309. * 测试高级聚合查询,每个国家的人有几个,每个国家的平均年龄是多少
  310. */
  311. @Test
  312. public void advanceAgg() {
  313. // 构造查询条件
  314. NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
  315. // 不查询任何结果
  316. queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
  317. // 1. 添加一个新的聚合,聚合类型为terms,聚合名称为country,聚合字段为age
  318. queryBuilder.addAggregation(AggregationBuilders.terms("country").field("country")
  319. // 2. 在国家聚合桶内进行嵌套聚合,求平均年龄
  320. .subAggregation(AggregationBuilders.avg("avg").field("age")));
  321. log.info("【queryBuilder】= {}", JSONUtil.toJsonStr(queryBuilder.build()));
  322. // 3. 查询
  323. AggregatedPage<Person> people = (AggregatedPage<Person>) repo.search(queryBuilder.build());
  324. // 4. 解析
  325. // 4.1. 从结果中取出名为 country 的那个聚合,因为是利用String类型字段来进行的term聚合,所以结果要强转为StringTerm类型
  326. StringTerms country = (StringTerms) people.getAggregation("country");
  327. // 4.2. 获取桶
  328. List<StringTerms.Bucket> buckets = country.getBuckets();
  329. for (StringTerms.Bucket bucket : buckets) {
  330. // 4.3. 获取桶中的key,即国家名称 4.4. 获取桶中的文档数量
  331. log.info("{} 总共有 {} 人", bucket.getKeyAsString(), bucket.getDocCount());
  332. // 4.5. 获取子聚合结果:
  333. InternalAvg avg = (InternalAvg) bucket.getAggregations().asMap().get("avg");
  334. log.info("平均年龄:{}", avg);
  335. }
  336. }
  337. }
  338. ```
  339. ## 参考
  340. 1. ElasticSearch 官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/6.x/getting-started.html
  341. 2. spring-data-elasticsearch 官方文档:https://docs.spring.io/spring-data/elasticsearch/docs/3.1.2.RELEASE/reference/html/

一个用来深度学习并实战 spring boot 的项目,目前总共包含 66 个集成demo,已经完成 55 个。