当前位置:   article > 正文

SpringBoot 2.6.3 + ElasticSearch7.12.1 - SpringData 开发指南_spring-boot-starter-data-elasticsearch

spring-boot-starter-data-elasticsearch

目录

一、SpringData ElasticSearch

1.1、环境配置

1.2、创建实体类

1.3、ElasticsearchRestTemplate 的使用

1.3.1、创建索引 设置映射

1.3.2、创建索引映射注意事项(必看)

1.3.3、简单的增删改查

1.3.4、搜索

1.4、ElasticsearchRepository

1.4.1、使用方式

1.4.2、简单的增删改查

1.4.3、分页排序查询


一、SpringData ElasticSearch


1.1、环境配置

a)依赖如下:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
  4. </dependency>

b)配置文件如下:

  1. spring:
  2. elasticsearch:
  3. uris: env-base:9200

1.2、创建实体类

a)简单结构如下(后续实例,围绕此结构展开):

  1. import org.springframework.data.annotation.Id
  2. import org.springframework.data.elasticsearch.annotations.Document
  3. import org.springframework.data.elasticsearch.annotations.Field
  4. import org.springframework.data.elasticsearch.annotations.FieldType
  5. /**
  6. * @shards: 主分片数量
  7. * @replicas: 副本分片数量
  8. */
  9. @Document(indexName = "album_info", shards = 1, replicas = 0)
  10. data class AlbumInfoDo(
  11. /**
  12. * @Id: 表示文档中的主键,并且会在保存在 ElasticSearch 数据结构中 {"id": "", "userId": "", "title": ""}
  13. */
  14. @Id
  15. @Field(type = FieldType.Keyword)
  16. val id: Long? = null,
  17. /**
  18. * @Field: 描述 Java 类型中的属性映射
  19. * - name: 对应 ES 索引中的字段名. 默认和属性同名
  20. * - type: 对应字段类型,默认是 FieldType.Auto (会根据我们数据类型自动进行定义),但是建议主动定义,避免导致错误映射
  21. * - index: 是否创建索引. text 类型创建倒排索引,其他类型创建正排索引. 默认是 true
  22. * - analyzer: 分词器名称. 中文我们一般都使用 ik 分词器(ik分词器有 ik_smart 和 ik_max_word)
  23. */
  24. @Field(name = "user_id", type = FieldType.Long)
  25. val userId: Long,
  26. @Field(type = FieldType.Text, analyzer = "ik_max_word")
  27. val title: String,
  28. @Field(type = FieldType.Text, analyzer = "ik_smart")
  29. val content: String,
  30. )

b)复杂嵌套结构如下:

  1. import org.springframework.data.annotation.Id
  2. import org.springframework.data.elasticsearch.annotations.Document
  3. import org.springframework.data.elasticsearch.annotations.Field
  4. import org.springframework.data.elasticsearch.annotations.FieldType
  5. @Document(indexName = "album_list")
  6. data class AlbumListDo(
  7. @Id
  8. @Field(type = FieldType.Keyword)
  9. var id: Long,
  10. @Field(type = FieldType.Nested) // 表示一个嵌套结构
  11. var userinfo: UserInfoSimp,
  12. @Field(type = FieldType.Text, analyzer = "ik_max_word")
  13. var title: String,
  14. @Field(type = FieldType.Text, analyzer = "ik_smart")
  15. var content: String,
  16. @Field(type = FieldType.Nested) // 表示一个嵌套结构
  17. var photos: List<AlbumPhotoSimp>,
  18. )
  19. data class UserInfoSimp(
  20. @Field(type = FieldType.Long)
  21. val userId: Long,
  22. @Field(type = FieldType.Text, analyzer = "ik_max_word")
  23. val username: String,
  24. @Field(type = FieldType.Keyword, index = false)
  25. val avatar: String,
  26. )
  27. data class AlbumPhotoSimp(
  28. @Field(type = FieldType.Integer, index = false)
  29. val sort: Int,
  30. @Field(type = FieldType.Keyword, index = false)
  31. val photo: String,
  32. )

