Spring Data Rest - PUT is not working for associated reference types?
Asked Answered
R

2

6

I have the following domain class implemented for a Spring Data Rest project.

@Entity
@Data
public class Address {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private long addressID;

    private String houseName;

    private String apartmentNumber;

    @ManyToOne
    private City city;

    @ManyToOne
    private Country country; 

}

Now I am creating an Address resource by sending a POST with following JSON.

{   
    "houseName":"Some House",
    "apartmentNumber":"13 B",
    "city": "http://localhost:8080/city/1"
    "country":"http://localhost:8080/countries/1"
}

When I send a PUT request to the endpoint http://localhost:8080/addresses/1 with the following JSON, the values for houseName is updated. However the city remains unchanged even though I am sending a different URI for the city.

{   
    "houseName":"Another House",
    "apartmentNumber":"13 B",
    "city": "http://localhost:8080/city/2"
    "country":"http://localhost:8080/countries/1"
}

If I send a PATCH instead of PUT the city value is also updated. So how do I fix this?

UPDATE 1

Country class

@Data
@Entity
public class Country {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long countryID;

    private String countryName;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "country", orphanRemoval = true)
    private List<City> cities;

}

City class

@Data
@Entity
public class City {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private long cityID;

    private String cityName;

    @ManyToOne
    @JoinColumn(name = "country_id")
    private Country country;
}
Rombert answered 22/5, 2017 at 15:46 Comment(4)
You are passing String value in "city" and "country", but you have declared them as User defined data types City, Country..Krumm
But they are URIs so spring data rest will know how to dereference themRombert
Please share the country and city entity classes as well.Greatniece
@mephis-slayer I have updated the question with the city and country classesRombert
B
4

I had the same problem and manage to find some information on it.

It is a change in version 2.5.7 of Spring Data Rest and is "by purpose".

The answer of Oliver Drotbohm is:

I looked into this and I'd argue you're expecting things to work in a way they don't work. PUT requests don't consider associations to linkable resources, i.e. related resources that are pointed to by links. The reason for that is two-fold:

  1. If we consider URIs for association fields in the payload to update those associations, the question comes up about what's supposed to happen if no URI is specified. With the current behavior, linked associations are simply not a part of the payload as they only reside in the _links block. We have two options in this scenario: wiping the associations that are not handed, which breaks the "PUT what you GET" approach. Only wiping the ones that are supplied using null would sort of blur the "you PUT the entire state of the resource".
  2. For all the reasons mentioned in 1. there are dedicated assoctiation resources exposed that can be manipulated directly.

So it looks like that if you want to change both state of the resource plus associations at the same time, I guess exposing a dedicated resource to do that is the way to go.

Full answer you can find on Jira Spring site: Unable to update associated resource using PUT request on the item resource

(the question I wrote on stack overflow is here: Spring Data Rest - PUT on repository silently fails on child references)

Biskra answered 30/1, 2019 at 16:59 Comment(0)
G
1

If you're using Hibernate as your JPA provider, then you must let know how the entities are mapped in both the sides and indicate the how it is mapped in the child entity which will take care how the relationships are managed during a transaction.

EDITED and UPDATED:

// City Class

@Entity
public class City {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "city_id")
    private Long cityID;

    @Column(name = "city_name")
    private String cityName;

    @ManyToOne
    private Country country;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "city", orphanRemoval = true)
    private List<Address> addresses;
}

// Country Class

@Entity
public class Country {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "country_id")
    private Long countryID;

    @Column(name = "country_name")
    private String countryName;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "country", orphanRemoval = true)
    private List<City> cities = new ArrayList<>();;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "country", orphanRemoval = true)
    private List<Address> addresses;
}

USE PATCH: If you're updating part of the resource, subset of the resource and relationships

USE PUT: If you're replacing the resource with an entirely new representation

Greatniece answered 22/5, 2017 at 17:55 Comment(1)
Updated the answer. Plese check and let know.Greatniece

© 2022 - 2024 — McMap. All rights reserved.