Spring Data Rest:目标 bean 不是持久实体的类型

Spring Data Rest: Target bean is not of type of the persistent entity

我有一个名为 Project 的数据模型,它是通过 Spring Data Rest 从 Angular 应用程序中获取生命周期的。 ProjectScenes,场景有 UpstreamKeys 保存在 Map<Integer, UpstreamKey>UpstreamKey 是一个抽象 class 有两个实现 ChromaKeyLumaKey。 (见下面的代码)

当我编辑 (PUT) 和地图中带有 ChromaKey 的现有 Project,并将其更改为 LumaKey 时,我在后端:

Caused by: java.lang.IllegalArgumentException: Target bean of type io.mewald.notime.designer.model.scene.usk.LumaKey is not of type of the persistent entity (io.mewald.notime.designer.model.scene.usk.chroma.ChromaKey)!: io.mewald.notime.designer.model.scene.usk.LumaKey
    at org.springframework.util.Assert.instanceCheckFailed(Assert.java:702) ~[spring-core-5.3.10.jar:5.3.10]
    at org.springframework.util.Assert.isInstanceOf(Assert.java:621) ~[spring-core-5.3.10.jar:5.3.10]
    at org.springframework.data.mapping.model.BasicPersistentEntity.verifyBeanType(BasicPersistentEntity.java:584) ~[spring-data-commons-2.5.5.jar:2.5.5]
    at org.springframework.data.mapping.model.BasicPersistentEntity.getPropertyAccessor(BasicPersistentEntity.java:458) ~[spring-data-commons-2.5.5.jar:2.5.5]
    at org.springframework.data.rest.webmvc.json.DomainObjectReader$MergingPropertyHandler.<init>(DomainObjectReader.java:639) ~[spring-data-rest-webmvc-3.5.5.jar:3.5.5]
    at org.springframework.data.rest.webmvc.json.DomainObjectReader.lambda$mergeForPut(DomainObjectReader.java:141) ~[spring-data-rest-webmvc-3.5.5.jar:3.5.5]
    at java.base/java.util.Optional.map(Optional.java:265) ~[na:na]
    at org.springframework.data.rest.webmvc.json.DomainObjectReader.mergeForPut(DomainObjectReader.java:139) ~[spring-data-rest-webmvc-3.5.5.jar:3.5.5]
    at org.springframework.data.rest.webmvc.json.DomainObjectReader.lambda$mergeMaps(DomainObjectReader.java:429) ~[spring-data-rest-webmvc-3.5.5.jar:3.5.5]
    at java.base/java.util.Optional.map(Optional.java:265) ~[na:na]
    at org.springframework.data.rest.webmvc.json.DomainObjectReader.mergeMaps(DomainObjectReader.java:418) ~[spring-data-rest-webmvc-3.5.5.jar:3.5.5]
    at org.springframework.data.rest.webmvc.json.DomainObjectReader.access[=11=]0(DomainObjectReader.java:65) ~[spring-data-rest-webmvc-3.5.5.jar:3.5.5]
    at org.springframework.data.rest.webmvc.json.DomainObjectReader$MergingPropertyHandler.doWithPersistentProperty(DomainObjectReader.java:673) ~[spring-data-rest-webmvc-3.5.5.jar:3.5.5]
    at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:374) ~[spring-data-commons-2.5.5.jar:2.5.5]
    at org.springframework.data.rest.webmvc.json.DomainObjectReader.lambda$mergeForPut(DomainObjectReader.java:143) ~[spring-data-rest-webmvc-3.5.5.jar:3.5.5]
    at java.base/java.util.Optional.map(Optional.java:265) ~[na:na]
    at org.springframework.data.rest.webmvc.json.DomainObjectReader.mergeForPut(DomainObjectReader.java:139) ~[spring-data-rest-webmvc-3.5.5.jar:3.5.5]
    at org.springframework.data.rest.webmvc.json.DomainObjectReader.lambda$mergeCollections(DomainObjectReader.java:469) ~[spring-data-rest-webmvc-3.5.5.jar:3.5.5]
    at java.base/java.util.Optional.map(Optional.java:265) ~[na:na]
    at org.springframework.data.rest.webmvc.json.DomainObjectReader.mergeCollections(DomainObjectReader.java:452) ~[spring-data-rest-webmvc-3.5.5.jar:3.5.5]
    at org.springframework.data.rest.webmvc.json.DomainObjectReader.access0(DomainObjectReader.java:65) ~[spring-data-rest-webmvc-3.5.5.jar:3.5.5]
    at org.springframework.data.rest.webmvc.json.DomainObjectReader$MergingPropertyHandler.doWithPersistentProperty(DomainObjectReader.java:675) ~[spring-data-rest-webmvc-3.5.5.jar:3.5.5]
    at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:374) ~[spring-data-commons-2.5.5.jar:2.5.5]
    at org.springframework.data.rest.webmvc.json.DomainObjectReader.lambda$mergeForPut(DomainObjectReader.java:143) ~[spring-data-rest-webmvc-3.5.5.jar:3.5.5]
    at java.base/java.util.Optional.map(Optional.java:265) ~[na:na]
    at org.springframework.data.rest.webmvc.json.DomainObjectReader.mergeForPut(DomainObjectReader.java:139) ~[spring-data-rest-webmvc-3.5.5.jar:3.5.5]
    at org.springframework.data.rest.webmvc.json.DomainObjectReader.readPut(DomainObjectReader.java:116) ~[spring-data-rest-webmvc-3.5.5.jar:3.5.5]
    at org.springframework.data.rest.webmvc.config.JsonPatchHandler.applyPut(JsonPatchHandler.java:100) ~[spring-data-rest-webmvc-3.5.5.jar:3.5.5]
    at org.springframework.data.rest.webmvc.config.PersistentEntityResourceHandlerMethodArgumentResolver.readPutForUpdate(PersistentEntityResourceHandlerMethodArgumentResolver.java:234) ~[spring-data-rest-webmvc-3.5.5.jar:3.5.5]
    ... 90 common frames omitted