对于一个小型系统来说,一般也不会创建这种复杂程度的文档,因为会涉及到很多一致性问题, 需要通过大量的 mq 进行同步,给系统带来一定的开销. 

因此,一般会将需要进行模糊查询的字段存 Document 中(es 就擅长这个),而其他数据则可以在 Document 中以 id 的形式进行存储.   这样就既可以借助 es 高效的模糊查询能力,也能减少为保证一致性而带来的系统开销.  从 es 中查到数据后,再通过其他表的 id 从数据库中拿数据即可(这点开销,相对于从大量数据的数据库中进行 like 查询,几乎可以忽略).

1.3、ElasticsearchRestTemplate 的使用

1.3.1、创建索引 设置映射

  1. @SpringBootTest(classes = [DataEsApplication::class])
  2. class DataEsApplicationTests {
  3. @Resource private lateinit var elasticsearchTemplate: ElasticsearchRestTemplate
  4. @Test
  5. fun test1() {
  6. //创建索引
  7. elasticsearchTemplate.indexOps(AlbumInfoDo::class.java).create()
  8. //设置映射
  9. elasticsearchTemplate.indexOps(AlbumInfoDo::class.java).putMapping(
  10. elasticsearchTemplate.indexOps(AlbumInfoDo::class.java).createMapping()
  11. )
  12. }
  13. }

效果如下:

1.3.2、创建索引映射注意事项(必看)

a)在没有创建索引库和映射的情况下,也可以直接向 es 库中插入数据,如下代码:

  1. @Test
  2. fun test1() {
  3. val o = AlbumListDo(
  4. id = 1,
  5. userinfo = UserInfoSimp(
  6. userId = 1,
  7. username = "cyk",
  8. avatar = "http:photo1.com"
  9. ),
  10. title = "天气很好的一天",
  11. content = "早上起来,我要好好学习,然去公园散步~",
  12. photos = listOf(
  13. AlbumPhotoSimp(1, "www.photo1"),
  14. AlbumPhotoSimp(2, "www.photo2")
  15. )
  16. )
  17. val result = esTemplate.save(o)
  18. println(result)
  19. }

b)即使上述代码中 AlbumListDo 中有各种注解标记,但是不会生效!!! es 会根据插入的数据,自动转化数据结构(无视你的注解).

c)因此,一定要先创建索引库和映射,再进行数据插入!

1.3.3、简单的增删改查

  1. /**
  2. * 更新和添加都是这样
  3. * 更新的时候会根据 id 进行覆盖
  4. */
  5. @Test
  6. fun testSave() {
  7. //保存单条数据
  8. val a1 = AlbumInfoDo(
  9. id = 1,
  10. userId = 10000,
  11. title = "今天天气真好",
  12. content = "学习完之后,我要出去好好玩"
  13. )
  14. val result = elasticsearchTemplate.save(a1)
  15. println(result)
  16. //保存多条数据
  17. val list = listOf(
  18. AlbumInfoDo(2, 10000, "西安六号线避雷", "前俯后仰。他就一直在那前后动。他背后是我朋友,我让他不要挤了,他直接就急了,开始故意很大力的挤来挤去。"),
  19. AlbumInfoDo(3, 10000, "字节跳动快上车~", "#内推 #字节跳动内推 #互联网"),
  20. AlbumInfoDo(4, 10000, "连王思聪也变得低调老实了", "如今的王思聪,不仅交女友的质量下降,在网上也不再像以前那样随意喷这喷那。显然,资金的紧张让他低调了许多")
  21. )
  22. val resultList = elasticsearchTemplate.save(list)
  23. resultList.forEach(::println)
  24. }
  25. @Test
  26. fun testDelete() {
  27. //根据主键删除
  28. elasticsearchTemplate.delete("1", AlbumInfoDo::class.java)
  29. }
  30. @Test
  31. fun testGet() {
  32. val result = elasticsearchTemplate.get("1", AlbumInfoDo::class.java)
  33. println(result)
  34. }

