定制转换器,用于具有数据存储实体的嵌套密封类

发布于 2025-02-01 04:27:57 字数 8671 浏览 2 评论 0原文

我们有一个主要的对象模型,其中包含其他值的集合。这些事件是一组已知类型的类型,例如创建,错误,完成等...每个事件都有几个覆盖字段,但对于该事件的一些特定字段。对我们来说,将它们作为密封类型的数据类作为数据类是有意义的。我们正在使用GCP的数据存储和com.google.cloud:spring-cloud-gcp-starter-data-dataStore库>用于读写的库。

我们遇到的错误发生在datastoretemplate.findbyid(...)上,我们相信这与当供应实体的事件的密封类有关。 StackTrace:

Failed to instantiate com.PhoneExample.PhoneEntity$EventEntity using constructor fun <init>(): com.PhoneExample.PhoneEntity.EventEntity with arguments
org.springframework.data.mapping.model.MappingInstantiationException: Failed to instantiate com.PhoneExample.PhoneEntity$EventEntity using constructor fun <init>(): com.PhoneExample.PhoneEntity.EventEntity with arguments 
    at org.springframework.data.mapping.model.ReflectionEntityInstantiator.createInstance(ReflectionEntityInstantiator.java:79)
    at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator.createInstance(ClassGeneratingEntityInstantiator.java:89)
    at com.google.cloud.spring.data.datastore.core.convert.DefaultDatastoreEntityConverter.read(DefaultDatastoreEntityConverter.java:169)
    at com.google.cloud.spring.data.datastore.core.convert.DefaultDatastoreEntityConverter.read(DefaultDatastoreEntityConverter.java:54)
    at com.google.cloud.spring.data.datastore.core.convert.TwoStepsConversions.convertOnReadSingleEmbedded(TwoStepsConversions.java:205)
    at com.google.cloud.spring.data.datastore.core.convert.TwoStepsConversions.convertOnRead(TwoStepsConversions.java:183)
    at com.google.cloud.spring.data.datastore.core.convert.TwoStepsConversions.convertOnRead(TwoStepsConversions.java:130)
    at com.google.cloud.spring.data.datastore.core.convert.EntityPropertyValueProvider.getPropertyValue(EntityPropertyValueProvider.java:68)
    at com.google.cloud.spring.data.datastore.core.convert.EntityPropertyValueProvider.getPropertyValue(EntityPropertyValueProvider.java:54)
    at com.google.cloud.spring.data.datastore.core.convert.EntityPropertyValueProvider.getPropertyValue(EntityPropertyValueProvider.java:32)
    at org.springframework.data.mapping.model.PersistentEntityParameterValueProvider.getParameterValue(PersistentEntityParameterValueProvider.java:74)
    at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator.extractInvocationArguments(ClassGeneratingEntityInstantiator.java:276)
    at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator$EntityInstantiatorAdapter.createInstance(ClassGeneratingEntityInstantiator.java:248)
    at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator.createInstance(ClassGeneratingEntityInstantiator.java:89)
    at com.google.cloud.spring.data.datastore.core.convert.DefaultDatastoreEntityConverter.read(DefaultDatastoreEntityConverter.java:169)
    at com.google.cloud.spring.data.datastore.core.convert.DefaultDatastoreEntityConverter.read(DefaultDatastoreEntityConverter.java:54)
    at com.google.cloud.spring.data.datastore.core.DatastoreTemplate.convertEntityResolveDescendantsAndReferences(DatastoreTemplate.java:669)
    at com.google.cloud.spring.data.datastore.core.DatastoreTemplate.lambda$convertEntitiesForRead$15(DatastoreTemplate.java:657)
    at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
    at java.base/java.util.HashMap$KeySpliterator.forEachRemaining(HashMap.java:1603)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
    at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
    at com.google.cloud.spring.data.datastore.core.DatastoreTemplate.convertEntitiesForRead(DatastoreTemplate.java:659)
    at com.google.cloud.spring.data.datastore.core.DatastoreTemplate.findAllById(DatastoreTemplate.java:264)
    at com.google.cloud.spring.data.datastore.core.DatastoreTemplate.performFindByKey(DatastoreTemplate.java:246)
    at com.google.cloud.spring.data.datastore.core.DatastoreTemplate.findById(DatastoreTemplate.java:140)
    at com.PhoneExample.ExampleTest.processTest(ExampleTest.kt:17)
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.PhoneExample.PhoneEntity$EventEntity]: Is it an abstract class?; nested exception is java.lang.InstantiationException
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:215)
    at org.springframework.data.mapping.model.ReflectionEntityInstantiator.createInstance(ReflectionEntityInstantiator.java:77)
