Modify @OneToMany entity in Spring Data Rest without its repository

rotmajster picture rotmajster · Jan 17, 2016 · Viewed 7.8k times · Source

In my project I use object of type A which has OneToMany relation (orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER) to objects of type B. I need SpringDataRest (SDR) to store complete full A object with its B objects (children) using single one POST request. I tried several combinations in SDR, the only one which worked for me, was to create @RepositoryRestResource for object A and to create @RepositoryRestResource also for object B, but mark this (B) as exported=false (if I did not create repository out of object B at all, it would not work -> just A object would be stored on single POST request, but not its children (@OneToMany relation) of type B; the same outcome occurs if exported=false is omitted for B repository). Is this ok and the only way how to achieve it (single POST request with storing all objects at once)?

The reason I'm asking, in my previous example, I have to (I would like to) control all objects "lifecycle" by using A's repository. I am ok with it, because A->B relation is composition (B does not exists outside of A). But I have serious problem of editing (also removing) one certain object of type B by SDR using its parent repository (since object B doest not have its own repository exported). Maybe, this is not possible by definition. I have tried these solutions:

  • PATCH for "/A/1/B/2" does not work -> method not allowed (in headers is "Allow: GET, DELETE") -> so, also PUT is out of question
  • Json Patch would not work either - PATCH for "/A/1" using json patch content-type [{"op": "add", "path": "/B/2", ....}] -> "no such index in target array" - because Json Patch uses scalar "2" after "array" as a index to its array. This is not practical in Java world, when relations are kept in Set of objects - indexing has no meaning at all.
  • I could export repository (exported=true) of object B for manipulating it "directly", but this way I would loose ability to store the whole object A with its B objects at one single POST request as I have mentioned before.

I would like to avoid sending the whole A object with one single tiny modification of its B object for PUT, if possible. Thank you.

Answer

Mathias Dpunkt picture Mathias Dpunkt · Jan 18, 2016

I managed to change the child entity as follows. As a sample I used the following entities:

@Entity
@Data
@NoArgsConstructor
public class One {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(cascade = ALL)
    private List<Many> manies = new ArrayList<>();

}

@Entity
@Data
@NoArgsConstructor
public class Many {

    public Many(String name) {
        this.name = name;
    }

    @Id
    @GeneratedValue
    private Long id;

    private String name;
}

I just have a repository for One exposed.

(My examples use the excellent httpie - CLI HTTP client)

Removing an item using json patch

This example will remove the second item in the manies list. You could use @OrderColumn to make sure that you can rely on the order of list items.

echo '[{"op":"remove", "path":"/manies/1"}]' | http PATCH :8080/ones/1 Content-Type:application/json-patch+json -v

PATCH /ones/1 HTTP/1.1
Content-Type: application/json-patch+json

[
    {
        "op": "remove", 
        "path": "/manies/1"
    }
]

Replacing the entire list using json patch

This sample replaces the list with the array specified in the value.

echo '[{"op":"add", "path":"/manies", "value":[{"name":"3"}]}]' | http PATCH :8080/ones/1 Content-Type:application/json-patch+json -v

PATCH /ones/1 HTTP/1.1
Accept: application/json
Content-Type: application/json-patch+json

[
    {
        "op": "add", 
        "path": "/manies", 
        "value": [
            {
                "name": "3"
            }
        ]
    }
]

Adding an item to the list using json patch

This sample adds an item to the end of a list. Also here the client just needs to know the length of the list before the update. So the order does not really matter here.

echo '[{"op":"add", "path":"/manies/-", "value":{"name":"4"}}]' | http PATCH :8080/ones/1 Content-Type:application/json-patch+json -v

PATCH /ones/1 HTTP/1.1
Accept: application/json
Content-Type: application/json-patch+json

[
    {
        "op": "add", 
        "path": "/manies/-", 
        "value": {
            "name": "4"
        }
    }
]

Hope this helps.