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

PUT for nested abstract entity has unexpected results [DATAREST-869]

Open
#1,239 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

Michael S opened DATAREST-869 and commented

There's a problem when updating an abstract type in an entity using PUT. Imagine the following classes including getters/setters and a simple CrudRepository for Owner

@Entity
public class Owner {
    @Id
    @GeneratedValue
    private Integer id;
    
    private String name;
    
    @OneToOne(orphanRemoval = true, cascade = { CascadeType.ALL })
    @JoinColumn(name = "pet_id")
    private Pet pet;
}

//@formatter:off
@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.EXISTING_PROPERTY,
        property = "type"
)
@JsonSubTypes({
        @Type(value = Cat.class, name = Cat.TYPE),
        @Type(value = Dog.class, name = Dog.TYPE)
})
//@formatter:on
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class Pet {
    
    @Id
    @GeneratedValue
    private Integer id;
    
    private final String type;

}
@Entity
public class Dog extends Pet {
    
    public static final String TYPE = "dog";
    
    private String favoriteToy;
    
    public Dog() {
        super(TYPE);
    }

}
@Entity
public class Cat extends Pet {
    
    public static final String TYPE = "cat";
    
    private String nickName;
    
    public Cat() {
        super(TYPE);
    }

}

Creating (i.e. POSTing) an Owner with either Pet type works as expected, however changing the Pet and PUTting gets unexpected results (the type changes, but the value remains null), e.g. (pseudocode):

Owner o = new Owner(new Dog("a stick"));
Owner result = post(o);
assertEquals(o, result); // true, same owner, same pet

o.setPet(new Cat("Lucy"));
Owner result = putToExisting(o);
assertEquals(o, result); // false, cat.nickName is null

As far as I could figure out, this is because the doMerge method in DomainObjectReader doesn't check for different types, but only iterates over the existing (root) entity's properties and merges only their known properties.

I added a quickstart project with an AppTest that illustrates the problem. The easiest - but not really satisfying - workaround I could find was to use a Pet Array on JSON level, this way the value gets skipped in doMerge and just the new value gets used (uncomment the lines in Owner to let the AppTest pass)


Affects: 2.4.4 (Gosling SR4)

Attachments:

2 votes, 4 watchers

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.