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

发布于 2025-01-15 00:26:41 字数 7314 浏览 1 评论 0原文

我有一个名为 Project 的数据模型,它是通过 Spring Data Rest 从 Angular 应用程序进行生命周期的。 ProjectScene,场景有 UpstreamKey 保存在 Map>UpstreamKey 是一个抽象类,具有两个实现 ChromaKeyLumaKey。 (参见下面的代码)

当我在地图中使用 ChromaKey 编辑 (PUT) 和现有 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$1(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$6(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$000(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$1(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$7(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.access$100(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$1(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.

该文档明确讨论了替换 所以我想知道为什么会涉及一个名为 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

I have a data model called Project which is lifecycled from an Angular app via Spring Data Rest. The Project has Scenes, Scenes have UpstreamKeys held in a Map<Integer, UpstreamKey> and UpstreamKey is an abstract class with two implementations ChromaKey and LumaKey. (see code below)

When I edit (PUT) and existing Project with a ChromaKey in the map, and change that to a LumaKey, I get an error message in the backend:

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$1(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$6(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$000(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$1(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$7(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.access$100(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$1(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

My interpretation is that it is somehow trying to merge the LumaKey into the ChromaKey and - of course - that doesn't work. Why is it not simply replacing the bean? What would be a fix to get this working?

Here's the structure of the data model relevant to this questions:

@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";
    }
}

EDIT: Taken from the documentation at 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.

The documentation explicitly talks about replace so I am wondering why a method called mergeForPut is involved at all.

EDIT: Just realised my spring-boot-starter-parent was a bit old. I updated to 2.6.4 but the error remains.

EDIT: I just refactored everything so that scene.upstreamKeys can be a List instead of a Map just in case this might cause it. But it does not. Still the same error.

EDIT: As requested, I created a minimum project that replicates the error I am describing above: https://github.com/mathias-ewald/demo-sdr-target-bean-is-not-of-type

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

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

发布评论

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

评论(2

美男兮 2025-01-22 00:26:41

虽然我可以在 Spring Data MongoDB 中实现更新,但现在在 Spring Data Rest 中不可能实现。在当前的 Spring Data Rest 实现中,PUT 请求尝试通过调用 DomainObjectReader.mergeForPut(...) 来合并嵌套集合成员,这会因类型不匹配而失败,而不是替换它。请参阅此 github 问题拉取请求

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

编辑 2022-04-05

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

找到更新后的项目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'
}

Though I can achieve the update in spring data mongodb, it's not possible in spring data rest now. In current spring data rest implementation, a PUT request try to merge the nested collection member by calling DomainObjectReader.mergeForPut(...) which fails on type mismatch, rather than replace it. See this github issue and pull request.

So the workaround is implement a controller yourself.

Edit 2022-04-05

Annotate UpstreamKey with @Immutable make spring data rest perform replacement rather than merge. This solution only works if the nested collection member is just value (no id, no audit data, no @JsonIgnore to hide server side data) instead of entity (with id).

Find the updated project and the diff

Below is result mongo data after the put opertaion.

{
    _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'
}
同尘 2025-01-22 00:26:41

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

你可以检查一下这个答案。我调试你的代码,这就是你想要的。
https://stackoverflow.com/a/17004028/8909313

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

编辑2

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

在这个方法中request json和mongo json反序列化。对象映射器映射它们。

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

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

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

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

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

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

我认为这是一个逻辑问题。

Shortly you want to do type casting between LumaKey and ChromaKey. So it's not ok with Java.

You can check this answer. I debug your code and this is what you want.
https://stackoverflow.com/a/17004028/8909313

Edit 1
You try to delete ChromaKey and add LumaKey.

Edit 2

Please debug this
PersistentEntityResourceHandlerMethodArgumentResolver.java (line 158 [readPutForUpdate method])

In this method request json and mongo json deserialize. And Object Mapper map them.

When mongodb object's usks field's type is ChromaKey but your request's type LumaKey.

And then spring call mapper method for mapping.

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

for this line target's type is ChromaKey but source's type is LumaKey.

So mapper cannot cast your ChromaKey to LumaKey.

I try to write custom JsonDeserializer but it didn't solve your problem. Maybe you can try this way.

I think it's a logical problem.

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