Caused by: java.lang.InstantiationException
    at java.base/jdk.internal.reflect.InstantiationExceptionConstructorAccessorImpl.newInstance(InstantiationExceptionConstructorAccessorImpl.java:48)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
    at kotlin.reflect.jvm.internal.calls.CallerImpl$Constructor.call(CallerImpl.kt:41)
    at kotlin.reflect.jvm.internal.KCallableImpl.call(KCallableImpl.kt:108)
    at kotlin.reflect.jvm.internal.KCallableImpl.callDefaultMethod$kotlin_reflection(KCallableImpl.kt:159)
    at kotlin.reflect.jvm.internal.KCallableImpl.callBy(KCallableImpl.kt:112)
    at org.springframework.beans.BeanUtils$KotlinDelegate.instantiateClass(BeanUtils.java:854)
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:196)

我试图使以下示例在维护我们使用的结构时尽可能最小。

class ExampleTest(private val datastoreTemplate: DatastoreTemplate) {
    fun processTest() {
        val phone = Phone(
            "123456789",
            ErrorEvent("Error-01", RuntimeException("some exception"), Instant.now())
        )

        val phoneEntity = datastoreTemplate.save(PhoneEntity(phone))
        datastoreTemplate.findById(phoneEntity.id, PhoneEntity::class.java)
    }
}

data class Phone(
    val id: String,
    val events: Event
)

abstract class BaseEvent {
    abstract val name: String
    abstract val time: Instant
}
sealed class Event : BaseEvent()

data class ErrorEvent(
    override val name: String,
    val error: Throwable,
    override val time: Instant
) : Event()


@Entity(name = "phone")
data class PhoneEntity(
    @Id
    val id: String,
    val events: EventEntity
) {
    companion object {
        operator fun invoke(phone: Phone) =
            with(phone) {
                PhoneEntity(
                    id,
                    EventEntity(events)
                )
            }
    }

    fun to(): Phone =
        Phone(
            id,
            events.to()
        )

    @Entity
    sealed class EventEntity {
        companion object {
            operator fun invoke(event: Event) =
                when (event) {
                    is ErrorEvent -> with(event) {
                        ErrorEventEntity(
                            name,
                            error,
                            time
                        )
                    }
                }
        }

        fun to(): Event =
            when (this) {
                is ErrorEventEntity ->
                    with(this) {
                        ErrorEvent(name, error, time)
                    }
            }

        data class ErrorEventEntity(
            val name: String,
            val error: Throwable,
            val time: Instant
        ) : EventEntity()
    }
}

我们尝试通过使用DataStoreCustomConversions来解决此问题,以为我们可以指定如何从保存的实体转到事件,但最终仍会出现相同的错误。在调试时,看起来自定义转换器正在DataStoreTemplate中注册,因此除非我们缺少其他内容,否则我认为这是正确的。

val EVENT_ENTITY_CONVERTER: Converter<PhoneEntity.EventEntity, Event> = object : Converter<PhoneEntity.EventEntity, Event> {
    override fun convert(eventEntity: PhoneEntity.EventEntity): Event {
        return eventEntity.to()
    }
}

@Configuration
class ConverterConfiguration {
    @Bean
    fun datastoreCustomConversions(): DatastoreCustomConversions {
        return DatastoreCustomConversions(
            listOf(
                EVENT_ENTITY_CONVERTER
            )
        )
    }
}

我已经尝试更改转换器实现,但没有任何区别。

因此,主要问题:

  1. 在自定义转换器中,我们是否在不允许其帮助该过程的情况下做错了什么,或者转换器无法解决此问题?
  2. 如果转换器无济于事,在我们的事件上使用密封类可以维护其他操作,这将使我们能够从数据存储中对其进行认可?
  3. 我们应该这样做的另一种方法是我们的​​结构中会更好或任何明显的设计缺陷吗?

目前,我们有一些工作的工作,但它涉及将我们的现场性更改为数据类,然后将所有可能的领域用于所有儿童事件,并使它们无效。然后,每次施工最终都以一堆无关的磁场为零(事件 - &gt; Evententity and Evententity -gt; event)。感觉就像是一种灵活的解决方案,只是丑陋的。

任何建议或帮助将不胜感激,谢谢

We have a main object model that has a collection of Events on it among other values. These events are a known set of types such as Created, Errored, Completed, etc... each with a handful of overridden fields but some specific fields for that event. To us it makes sense to have these as data classes under a Sealed Type. We're using GCP's Datastore and the com.google.cloud:spring-cloud-gcp-starter-data-datastore library for reading and writing.

