SpringData ElasticSearch - 简化开发,完美适配 Spring 生态

这篇具有很好参考价值的文章主要介绍了SpringData ElasticSearch - 简化开发,完美适配 Spring 生态。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一、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)依赖如下:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>

b)配置文件如下:

spring:
  elasticsearch:
    uris: env-base:9200

1.2、创建实体类

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

import org.springframework.data.annotation.Id
import org.springframework.data.elasticsearch.annotations.Document
import org.springframework.data.elasticsearch.annotations.Field
import org.springframework.data.elasticsearch.annotations.FieldType

/**
 *  @shards: 主分片数量
 *  @replicas: 副本分片数量
 */
@Document(indexName = "album_info", shards = 1, replicas = 0)
data class AlbumInfoDo(
    /**
     * @Id: 表示文档中的主键,并且会在保存在 ElasticSearch 数据结构中 {"id": "", "userId": "", "title": ""}
     */
    @Id
    @Field(type = FieldType.Keyword)
    val id: Long? = null,
    /**
     * @Field: 描述 Java 类型中的属性映射
     *      - name: 对应 ES 索引中的字段名. 默认和属性同名
     *      - type: 对应字段类型,默认是 FieldType.Auto (会根据我们数据类型自动进行定义),但是建议主动定义,避免导致错误映射
     *      - index: 是否创建索引. text 类型创建倒排索引,其他类型创建正排索引.  默认是 true
     *      - analyzer: 分词器名称.  中文我们一般都使用 ik 分词器(ik分词器有 ik_smart 和 ik_max_word)
     */
    @Field(name = "user_id", type = FieldType.Long)
    val userId: Long,
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    val title: String,
    @Field(type = FieldType.Text, analyzer = "ik_smart")
    val content: String,
)

b)复杂嵌套结构如下:

import org.springframework.data.annotation.Id
import org.springframework.data.elasticsearch.annotations.Document
import org.springframework.data.elasticsearch.annotations.Field
import org.springframework.data.elasticsearch.annotations.FieldType

@Document(indexName = "album_list")
data class AlbumListDo(
    @Id
    @Field(type = FieldType.Keyword)
    var id: Long,
    @Field(type = FieldType.Nested) // 表示一个嵌套结构
    var userinfo: UserInfoSimp,
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    var title: String,
    @Field(type = FieldType.Text, analyzer = "ik_smart")
    var content: String,
    @Field(type = FieldType.Nested) // 表示一个嵌套结构
    var photos: List<AlbumPhotoSimp>,
)

data class UserInfoSimp(
    @Field(type = FieldType.Long)
    val userId: Long,
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    val username: String,
    @Field(type = FieldType.Keyword, index = false)
    val avatar: String,
)

data class AlbumPhotoSimp(
    @Field(type = FieldType.Integer, index = false)
    val sort: Int,
    @Field(type = FieldType.Keyword, index = false)
    val photo: String,
)

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

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

1.3、ElasticsearchRestTemplate 的使用

1.3.1、创建索引 设置映射

@SpringBootTest(classes = [DataEsApplication::class])
class DataEsApplicationTests {

    @Resource private lateinit var elasticsearchTemplate: ElasticsearchRestTemplate

    @Test
    fun test1() {
        //创建索引
        elasticsearchTemplate.indexOps(AlbumInfoDo::class.java).create()
        //设置映射
        elasticsearchTemplate.indexOps(AlbumInfoDo::class.java).putMapping(
            elasticsearchTemplate.indexOps(AlbumInfoDo::class.java).createMapping()
        )
    }

}

效果如下:

org.springframework.data.elasticsearch.core.elasticsearchresttemplate#delete,ElasticSearch,elasticsearch,spring,jenkins

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

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

    @Test
    fun test1() {
        val o = AlbumListDo(
            id = 1,
            userinfo = UserInfoSimp(
                userId = 1,
                username = "cyk",
                avatar = "http:photo1.com"
            ),
            title = "天气很好的一天",
            content = "早上起来,我要好好学习,然去公园散步~",
            photos = listOf(
                AlbumPhotoSimp(1, "www.photo1"),
                AlbumPhotoSimp(2, "www.photo2")
            )
        )
        val result = esTemplate.save(o)
        println(result)
    }

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

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

1.3.3、简单的增删改查

    /**
     * 更新和添加都是这样
     * 更新的时候会根据 id 进行覆盖
     */
    @Test
    fun testSave() {
        //保存单条数据
        val a1 = AlbumInfoDo(
            id = 1,
            userId = 10000,
            title = "今天天气真好",
            content = "学习完之后,我要出去好好玩"
        )
        val result = elasticsearchTemplate.save(a1)
        println(result)
        //保存多条数据
        val list = listOf(
            AlbumInfoDo(2, 10000, "西安六号线避雷", "前俯后仰。他就一直在那前后动。他背后是我朋友,我让他不要挤了,他直接就急了,开始故意很大力的挤来挤去。"),
            AlbumInfoDo(3, 10000, "字节跳动快上车~", "#内推 #字节跳动内推 #互联网"),
            AlbumInfoDo(4, 10000, "连王思聪也变得低调老实了", "如今的王思聪,不仅交女友的质量下降,在网上也不再像以前那样随意喷这喷那。显然,资金的紧张让他低调了许多")
        )
        val resultList = elasticsearchTemplate.save(list)
        resultList.forEach(::println)
    }

    @Test
    fun testDelete() {
        //根据主键删除
        elasticsearchTemplate.delete("1", AlbumInfoDo::class.java)
    }

    @Test
    fun testGet() {
        val result = elasticsearchTemplate.get("1", AlbumInfoDo::class.java)
        println(result)
    }

补充一个修改:

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

1.3.4、搜索

a)一般搜索

import org.cyk.dataes.model.AlbumInfoDo
import org.elasticsearch.index.query.QueryBuilders
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Sort
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder
import javax.annotation.Resource

@SpringBootTest(classes = [DataEsApplication::class])
class TemplateTests {

    @Resource private lateinit var elasticsearchTemplate: ElasticsearchRestTemplate

    /**
     * 全文检索查询(match_all)
     */
    @Test
    fun testMatchAllQuery() {
        val query = NativeSearchQueryBuilder()
            .withQuery(QueryBuilders.matchAllQuery())
            .build()
        val hits = elasticsearchTemplate.search(query, AlbumInfoDo::class.java)
        println("总数为: ${hits.totalHits}")
        hits.forEach { println(it.content) }
    }

    /**
     * 全文检索查询(match)
     */
    @Test
    fun testMatchQuery() {
        val query = NativeSearchQueryBuilder()
            .withQuery(QueryBuilders.matchQuery("title", "天气"))
            .build()
        val hits = elasticsearchTemplate.search(query, AlbumInfoDo::class.java)
        hits.forEach { println(it.content) }
    }

    /**
     * 精确查询(term)
     */
    @Test
    fun testTerm() {
        val query = NativeSearchQueryBuilder()
            .withQuery(QueryBuilders.termQuery("user_id", 10001))
            .build()
        val hits = elasticsearchTemplate.search(query, AlbumInfoDo::class.java)
        hits.forEach { println(it.content) }
    }

    /**
     * 范围查询
     */
    @Test
    fun testRangeQuery() {
        val query = NativeSearchQueryBuilder()
            .withQuery(QueryBuilders.rangeQuery("id").gte(1).lt(4))
            .build()
        val hits = elasticsearchTemplate.search(query, AlbumInfoDo::class.java)
        hits.forEach { println(it.content) }
    }

