spring-projects / spring-projects/spring-data-rest

Unable to make a PUT for entity with OneToMany collection [DATAREST-1169]

Open
#1,535 1 comment 0 reactions 1 assignee View on GitHub

@odrotbohm is already working on this.

Since Dec 31, 2020.

in: repository type: bug
Dominant language
Java
Stars
958
Forks
568
PR merge metrics
No merged PRs in 30d

Description

Slawek Mazur opened DATAREST-1169 and commented

Having a very simple domain like this:

@Entity
@Getter
@Table(name = "project")
public class Project extends AbstractEntity {

    @NotEmpty
    private String name;

    @Column(unique = true)
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "project")
    @OrderBy("position ASC")
    private final List<Module> modules = new ArrayList<>();

    Project(String name, Module... modules) {
        this.name = name;
        this.modules.addAll(Arrays.asList(modules));
    }

    Project() {
        this("", new Module[0]);
    }

    public void orderModules() {
        int position = 0;
        for (Module module : modules) {
            module.onPosition(position++);
        }
    }
}
@Data
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) // not the best choice ?
@Table(name = "module")
@NoArgsConstructor(force = true)
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
abstract class Module
    extends AbstractEntity {

    @ManyToOne
    @JoinColumn(name = "project")
    private Project project;

    @Column(name = "content", columnDefinition = "text")
    private String content;

    abstract String getType();

    private int position;

    public void onPosition(int position) {
        this.position = position;
    }
}
@Data
@Entity
@NoArgsConstructor(force = true)
@EqualsAndHashCode(callSuper = true)
class Article extends Module {

    @Override
    public String getType() {
        return "Article";
    }
}
@Data
@Entity
@NoArgsConstructor(force = true)
@EqualsAndHashCode(callSuper = true)
class Footer extends Module {

    @Override
    public String getType() {
        return "Footer";
    }

    @Override
    public void onPosition(int position) {
        super.onPosition(Integer.MAX_VALUE);
    }
}
@Data
@Entity
@NoArgsConstructor(force = true)
@EqualsAndHashCode(callSuper = true)
class Header extends Module {

    @Override
    public String getType() {
        return "Header";
    }

    @Override
    public void onPosition(int position) {
        super.onPosition(Integer.MIN_VALUE);
    }
}
@MappedSuperclass
@Getter
@ToString
@EqualsAndHashCode
public class AbstractEntity implements Identifiable<Long> {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private final Long id;

    @Version
    private Long version;

    protected AbstractEntity() {
        this.id = null;
    }

    @CreatedDate
    @Column(name = "created_at", nullable = false)
    @JsonIgnore
    private Instant createdAt = Instant.now();

    @LastModifiedDate
    @Column(name = "updated_at")
    @JsonIgnore
    private Instant updatedAt = Instant.now();
}
@RepositoryRestResource
interface ProjectRepository extends PagingAndSortingRepository<Project, Long> {}

I do POST to create new Project with few modules

{
  "name": "Test project",
  "modules": [
    {
      "type": "Header",
      "content": "Header - content"
    },
    {
      "type": "Article",
      "content": "Article - content"
    },
    {
      "type": "Footer",
      "content": "Footer - content"
    }
  ]
}

and then when I do PUT to update it

{
  "name": "Real project",
  "modules": [
    {
      "id": "1",
      "type": "Header",
      "content": "Header - content"
    },
    {
      "id": "2",
      "type": "Article",
      "content": "Article 1 - content"
    },
    {
      "id": "3",
      "type": "Article",
      "content": "Article 2 - content"
    },
    {
      "id": "4",
      "type": "Article",
      "content": "Article 3 - content"
    },
    {
      "id": "5",
      "type": "Footer",
      "content": "Footer - content"
    }
  ]
}

org.springframework.http.converter.HttpMessageNotReadableException: Could not read an object of type class com.example.Project from the request!; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: Could not read payload!; nested exception is java.lang.IllegalArgumentException: Target bean of type com.example.Article is not of type of the persistent entity (com.example.Footer)!
at org.springframework.data.rest.webmvc.config.PersistentEntityResourceHandlerMethodArgumentResolver.readPutForUpdate(PersistentEntityResourceHandlerMethodArgumentResolver.java:228) ~[spring-data-rest-webmvc-2.6.9.RELEASE.jar:na]
at org.springframework.data.rest.webmvc.config.PersistentEntityResourceHandlerMethodArgumentResolver.read(PersistentEntityResourceHandlerMethodArgumentResolver.java:194) ~[spring-data-rest-webmvc-2.6.9.RELEASE.jar:na]
at org.springframework.data.rest.webmvc.config.PersistentEntityResourceHandlerMethodArgumentResolver.resolveArgument(PersistentEntityResourceHandlerMethodArgumentResolver.java:141) ~[spring-data-rest-webmvc-2.6.9.RELEASE.jar:na]
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121) ~[spring-web-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:158) ~[spring-web-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:128) ~[spring-web-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97) ~[spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827) ~[spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738) ~[spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) ~[spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967) ~[spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901) ~[spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) [spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doPut(FrameworkServlet.java:883)

Complete project with test case attached.


Affects: 2.6.9 (Ingalls SR9)

Attachments:

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.