The error we're running into occurs on the datastoreTemplate.findById(...) and we believe it has something to do with the Sealed classes for the Events when deserializing the entities.
Stacktrace:

Failed to instantiate com.PhoneExample.PhoneEntity$EventEntity using constructor fun <init>(): com.PhoneExample.PhoneEntity.EventEntity with arguments
org.springframework.data.mapping.model.MappingInstantiationException: Failed to instantiate com.PhoneExample.PhoneEntity$EventEntity using constructor fun <init>(): com.PhoneExample.PhoneEntity.EventEntity with arguments 
    at org.springframework.data.mapping.model.ReflectionEntityInstantiator.createInstance(ReflectionEntityInstantiator.java:79)
    at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator.createInstance(ClassGeneratingEntityInstantiator.java:89)
    at com.google.cloud.spring.data.datastore.core.convert.DefaultDatastoreEntityConverter.read(DefaultDatastoreEntityConverter.java:169)
    at com.google.cloud.spring.data.datastore.core.convert.DefaultDatastoreEntityConverter.read(DefaultDatastoreEntityConverter.java:54)
    at com.google.cloud.spring.data.datastore.core.convert.TwoStepsConversions.convertOnReadSingleEmbedded(TwoStepsConversions.java:205)
    at com.google.cloud.spring.data.datastore.core.convert.TwoStepsConversions.convertOnRead(TwoStepsConversions.java:183)
    at com.google.cloud.spring.data.datastore.core.convert.TwoStepsConversions.convertOnRead(TwoStepsConversions.java:130)
    at com.google.cloud.spring.data.datastore.core.convert.EntityPropertyValueProvider.getPropertyValue(EntityPropertyValueProvider.java:68)
    at com.google.cloud.spring.data.datastore.core.convert.EntityPropertyValueProvider.getPropertyValue(EntityPropertyValueProvider.java:54)
    at com.google.cloud.spring.data.datastore.core.convert.EntityPropertyValueProvider.getPropertyValue(EntityPropertyValueProvider.java:32)
    at org.springframework.data.mapping.model.PersistentEntityParameterValueProvider.getParameterValue(PersistentEntityParameterValueProvider.java:74)
    at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator.extractInvocationArguments(ClassGeneratingEntityInstantiator.java:276)
    at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator$EntityInstantiatorAdapter.createInstance(ClassGeneratingEntityInstantiator.java:248)
    at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator.createInstance(ClassGeneratingEntityInstantiator.java:89)
    at com.google.cloud.spring.data.datastore.core.convert.DefaultDatastoreEntityConverter.read(DefaultDatastoreEntityConverter.java:169)
    at com.google.cloud.spring.data.datastore.core.convert.DefaultDatastoreEntityConverter.read(DefaultDatastoreEntityConverter.java:54)
    at com.google.cloud.spring.data.datastore.core.DatastoreTemplate.convertEntityResolveDescendantsAndReferences(DatastoreTemplate.java:669)
    at com.google.cloud.spring.data.datastore.core.DatastoreTemplate.lambda$convertEntitiesForRead$15(DatastoreTemplate.java:657)
    at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
    at java.base/java.util.HashMap$KeySpliterator.forEachRemaining(HashMap.java:1603)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
    at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
    at com.google.cloud.spring.data.datastore.core.DatastoreTemplate.convertEntitiesForRead(DatastoreTemplate.java:659)
    at com.google.cloud.spring.data.datastore.core.DatastoreTemplate.findAllById(DatastoreTemplate.java:264)
    at com.google.cloud.spring.data.datastore.core.DatastoreTemplate.performFindByKey(DatastoreTemplate.java:246)
    at com.google.cloud.spring.data.datastore.core.DatastoreTemplate.findById(DatastoreTemplate.java:140)
    at com.PhoneExample.ExampleTest.processTest(ExampleTest.kt:17)
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.PhoneExample.PhoneEntity$EventEntity]: Is it an abstract class?; nested exception is java.lang.InstantiationException
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:215)
    at org.springframework.data.mapping.model.ReflectionEntityInstantiator.createInstance(ReflectionEntityInstantiator.java:77)