    /**
     * 复合查询(bool)
     */
    @Test
    fun testBoolQuery() {
        val boolQuery = QueryBuilders.boolQuery()
        //必要条件: query.must 得到一个集合
        val mustList = boolQuery.must()
        mustList.add(QueryBuilders.rangeQuery("user_id").gte(10000).lt(10003))
        //其他的搜索条件集合的获取方式类似
        val mustNotList = boolQuery.mustNot()
        val should = boolQuery.should()

        //当然,还有一种简化的写法,如下,下述代码相当于 query.should().add(QueryBuilders.matchAllQuery())
        boolQuery.should(QueryBuilders.matchAllQuery())

        val query =  NativeSearchQueryBuilder()
            .withQuery(boolQuery)
            .build()
        val hits = elasticsearchTemplate.search(query, AlbumInfoDo::class.java)
        hits.forEach { println(it.content) }
    }

    /**
     * 排序和分页
     */
    @Test
    fun testSortAndPage() {
        val query = NativeSearchQueryBuilder()
            .withQuery(QueryBuilders.matchAllQuery())
            .withPageable(
                PageRequest.of(0, 3) //参数一: 页码(从 0 开始),size 每页查询多少条数据
                    .withSort(Sort.by(Sort.Order.desc("id"))) //根据 id 降序排序(这里也可以根据多个字段进行升序降序)
            ).build()
        val hits = elasticsearchTemplate.search(query, AlbumInfoDo::class.java)
        hits.forEach{ println(it.content) }
    }

    /**
     * 高亮搜索
     */
    @Test
    fun testHighLight() {
        //定义高亮字段
        val field = HighlightBuilder.Field("title")
        //a) 前缀标签
        field.preTags("<span style='color:red'>")
        //b) 后缀标签
        field.postTags("</span>")
        //c) 高亮的片段长度(多少个几个字需要高亮,一般会设置的大一些,让匹配到的字段尽量都高亮)
        field.fragmentSize(10)
        //d) 高亮片段的数量
        field.numOfFragments(1)

        // withHighlightFields(Field... 高亮字段数组)
        val query = NativeSearchQueryBuilder()
            .withQuery(QueryBuilders.matchQuery("title", "天气"))
            .withHighlightFields(field)
            .build()
        val hits = elasticsearchTemplate.search(query, AlbumInfoDo::class.java)

        //注意,hit.content 中本身是没有高亮数据的,因此这里需要手工处理
        hits.forEach {
            val result = it.content
            //根据高亮字段名称,获取高亮数据集合,结果是 List<String>
            val hList = it.getHighlightField("title")
            if(hList.size > 0) {
                //有高亮数据
                result.title = hList.get(0)
            }
            println(result)
        }
    }

}

b)基于 completionSuggestion 实现自动补全

data 如下:

@Document(indexName = "album_doc")
data class AlbumDocDo (
    @Id
    @Field(type = FieldType.Keyword)
    val id: Long,
    @Field(name = "user_id", type = FieldType.Long)
    val userId: Long,
    @Field(type = FieldType.Text, analyzer = "ik_max_word", copyTo = ["suggestion"])
    val title: String,
    @Field(type = FieldType.Text, analyzer = "ik_smart")
    val content: String,
    @Field(name = "ct_time", type = FieldType.Long)
    val ctTime: Long,
    @Field(name = "ut_time", type = FieldType.Long)
    val utTime: Long,
    @CompletionField(maxInputLength = 100, analyzer = "ik_max_word", searchAnalyzer = "ik_max_word")
    val suggestion: Completion? = null,
)

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

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

    private fun map(hit: Suggest.Suggestion.Entry.Option): String {
        return hit.text
    }

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

org.springframework.data.elasticsearch.core.elasticsearchresttemplate#delete,ElasticSearch,elasticsearch,spring,jenkins

org.springframework.data.elasticsearch.core.elasticsearchresttemplate#delete,ElasticSearch,elasticsearch,spring,jenkins

1.4、ElasticsearchRepository

1.4.1、使用方式

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

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

