11

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.

rotmajster
  • 111
  • 1
  • 6

1 Answers1

5

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.

Mathias Dpunkt
  • 11,594
  • 4
  • 45
  • 70
  • Thank you very much, Mathias, for your comprehensive post. My problem is, like I mentioned, that I use Sets and not Lists (@OrderColumn does not work with Set). Let me ask you a personal question: don't you think, that using "index" instead of "id" of real entity with easy rest query is quite "strange" solution? I know, this comes with JsonPatch main purpose (to modify json)... but anyway, I am curious, if this can be done in different way. – rotmajster Jan 19 '16 at 22:36
  • I am trying out your solution for adding an item to the list and I am receiving several errors: doing`"op": "add", "path": "/manies/-", "value":{"name":"4"}`to a list that has no entries gives me `o.s.e.s.SpelEvaluationException: EL1004E: Method call: Method size() cannot be found on One type`, so I tried using `/manies`, which gave me `org.springframework.data.rest.webmvc.json.patch.PatchException: Could not read {"name":"4"} into class org.hibernate.collection.internal.PersistentBag!` Any ideas? – joshwa Mar 31 '17 at 18:43