补充一个修改:

  1. override fun update(msg: UpdateAlbumInfoMsg): Int {
  2. val query = UpdateQuery.builder(msg.albumId.toString()) //指定修改的文档 id
  3. .withDocument(org.springframework.data.elasticsearch.core.document.Document.create() //指定修改字段
  4. .append("title", msg.title)
  5. .append("content", msg.content)
  6. .append("ut_time", msg.utTime)
  7. )
  8. .build()
  9. val result = restTemplate.update(query, IndexCoordinates.of("album_doc")).result
  10. return result.ordinal
  11. }

1.3.4、搜索

a)一般搜索

  1. import org.cyk.dataes.model.AlbumInfoDo
  2. import org.elasticsearch.index.query.QueryBuilders
  3. import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder
  4. import org.junit.jupiter.api.Test
  5. import org.springframework.boot.test.context.SpringBootTest
  6. import org.springframework.data.domain.PageRequest
  7. import org.springframework.data.domain.Sort
  8. import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate
  9. import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder
  10. import javax.annotation.Resource
  11. @SpringBootTest(classes = [DataEsApplication::class])
  12. class TemplateTests {
  13. @Resource private lateinit var elasticsearchTemplate: ElasticsearchRestTemplate
  14. /**
  15. * 全文检索查询(match_all)
  16. */
  17. @Test
  18. fun testMatchAllQuery() {
  19. val query = NativeSearchQueryBuilder()
  20. .withQuery(QueryBuilders.matchAllQuery())
  21. .build()
  22. val hits = elasticsearchTemplate.search(query, AlbumInfoDo::class.java)
  23. println("总数为: ${hits.totalHits}")
  24. hits.forEach { println(it.content) }
  25. }
  26. /**
  27. * 全文检索查询(match)
  28. */
  29. @Test
  30. fun testMatchQuery() {
  31. val query = NativeSearchQueryBuilder()
  32. .withQuery(QueryBuilders.matchQuery("title", "天气"))
  33. .build()
  34. val hits = elasticsearchTemplate.search(query, AlbumInfoDo::class.java)
  35. hits.forEach { println(it.content) }
  36. }
  37. /**
  38. * 精确查询(term)
  39. */
  40. @Test
  41. fun testTerm() {
  42. val query = NativeSearchQueryBuilder()
  43. .withQuery(QueryBuilders.termQuery("user_id", 10001))
  44. .build()
  45. val hits = elasticsearchTemplate.search(query, AlbumInfoDo::class.java)
  46. hits.forEach { println(it.content) }
  47. }
  48. /**
  49. * 范围查询
  50. */
  51. @Test
  52. fun testRangeQuery() {
  53. val query = NativeSearchQueryBuilder()
  54. .withQuery(QueryBuilders.rangeQuery("id").gte(1).lt(4))
  55. .build()
  56. val hits = elasticsearchTemplate.search(query, AlbumInfoDo::class.java)
  57. hits.forEach { println(it.content) }
  58. }
  59. /**
  60. * 复合查询(bool)
  61. */
  62. @Test
  63. fun testBoolQuery() {
  64. val boolQuery = QueryBuilders.boolQuery()
  65. //必要条件: query.must 得到一个集合
  66. val mustList = boolQuery.must()
  67. mustList.add(QueryBuilders.rangeQuery("user_id").gte(10000).lt(10003))
  68. //其他的搜索条件集合的获取方式类似
  69. val mustNotList = boolQuery.mustNot()
  70. val should = boolQuery.should()
  71. //当然,还有一种简化的写法,如下,下述代码相当于 query.should().add(QueryBuilders.matchAllQuery())
  72. boolQuery.should(QueryBuilders.matchAllQuery())
  73. val query = NativeSearchQueryBuilder()
  74. .withQuery(boolQuery)
  75. .build()
  76. val hits = elasticsearchTemplate.search(query, AlbumInfoDo::class.java)
  77. hits.forEach { println(it.content) }
  78. }
  79. /**
  80. * 排序和分页
  81. */
  82. @Test
  83. fun testSortAndPage() {
  84. val query = NativeSearchQueryBuilder()
  85. .withQuery(QueryBuilders.matchAllQuery())
  86. .withPageable(
  87. PageRequest.of(0, 3) //参数一: 页码(从 0 开始),size 每页查询多少条数据
  88. .withSort(Sort.by(Sort.Order.desc("id"))) //根据 id 降序排序(这里也可以根据多个字段进行升序降序)
  89. ).build()
  90. val hits = elasticsearchTemplate.search(query, AlbumInfoDo::class.java)
  91. hits.forEach{ println(it.content) }
  92. }
  93. /**
  94. * 高亮搜索
  95. */
  96. @Test
  97. fun testHighLight() {
  98. //定义高亮字段
  99. val field = HighlightBuilder.Field("title")
  100. //a) 前缀标签
  101. field.preTags("<span style='color:red'>")
  102. //b) 后缀标签
  103. field.postTags("</span>")
  104. //c) 高亮的片段长度(多少个几个字需要高亮,一般会设置的大一些,让匹配到的字段尽量都高亮)
  105. field.fragmentSize(10)
  106. //d) 高亮片段的数量
  107. field.numOfFragments(1)
  108. // withHighlightFields(Field... 高亮字段数组)
  109. val query = NativeSearchQueryBuilder()
  110. .withQuery(QueryBuilders.matchQuery("title", "天气"))
  111. .withHighlightFields(field)
  112. .build()
  113. val hits = elasticsearchTemplate.search(query, AlbumInfoDo::class.java)
  114. //注意,hit.content 中本身是没有高亮数据的,因此这里需要手工处理
  115. hits.forEach {
  116. val result = it.content
  117. //根据高亮字段名称,获取高亮数据集合,结果是 List<String>
  118. val hList = it.getHighlightField("title")
  119. if(hList.size > 0) {
  120. //有高亮数据
  121. result.title = hList.get(0)
  122. }
  123. println(result)
  124. }
  125. }
  126. }