import org.cyk.dataes.model.AlbumInfoDo
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository


interface AlbumInfoRepo: ElasticsearchRepository<AlbumInfoDo, Long> //<实体类,主键类型>

1.4.2、简单的增删改查

import org.cyk.dataes.model.AlbumInfoDo
import org.cyk.dataes.service.AlbumInfoESRepo
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import javax.annotation.Resource

@SpringBootTest(classes = [DataEsApplication::class])
class RepoTests {

    @Resource private lateinit var albumInfoESRepo: AlbumInfoESRepo

    @Test
    fun testSave() {
        //增加单个
        val a = AlbumInfoDo(1, 10000, "今天天气真好", "学习完之后,我要出去好好玩")
        val result = albumInfoESRepo.save(a)
        println(result)
        //批量新增
        val list = listOf(
            AlbumInfoDo(2, 10000, "西安六号线避雷", "前俯后仰。他就一直在那前后动。他背后是我朋友,我让他不要挤了,他直接就急了,开始故意很大力的挤来挤去。"),
            AlbumInfoDo(3, 10000, "字节跳动快上车~", "#内推 #字节跳动内推 #互联网"),
            AlbumInfoDo(4, 10000, "连王思聪也变得低调老实了", "如今的王思聪,不仅交女友的质量下降,在网上也不再像以前那样随意喷这喷那。显然,资金的紧张让他低调了许多")
        )
        val resultList = albumInfoESRepo.saveAll(list)
        resultList.forEach(::println)
    }

    @Test
    fun testDel() {
        //根据 id 删除
        albumInfoESRepo.deleteById(1)
        //删除所有
        albumInfoESRepo.deleteAll()
    }

    @Test
    fun testFind() {
        //查询所有
        val resultList = albumInfoESRepo.findAll()
        resultList.forEach(::println)
        //根据 id 查询
        val result = albumInfoESRepo.findById(1)
        println(result.get())
    }

}

1.4.3、分页排序查询

import org.cyk.dataes.service.AlbumInfoESRepo
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Sort
import javax.annotation.Resource

@SpringBootTest(classes = [DataEsApplication::class])
class RepoTests2 {

    @Resource
    private lateinit var albumInfoESRepo: AlbumInfoESRepo

    @Test
    fun testFindPageAndSort() {
        //从 0 下标开始向后获取 3 个,并根据 id 降序排序
        val result = albumInfoESRepo.findAll(
            PageRequest.of(0, 3,
                Sort.by(Sort.Direction.DESC, "id"))
        )
        result.content.forEach(::println)
    }

}

org.springframework.data.elasticsearch.core.elasticsearchresttemplate#delete,ElasticSearch,elasticsearch,spring,jenkins文章来源地址https://www.toymoban.com/news/detail-846830.html