我的解释是,它试图以某种方式将 LumaKey 合并到 ChromaKey 中,当然,这是行不通的。为什么不简单地替换 bean?有什么办法可以解决这个问题?

这里是与这个问题相关的数据模型的结构:

@NoArgsConstructor @Data @SuperBuilder @AllArgsConstructor
@Document
public class Project {
    @Id
    private String id;
    ...
    private List<Scene> scenes;
}

@NoArgsConstructor @Data @SuperBuilder @AllArgsConstructor @EqualsAndHashCode(callSuper = false)
public class Scene {
    ...
    private Map<Integer, UpstreamKey> upstreamKeys;
}

@Data @NoArgsConstructor @AllArgsConstructor @SuperBuilder(toBuilder = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({
    @JsonSubTypes.Type(value = ChromaKey.class, name = "ChromaKey"),
    @JsonSubTypes.Type(value = LumaKey.class, name = "LumaKey")
})
public abstract class UpstreamKey {
    ...
    public abstract String getType();
}

@Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor @AllArgsConstructor @SuperBuilder(toBuilder = true)
public class ChromaKey extends UpstreamKey {
    ...
    @Override
    public String getType() {
        return "ChromaKey";
    }
}

@Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor @AllArgsConstructor @SuperBuilder(toBuilder = true)
public class LumaKey extends UpstreamKey {
    ...
    @Override
    public String getType() {
        return "LumaKey";
    }
}

编辑:取自 https://docs.spring.io/spring-data/rest/docs/current/reference/html/#repository-resources.item-resource

的文档
The PUT method replaces the state of the target resource with the supplied request body.

文档明确讨论了 replace,所以我想知道为什么会涉及一个名为 mergeForPut 的方法。

编辑:刚刚意识到我的 spring-boot-starter-parent 有点老了。我更新到 2.6.4 但错误仍然存​​在。

编辑:我只是重构了所有内容,以便 scene.upstreamKeys 可以是 List 而不是 Map 以防万一这可能导致它。但事实并非如此。还是一样的错误。

编辑:根据要求,我创建了一个最小项目来复制我在上面描述的错误:https://github.com/mathias-ewald/demo-sdr-target-bean-is-not-of-type

虽然我可以在spring data mongodb 中实现更新,但现在在spring data rest 中是不可能的。在当前的 spring 数据剩余实现中,PUT 请求尝试通过调用 DomainObjectReader.mergeForPut(...) 来合并嵌套集合成员,但由于类型不匹配而失败,而不是替换它。看到这个 github issue and pull request.

所以解决方法是自己实现一个控制器。

编辑 2022-04-05

@Immutable注释UpstreamKey使spring数据休息执行替换而不是合并。此解决方案仅在嵌套集合成员只是值(没有 id、没有审计数据、没有 @JsonIgnore 来隐藏服务器端数据)而不是实体(带有 id)时才有效。

找到 updated project and the diff

下面是put操作后的结果mongo数据。

{
    _id: ObjectId('624b9fe3f32185579ff56849'),
    name: 'Project 1',
    scenes: [
        {
            name: 'Scene 1',
            usks: [
                {
                    c: 3,
                    a: 1,
                    _class: 'com.example.demo.LumaKey'
                }
            ]
        }
    ],
    _class: 'com.example.demo.Project'
}

您很快想要在 LumaKey 和 ChromaKey 之间进行类型转换。所以 Java.

不行

你可以查看这个答案。我调试了你的代码,这就是你想要的。

编辑 1 您尝试删除 ChromaKey 并添加 LumaKey。

编辑 2

请调试这个 PersistentEntityResourceHandlerMethodArgumentResolver.java(第 158 行 [readPutForUpdate 方法])

在此方法中请求 json 和 mongo json 反序列化。 Object Mapper 映射它们。

当 mongodb 对象的 usks 字段类型为 ChromaKey 但您的请求类型为 LumaKey。

然后spring调用mapper方法进行映射

mapper.readerFor(target.getClass()).readValue(source);

对于此行,目标类型是 ChromaKey,但源类型是 LumaKey。

因此映射器无法将您的 ChromaKey 转换为 LumaKey。

我尝试编写自定义 JsonDeserializer,但它没有解决您的问题。也许你可以这样试试

我觉得是逻辑问题