将elastic4s 7.12.1与spray-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中:
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)
}然而,这种方法存在主要问题:
Foo时需要提供一个id,而实际上我希望ES在文档索引时为我生成_id。当然,这主要是因为使用case classFoo文档,它的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")需要手动指定。
Bottomline:是否有更好的方法来实现spring-data-elasticsearch-like体验,还是elastic4s与spray-json不适合这个任务?
编辑:的另一种可能性是从Foo中删除id字段,引入包装器case class,例如FooResultWrapper,它将_id的Foo搜索结果存储在Map[String, Foo]中,使用RootJsonFormat[Foo]和HitReader[FooResultWrapper]将_source转换为Foo并以hit.id存储它。但这也不是很令人满意。
发布于 2021-05-06 14:33:21
看看我想出的绝妙解决方案(基本上是我在问题编辑中提出的):
删除了我的域case class的case class字段(例如,Foo),并引入了一个通用的case class来包装结果,并强制使用objects实现特定case class的elastic4s中的read。
case class ESResultWrapper[T](id: String, result: T)与泛型trait一起使用,它包含在ESResultWrapper实例中包装T类型结果的实现:
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]
implicit object FooResultWrapperHitReader extends ESResultWrapperHitReader[Foo] {
override def read(hit: Hit): Try[ESResultWrapper[Foo]] = readInternal(hit)
} 用法非常简单(如问题中的示例所示):
result match {
case RequestSuccess(_, _, _, result) => result.to[ESResultWrapper[Foo]].foreach(println)
case RequestFailure(_, _, _, error) => println(error.toString)
}导致例如:ESResultWrapper(-XMSQXkB-5ze1JvrVWup,Foo("someFoo"))
最好的部分是: Changig包装实现不影响域类。
我为自己在使用Scala的第三天就想到了这一点而鼓掌。做得好。
https://stackoverflow.com/questions/67417523
复制相似问题