到了这里,关于SpringData ElasticSearch - 简化开发,完美适配 Spring 生态的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 3: [SpringData集成Elasticsearch] --- 创建索引和字段 --- 相关注解说明

    1.1.0:注意:ES 6.0.0以后ndex一个索引对应一个type(mysql 库里的表) 每个index有shard分片存储文件,相互独立。 1.1.1:@Document元注解参数和使用 — 注解使用 1.2.1:参数说明: type — 枚举类型 @Field(analyzer = “ik_max_word”,type = FieldType.Text) package org.springframework.data.elasticsearch.annotations

    2024年02月07日
    浏览(28)
  • Spring-2-深入理解Spring 注解依赖注入(DI):简化Java应用程序开发

      掌握纯注解开发依赖注入(DI)模式 学习使用纯注解进行第三方Bean注入 问题导入 思考:如何使用注解方式将Bean对象注入到类中 1.1 使用@Autowired注解开启自动装配模式(按类型) 说明:不管是使用配置文件还是配置类,都必须进行对应的Spring注解包扫描才可以使用。@Autowired默

    2024年02月14日
    浏览(47)
  • SSMP整合案例(2) Spring Boot整合Lombok简化实体类开发

    好啊 接着我们上文SSMP整合案例(1) 构建 Spring Boot Vue MySql项目环境 我们继续 接下来 我们要在java项目中 建立出数据库表对应的实体类 我们还是先看看自己上文中 创建的这个 book表 其中四个字段 主键id 数字枚举类型的type 字符串类型name 字符串类型 description 我们打开idea 找到上

    2024年02月09日
    浏览(37)
  • Spring-1-深入理解Spring XML中的依赖注入(DI):简化Java应用程序开发

    前两篇文章我们介绍了什么是Spring,以及Spring的一些核心概念,并且快速快发一个Spring项目,以及详细讲解IOC,今天详细介绍一些DI(依赖注入) 能够配置setter方式注入属性值 能够配置构造方式注入属性值 能够理解什么是自动装配 思考:向一个类中传递数据的方式有几种?(给类

    2024年02月13日
    浏览(40)
  • Spring Data JPA简化数据访问,提升开发效率和程序可维护性

    Java Persistence API(JPA)是一种Java EE的ORM规范用于与关系型数据库进行交互。它提供了Java对象模型和关系型数据库之间的映射。JPA为开发者提供了一种面向对象的方式来访问数据库,同时也减少了应用程序与底层数据库之间的重复代码。 Spring Data JPA是基于JPA规范的一个Spring

    2024年02月05日
    浏览(42)
  • Spring AI 来了,打造Java生态大模型应用开发新框架!

    尽管 Python 长期主导 AI 大模型应用开发领域,但 Java 并未熄火!Spring AI 来了,正式告别实验期,迈向广泛应用新阶段!这意味着 Spring 生态体系的广大开发者,迎来 AI 大模型应用开发的新里程。 Spring AI 是一个 AI 工程师的应用框架,它提供了一个友好的 API 和开发 AI 应用的抽

    2024年04月12日
    浏览(39)
  • Spring Boot与Docker的完美融合:从开发到部署的实战

    一、Spring Boot与Docker简介 Spring Boot框架特点与优势概述 Spring Boot 是一个由Pivotal团队开发的用于简化新Spring应用初始搭建以及开发过程的框架。它的核心特点和优势包括: 简化配置 :Spring Boot采用约定优于配置的方式,减少了大量的XML配置工作,通过自动配置功能极大地加速

    2024年04月26日
    浏览(28)
  • 【简化程序设计】C++STL“容器适配器“之栈和队列

    🎉博客主页:小智_x0___0x_ 🎉欢迎关注:👍点赞🙌收藏✍️留言 🎉系列专栏:C++初阶 🎉代码仓库:小智的代码仓库 【本节目标】: stack的介绍和使用 stack的模拟实现 queue的介绍和使用 queue的模拟实现 priority_queue的介绍和使用 priority_queue的模拟实现 容器适配器 deuqe的介绍

    2024年02月15日
    浏览(27)
  • Android存储权限完美适配(Android11及以上适配)

    一、Bug简述 一个很普通的需求,需要下载图片到本地,我的三个测试机(荣耀Android10,红米 11 和小米Android 13都没有问题)。 然后,主角登场了,测试的三星Android 13 死活拉不起存储权限弹窗。 想了下,三星的系统可能和小米的系统做了些区别。于是就是看了下存储权限的版

    2024年02月06日
    浏览(35)
  • 鸿蒙生态构建:适配智能家居、车联网等多种设备

    鸿蒙系统是一个面向未来、支持多种设备、多种场景的分布式操作系统。其架构允许开发者在不同的设备上运行相同的代码和应用,大大提高了开发效率和用户体验。鸿蒙系统的生态构建是其适配多种设备的关键。下面,我们将重点讲解鸿蒙系统如何适配智能家居和车联网设

    2024年02月12日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包