Deleting an association over REST in Spring Data REST+HATEOAS
Asked Answered
U

2

5

I wish to know how to delete a many-to-many association via a REST call. I am able to create records, and associated them, but do not understand how to delete.

I have a Spring Boot project where i'm using REST and HATEOAS to by pass Services and Controllers and expose my Repository directly.

I have a User Model/Domain class

@Entity
@Table(name = "usr")
public class User implements Serializable {

private static final long serialVersionUID = 1L;

@Version
private long version = 0;

@Id
@GeneratedValue(generator="optimized-sequence")
private Long id;

@Column(nullable = false, unique = true, length = 500)
@Size(max = 500)
private String userName;

@Column(nullable = false, length = 500)
@Size(max = 500)
private String firstName;

@Column(nullable = false, length = 500)
@Size(max = 500)
private String lastName;

@ManyToMany(    fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinTable( name="user_role",
            joinColumns={ @JoinColumn(  name = "user_id", 
                                        nullable = false
                                    ) 
                        }, 
            inverseJoinColumns={ @JoinColumn(   name="role_id", 
                                                nullable=false
                                            ) 
                                }
)
private Set<Role> roles = new HashSet<Role>(0);

...Getters/Setters Below...

As as you can see, I have a roles member that is Many-To-Many association with Role class, of which the code is such:

@Entity
public class Role {

@Id
@GeneratedValue(generator="optimized-sequence")
private Long id;

@Column(nullable = false)
private String name;

@Column(nullable = false)
private String description;

...Getters/Setters Below...

My repositories look like so:

UserRepository

public interface UserRepository extends 
        JpaRepository<User, Long>, JpaSpecificationExecutor<User> {

    List<User> findByUserName(String username);

}

RoleRepository

public interface RoleRepository 
        extends JpaRepository<Role, Long> {

}

Now, all is well. When I access the project root from a browser, I get the repository index/directory in JSON+HAL format. Wonderful.

(Note I'm remove the http:// part from the test below because StackOverflow is counting it towards my links quota)

I, using WizTools REST Client, HTTP.POST to the Role ( localhost:8080/resttest/roles ) repository and create a new Role. Success, Role ID #4 created.

Then I POST to the User repository to create a User ( localhost:8080/resttest/users ). Success, User ID #7 created.

Then I PUT to the User repository to create an association with the role:

PUT localhost:8080/resttest/users/7/roles
Content-type: uri-list
Body: localhost:8080/resttest/roles/4

Great! Association made. User 9 is now associated with Role 4.

Now I can't for the life of me figure out how to DELETE this association.

I'm sending an HTTP DELETE instead of PUT with the same command as above.

DELETE localhost:8080/resttest/users/7/roles
Content-type: uri-list
Body: localhost:8080/resttest/roles/4

I get back: HTTP/1.1 405 Method Not Allowed

{
    "timestamp":1424827169981,
    "status":405,
    "error":"Method Not  Allowed",
    "exception": "org.springframework.web.HttpRequestMethodNotSupportedException",
    "message":"Request method 'POST' not supported"
}
Ulane answered 25/2, 2015 at 4:58 Comment(1)
What is the solution to this please!?Lx
I
9

Although creating a PUT request with remaining elements does the trick, DELETE is an accepted command to delete an association resource and is in most cases easier to use.

As for your example, this should work:

DELETE localhost:8080/resttest/users/7/roles/4

On a separate note, when creating the association, it is expected to have URIs in the payload. You shouldn't need to write the whole URL in the body, this should be enough:

PUT localhost:8080/resttest/users/7/roles
Content-type: uri-list
Body: /roles/4

Hope this helps.

Insane answered 24/11, 2016 at 11:20 Comment(1)
This is inconvenient, since all other requests (put, post, get) relies on links not ID's. To perform a delete code need to be written to extract the id from the object _link.Penholder
D
2

From the docs:

DELETE

...

405 Method Not Allowed - if the association is non-optional

PUT means that you replace the whole roles Set. So to remove a single link you PUT all remaining links. If you only have a single link and want to remove it you would PUT an empty collection:

PUT localhost:8080/resttest/users/7/roles
Content-type: uri-list
Body:

BTW: You won't send a body with a DELETE request. It doesn't make sense.

EDIT

See also this answer from the developer of Spring HATEOAS.

Dorser answered 25/2, 2015 at 9:22 Comment(7)
Thank you for replying. That explains a lot. I have two follow up questions. In situations where the association maybe numerous, is there a way to just DELETE one instead of PUT a whole set without the one no longer needed? My other question is, what is a non-optional association?Ulane
I cant edit the comment anymore. Just want to clarify. Not saying DELETE one as in it has to be a DELETE http method, it can be any method, as long as it facilitates removing just one association instead of entire set without one you need. Or do I have to make a repository for that bridge table?Ulane
@Ulane You can use POST to add a single URI, but there is no convenient way to remove a single URI. Non-optional means that there has to be a value. Usually that's the case when a column is not nullable. I guess a collection is semantically always non-optional: It may be empty but it's always there.Dorser
@Ulane #28680434Dorser
Thanks so much, zeroflagL. I searched high and low before posting here, and the link you gave I saw as well. Of course since there were no "answers" in that thread, I gave up after reading the comments and finding nothing about deleting (glimpsed his comments, I noticed he was talking about PATCH/PUT and I was chasing DELETE, so I closed it in frustration). Almost every curl example out there shows vanilla adding and deleting and as far as associations go, they only show adding. I became so frustrated that I had to post here. You've been a great help.Ulane
@Ulane same situation. Hypothetically, if I have a collection of 10,000 entities in relation to my domain and the user deletes 1, I have to submit 9,999 URIs. Seems counter intuitive.Domiciliate
The answer by Oliver Gierke is about removing all the items of the list. To remove one element, DELETE is a better command. This might have changed in almost two years, so I remove my downvote.Insane

© 2022 - 2024 — McMap. All rights reserved.