b)基于 completionSuggestion 实现自动补全

data 如下:

  1. @Document(indexName = "album_doc")
  2. data class AlbumDocDo (
  3. @Id
  4. @Field(type = FieldType.Keyword)
  5. val id: Long,
  6. @Field(name = "user_id", type = FieldType.Long)
  7. val userId: Long,
  8. @Field(type = FieldType.Text, analyzer = "ik_max_word", copyTo = ["suggestion"])
  9. val title: String,
  10. @Field(type = FieldType.Text, analyzer = "ik_smart")
  11. val content: String,
  12. @Field(name = "ct_time", type = FieldType.Long)
  13. val ctTime: Long,
  14. @Field(name = "ut_time", type = FieldType.Long)
  15. val utTime: Long,
  16. @CompletionField(maxInputLength = 100, analyzer = "ik_max_word", searchAnalyzer = "ik_max_word")
  17. val suggestion: Completion? = null,
  18. )

自动补全的字段必须是 completion 类型.  自动补全字段为 title,将他 copy 到了 suggestion 字段,实现自动补全. 

  1. override fun suggestTexts(o: AlbumSuggestDto): List<String> {
  2. val suggest = SuggestBuilder().addSuggestion(
  3. "title_suggest", //自定义补全名
  4. SuggestBuilders.completionSuggestion("suggestion") //自动补全时需要查询的字段
  5. .prefix(o.text) //要进行补全的值(用户搜索框中输入的)
  6. .skipDuplicates(true) //如果查询时有重复的词条,是否自动跳过(true 为跳过)
  7. .size(o.limit) //获取多少个结果
  8. )
  9. val query = NativeSearchQueryBuilder()
  10. .withSuggestBuilder(suggest)
  11. .build()
  12. val hits = restTemplate.search(query, AlbumDocDo::class.java)
  13. val suggests = hits.suggest
  14. ?.getSuggestion("title_suggest") //根据自定义补全名获取对应的补全结果集
  15. ?.entries?.get(0) //结果集(记录了根据什么前缀(prefix)进行自动补全,补全的结果对象...)
  16. ?.options?.map(::map) ?: emptyList() //补全的结果对象(其中 text 就是自动补全的结果)
  17. return suggests
  18. }
  19. private fun map(hit: Suggest.Suggestion.Entry.Option): String {
  20. return hit.text
  21. }