Caused by: java.lang.InstantiationException
    at java.base/jdk.internal.reflect.InstantiationExceptionConstructorAccessorImpl.newInstance(InstantiationExceptionConstructorAccessorImpl.java:48)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
    at kotlin.reflect.jvm.internal.calls.CallerImpl$Constructor.call(CallerImpl.kt:41)
    at kotlin.reflect.jvm.internal.KCallableImpl.call(KCallableImpl.kt:108)
    at kotlin.reflect.jvm.internal.KCallableImpl.callDefaultMethod$kotlin_reflection(KCallableImpl.kt:159)
    at kotlin.reflect.jvm.internal.KCallableImpl.callBy(KCallableImpl.kt:112)
    at org.springframework.beans.BeanUtils$KotlinDelegate.instantiateClass(BeanUtils.java:854)
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:196)

I've tried to make the following example as minimal as possible while maintaining the structure we're using.

class ExampleTest(private val datastoreTemplate: DatastoreTemplate) {
    fun processTest() {
        val phone = Phone(
            "123456789",
            ErrorEvent("Error-01", RuntimeException("some exception"), Instant.now())
        )

        val phoneEntity = datastoreTemplate.save(PhoneEntity(phone))
        datastoreTemplate.findById(phoneEntity.id, PhoneEntity::class.java)
    }
}

data class Phone(
    val id: String,
    val events: Event
)

abstract class BaseEvent {
    abstract val name: String
    abstract val time: Instant
}
sealed class Event : BaseEvent()

data class ErrorEvent(
    override val name: String,
    val error: Throwable,
    override val time: Instant
) : Event()


@Entity(name = "phone")
data class PhoneEntity(
    @Id
    val id: String,
    val events: EventEntity
) {
    companion object {
        operator fun invoke(phone: Phone) =
            with(phone) {
                PhoneEntity(
                    id,
                    EventEntity(events)
                )
            }
    }

    fun to(): Phone =
        Phone(
            id,
            events.to()
        )

    @Entity
    sealed class EventEntity {
        companion object {
            operator fun invoke(event: Event) =
                when (event) {
                    is ErrorEvent -> with(event) {
                        ErrorEventEntity(
                            name,
                            error,
                            time
                        )
                    }
                }
        }

        fun to(): Event =
            when (this) {
                is ErrorEventEntity ->
                    with(this) {
                        ErrorEvent(name, error, time)
                    }
            }

        data class ErrorEventEntity(
            val name: String,
            val error: Throwable,
            val time: Instant
        ) : EventEntity()
    }
}

We tried working around this by using the DatastoreCustomConversions thinking we could specify how to go about going from the saved Entity to the Event but we still end up with the same error. While debugging it looks like the Custom Converter is being registered in the DatastoreTemplate so unless theres something else we're missing, I think it's correct.

val EVENT_ENTITY_CONVERTER: Converter<PhoneEntity.EventEntity, Event> = object : Converter<PhoneEntity.EventEntity, Event> {
    override fun convert(eventEntity: PhoneEntity.EventEntity): Event {
        return eventEntity.to()
    }
}

@Configuration
class ConverterConfiguration {
    @Bean
    fun datastoreCustomConversions(): DatastoreCustomConversions {
        return DatastoreCustomConversions(
            listOf(
                EVENT_ENTITY_CONVERTER
            )
        )
    }
}

I've tried changing the Converter implementation a couple different times with no difference.

So primary questions:

  1. Is there something we are doing obviously wrong with the Custom Converter that isn't allowing it to help with that process or is the converter unable to fix this issue?
  2. If the converter won't help, is there something else we can do to maintain using a Sealed class on our Events that will allow us to be able to deserialize them from datastore?
  3. Is there an alternative way we should be doing this that would be better or any obvious design flaws in our structure?

At present we have a somewhat work around but it involves changing our EventEntity into a data class and then taking all the possible fields for all the children events and making them nullable on the EventEntity. Then each construction ends up with a bunch of nulls for the unrelated fields when going back and forth (Event -> EventEntity and EventEntity -> Event). Which feels like a less flexible solution and just kind of ugly.

Any suggestions or help would be appreciated, thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

你是我的挚爱i 2025-02-08 04:27:57

异常迹线抱怨缺乏事件类别类别的无参数构造函数。

根据 https://kotlinlang.orgg/ docs/data-classes.html#属性 - 列表中的class-body kotlin不会为数据类生成无参数构造函数,除非您为类中的值指定默认值。

我怀疑,如果您为Evententity Feild添加默认值,您将超越该例外,甚至可能会遇到新的例外。

The exception trace is complaining about the lack of a parameterless constructor for the EventEntity class.

According to https://kotlinlang.org/docs/data-classes.html#properties-declared-in-the-class-body Kotlin won't generate a parameterless constructor for a data class unless you specify defaults for the values in the class.

I suspect that if you add defaults for EventEntity feilds you will get past that exception and possibly to new ones.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文