如何解决elastic4s:如何自动将文档 ID 读入案例类实例?
将 elastic4s 7.12.1
与 spray-json 1.3.6
(和 scala 2.13.5
)一起使用:
有没有办法将 Elasticsearch 文档的 _id
读入一个字段,例如 . id
,case class
实例的,
仅使用隐式 spray-json
RootJsonFormat
,i。 e.无需为 HitReader
编写自定义 elastic4s
,如果是,如何编写?
编写文档也是如此:有没有办法只使用前面提到的 case class
字段插入 _source
的实例而不序列化(使其成为 ES 中 id
的一部分)RootJsonFormat
字段{1}},我。 e.不编写自定义 Indexable
?
根据 elastic4s
文档,使用 jackson
应该可以做到这一点,我想避免这种情况,因为它有很多关键的安全问题,一直出现。
考虑这个case类,它应该被索引到ES中:
case class Foo(id: String,name: String)
使用 spray-json
,我只需要定义一个 RootJsonFormat
:
implicit val foojsonFormat: RootJsonFormat[Foo] = jsonFormat2(Foo)
并且可以使用 elastic4s
这种方式来索引和搜索 Foo
:
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)
}
但是,这种方法存在主要问题:
- 我需要在创建
id
时提供Foo
,而我实际上希望 ES 在索引文档时为我生成_id
。这当然主要是由于使用了case class
- 当加载一个
Foo
文档时,它的id
字段包含我在索引它时使用的(无意义的)虚拟值,而不是它存储在 ES 节点内的实际_id
为了解决这些问题(第一个只是部分),我当然可以像这样编写自己的 Indexable
和 HitReader
:
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"
)需要手动指定。
底线:是否有更好的方法来实现类似 spring-data-elasticsearch
的体验,或者 elastic4s
和 spray-json
不适合这项任务?
编辑:另一种可能性是从 id
中删除 Foo
字段,引入包装器 case class
,例如FooResultWrapper
将 Foo
的 _id
搜索结果存储在 Map[String,Foo]
中,使用 RootJsonFormat[Foo]
和 HitReader[FooResultWrapper]
转换 _source
到 Foo
并由 hit.id
存储。但这也不是很令人满意。
解决方法
看我想出的绝妙解决方案(基本上是我在编辑问题时提出的建议):
删除了我的域 id
(例如 case class
)的 Foo
字段并引入了通用 case class
来包装结果并强制使用 objects
来实现 {{1 }} 来自 read
的特定 elastic4s
:
case class
以及一个通用的 case class ESResultWrapper[T](id: String,result: T)
,它包含在 trait
实例中包装 T
类型的结果的实现:
ESResultWrapper
现在真正的“域”类剩下的就是使用特定的案例类(也存在 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]
)
})
}
)扩展 ESResultWrapperHitReader[T]
trait
并委托 {{1} } 到 RootJsonFormat
,从而通过 hit
:
hitInternal
HitReader[T]
用法非常简单(坚持问题中的示例):
RootJsonFormat[T]
导致 e。 G。:
implicit object FooResultWrapperHitReader extends ESResultWrapperHitReader[Foo] {
override def read(hit: Hit): Try[ESResultWrapper[Foo]] = readInternal(hit)
}
最好的部分是:改变包装实现不会影响域类。
我为自己在使用 Scala 的第三天提出这个想法而鼓掌。干得好。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。