例如需要自动补全 "c",result 结构如下

1.4、ElasticsearchRepository

1.4.1、使用方式

这个东西就跟 JPA 的使用方式一样,只不过高版本的 SpringData Elasticsearch 没有给 ElasticsearchRepository 接口提供复杂搜索查询,建议还是使用 ElasticsearchTemplate

自定义一个接口, 继承  ElasticsearchRepository 接口,如下:

  1. import org.cyk.dataes.model.AlbumInfoDo
  2. import org.springframework.data.elasticsearch.repository.ElasticsearchRepository
  3. interface AlbumInfoRepo: ElasticsearchRepository<AlbumInfoDo, Long> //<实体类,主键类型>

1.4.2、简单的增删改查

  1. import org.cyk.dataes.model.AlbumInfoDo
  2. import org.cyk.dataes.service.AlbumInfoESRepo
  3. import org.junit.jupiter.api.Test
  4. import org.springframework.boot.test.context.SpringBootTest
  5. import javax.annotation.Resource
  6. @SpringBootTest(classes = [DataEsApplication::class])
  7. class RepoTests {
  8. @Resource private lateinit var albumInfoESRepo: AlbumInfoESRepo
  9. @Test
  10. fun testSave() {
  11. //增加单个
  12. val a = AlbumInfoDo(1, 10000, "今天天气真好", "学习完之后,我要出去好好玩")
  13. val result = albumInfoESRepo.save(a)
  14. println(result)
  15. //批量新增
  16. val list = listOf(
  17. AlbumInfoDo(2, 10000, "西安六号线避雷", "前俯后仰。他就一直在那前后动。他背后是我朋友,我让他不要挤了,他直接就急了,开始故意很大力的挤来挤去。"),
  18. AlbumInfoDo(3, 10000, "字节跳动快上车~", "#内推 #字节跳动内推 #互联网"),
  19. AlbumInfoDo(4, 10000, "连王思聪也变得低调老实了", "如今的王思聪,不仅交女友的质量下降,在网上也不再像以前那样随意喷这喷那。显然,资金的紧张让他低调了许多")
  20. )
  21. val resultList = albumInfoESRepo.saveAll(list)
  22. resultList.forEach(::println)
  23. }
  24. @Test
  25. fun testDel() {
  26. //根据 id 删除
  27. albumInfoESRepo.deleteById(1)
  28. //删除所有
  29. albumInfoESRepo.deleteAll()
  30. }
  31. @Test
  32. fun testFind() {
  33. //查询所有
  34. val resultList = albumInfoESRepo.findAll()
  35. resultList.forEach(::println)
  36. //根据 id 查询
  37. val result = albumInfoESRepo.findById(1)
  38. println(result.get())
  39. }
  40. }

1.4.3、分页排序查询

  1. import org.cyk.dataes.service.AlbumInfoESRepo
  2. import org.junit.jupiter.api.Test
  3. import org.springframework.boot.test.context.SpringBootTest
  4. import org.springframework.data.domain.PageRequest
  5. import org.springframework.data.domain.Sort
  6. import javax.annotation.Resource
  7. @SpringBootTest(classes = [DataEsApplication::class])
  8. class RepoTests2 {
  9. @Resource
  10. private lateinit var albumInfoESRepo: AlbumInfoESRepo
  11. @Test
  12. fun testFindPageAndSort() {
  13. //从 0 下标开始向后获取 3 个,并根据 id 降序排序
  14. val result = albumInfoESRepo.findAll(
  15. PageRequest.of(0, 3,
  16. Sort.by(Sort.Direction.DESC, "id"))
  17. )
  18. result.content.forEach(::println)
  19. }
  20. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/609085
推荐阅读
相关标签
  

闽ICP备14008679号