首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >elastic4s:如何将文档id自动读取到case类实例中?

elastic4s:如何将文档id自动读取到case类实例中?
EN

Stack Overflow用户
提问于 2021-05-06 11:48:31
回答 1查看 135关注 0票数 0

elastic4s 7.12.1spray-json 1.3.6 (和scala 2.13.5)结合使用:

是否有方法将Elasticsearch文档的_id读入字段,例如。id,一个case class实例,

仅使用隐式spray-json RootJsonFormat,即不为elastic4s使用自定义HitReader,如果是,如何使用?

编写文档也是如此:是否有一种方法可以插入case class的实例而不序列化(在ES中使其成为_source的一部分)-- id字段仅使用前面提到的RootJsonFormat,即不编写自定义Indexable

根据elastic4s文档,使用jackson应该是可能的,我想避免这一点,因为它总是会出现许多关键的安全问题。

考虑这个案例类,它应该被索引到ES中:

代码语言:javascript
复制
case class Foo(id: String, name: String)

使用spray-json,我只需要定义一个RootJsonFormat

代码语言:javascript
复制
implicit val fooJsonFormat: RootJsonFormat[Foo] = jsonFormat2(Foo)

并且可以使用elastic4s这种方式索引和搜索Foo的:

代码语言:javascript
复制
val someFoo = Foo("idWhichShouldBeOverwrittenByES", "someName")
client.execute {
  indexInto("foos").doc(someFoo)
}

val result: Response[SearchResponse] = client.execute {
      search("foos").query {
        boolQuery().must {
          matchQuery("name", "someName")
        }
      }
    }.await

result match {
        case RequestSuccess(_, _, _, result) => result.to[Foo].foreach(println)
        case RequestFailure(_, _, _, error) => println(error.toString)
      }

然而,这种方法存在主要问题:

  • 我在创建Foo时需要提供一个id,而实际上我希望ES在文档索引时为我生成_id。当然,这主要是因为使用case class
  • When加载Foo文档,它的id字段包含我在索引它时使用的(无意义的)虚拟值,而不是存储在ES节点

中的实际_id

为了解决这些问题(第一个问题只是部分问题),我当然可以编写自己的IndexableHitReader,如下所示:

代码语言:javascript
复制
  implicit object FooHitReader extends HitReader[Foo] {
    override def read(hit: Hit): Try[Foo] = Try({
      val source = hit.sourceAsMap
      Foo(
        id = hit.id,
        name = source("name").toString
      )
    })
  }

  implicit object FooIndexable extends Indexable[Foo] {
    override def json(t: Foo): String =
      JsObject(
        "name" -> JsString(t.name),
      ).compactPrint
  }

在一个小示例中,这看起来并不太糟糕,但我认为很明显,这种方法扩展得很糟糕,没有提供任何类型的安全性,而且是重构的噩梦,因为字段的名称(例如,"name")需要手动指定。

Bottomline:是否有更好的方法来实现spring-data-elasticsearch-like体验,还是elastic4sspray-json不适合这个任务?

编辑:的另一种可能性是从Foo中删除id字段,引入包装器case class,例如FooResultWrapper,它将_idFoo搜索结果存储在Map[String, Foo]中,使用RootJsonFormat[Foo]HitReader[FooResultWrapper]_source转换为Foo并以hit.id存储它。但这也不是很令人满意。

EN

回答 1

Stack Overflow用户

发布于 2021-05-06 14:33:21

看看我想出的绝妙解决方案(基本上是我在问题编辑中提出的):

删除了我的域case classcase class字段(例如,Foo),并引入了一个通用的case class来包装结果,并强制使用objects实现特定case classelastic4s中的read

代码语言:javascript
复制
case class ESResultWrapper[T](id: String, result: T)

与泛型trait一起使用,它包含在ESResultWrapper实例中包装T类型结果的实现:

代码语言:javascript
复制
trait ESResultWrapperHitReader[T] extends HitReader[ESResultWrapper[T]] {
  def readInternal(hit: Hit)(implicit reader: HitReader[T]): Try[ESResultWrapper[T]] = Try({
    ESResultWrapper(
      id = hit.id,
      result = hit.to[T]
    )
  })
}

现在,真正的“域”类所剩下的就是用特定的case类扩展ESResultWrapperHitReader[T] trait (其中也存在RootJsonFormat ),并将hit委托给hitInternal,从而通过RootJsonFormat[T]隐式地提供HitReader[T]

代码语言:javascript
复制
  implicit object FooResultWrapperHitReader extends ESResultWrapperHitReader[Foo] {
    override def read(hit: Hit): Try[ESResultWrapper[Foo]] = readInternal(hit)
  } 

用法非常简单(如问题中的示例所示):

代码语言:javascript
复制
result match {
        case RequestSuccess(_, _, _, result) => result.to[ESResultWrapper[Foo]].foreach(println)
        case RequestFailure(_, _, _, error) => println(error.toString)
      }

导致例如:ESResultWrapper(-XMSQXkB-5ze1JvrVWup,Foo("someFoo"))

最好的部分是: Changig包装实现不影响域类。

我为自己在使用Scala的第三天就想到了这一点而鼓掌。做得好。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/67417523

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档