Spring Data Rest - PUT is not working for associated reference types? - spring

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;
}

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:
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".
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)

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

Related

Troubles with Bidirectional One-To-One JPA

I'm coding a CRUD JPA web application. My goal is that a given parent Vehicle can only have a single child Driver, but during runtime this same Driver can instead be assigned to another Vehicle and vice versa. To my understanding, this could be accomplished via an OneToOne relationship.
I've tried some different approaches, but to no success. I can assign a Vehicle to a Driver just fine, but when I try to update/create a new Vehicle and give him a Driver, via controllers, nothing happens. I can only do it the other way around. I'm assuming this is because Vehicle is the parent and I can only create a new relation by updating a parent.
My question is, is it possible to make these updates bidirectional and how can I achieve that?
I've tried using a shared primary key, using a foreign key, using a join table. The result is always the same and I can't quite grasp why. I have an OneToMany relationship working on this application and it works as I expect it to work. I can update on one side or the other, delete on one side or the other. Both entities have been updated. OneToOne? Parent seems to have all the power.
This is what I'm working with right now:
Driver
#Entity
#Table(name= "drivers")
public class Driver {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(nullable = false)
private String name;
#Column(nullable = false)
private int age;
#OneToOne(mappedBy = "driver")
private Vehicle vehicle;
Vehicle
#Entity
#Table(name= "vehicles")
public class Vehicle {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(nullable = false)
private String make;
#Column(nullable = false)
private String model;
#Column(nullable = false)
private int mileage;
#Column(nullable = false)
private int year;
#Column(nullable = false)
private int fuel;
#OneToOne
#JoinColumn(name = "driver_id")
private Driver driver;
And just for reference, this is the OneToMany relationship I have and that I'm happy with. I'd like my OneToOne to have the same behavior, except I don't need to save a list of entities, only one.
#Entity
#Table(name="stops")
public class Stop {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(nullable = false)
private String name;
#ManyToOne
#JoinColumn(name="route_id")
private Route route;
#Entity
#Table(name="routes")
public class Route {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(nullable = false)
private String name;
#OneToMany
#JoinColumn(name = "route_id")
private List<Stop> stops = new ArrayList<>();
Any tips would be appreciated, thank you for your time.

JPA repository findBy foreign key for ManyToMany relationship

I have the following relationship in the data base
Database Diagram
And these are the entities
Apartment.java
#Entity
#Table(name = "apartment")
#Data
public class Apartment {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_apartment")
private long id;
#ManyToMany
#JoinTable(
name = "apartment_facility",
joinColumns = #JoinColumn(name = "id_apartment"),
inverseJoinColumns = #JoinColumn(name = "id_facility"))
#Column(name = "city")
private String city;
#Column(name = "country")
private String country;
}
Facility.java
public class Facility {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_facility")
private long id;
#Column(name = "name")
private String name;
}
My question is how can I create a findBy method in JpaRepository in order to find all Apartments having the required facilities
I'm thinking of the result of this request:
api/apartments/findByFacilities?facilities=gym,pool,parking
I have tried to do a query but couldn't quite get it. Also tried using Jpa as following
List<Apartment> findByFacilities(#RequestParam("facilities") List<String> facilities);
But I'm getting the following error. Is there a walkaround?
Failed to convert from type [java.lang.String] to type [java.lang.Long]
In your first code block List <Facility> facilities = new ArrayList<>(); I guess you forgot to add the code line.
When I look at your link below, I understand that you want to search by the name of the facility.
api/apartments/findByFacilities?facilities=gym,pool,parking
For this, your code in Repository should be as follows:
List<Apartment> findDistinctByFacilitiesNameIn(List<String> facilities);
If we don't add distinct supported keyword here, each record repeats as many as the facilities it contains.
If you want to search differently like, not like, etc., See the supported keywords inside method names table here: Query Creation document.

How should i define which Hibernate mapping to use, and when/where to use it?

i and having a use case where there are 2 kinds of users namely the "clients" and "professionals". These 2 entities have a parent entity called the "users" where each "user" has one entry in either "client/professional" depending on their role.
Let's consider a "clients".
A "user" has a one-to-one mapping with a "client"
A client might have several "companies" under him, i.e "client" has one-to-many relationship with "companies".
I am creating a REST API for this use case using spring boot. I still dont have any idea about why i should be using mapping in Hibernate. So far the only advantage i see is that, the CASCADING property of it. If a "user" gets removed, all the tables having the "user-id" will also be flushed. But consider a scenario where i need to add "companies" for a "client". I am confused to whether i should persist "companies" via "clients" entity or should i directly persist to "client" entity. I dont see any major advantage here because in both cases we are checking whether a "client" exists with the given ID before persisting in the "clients" table.
User Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long UID;
private Integer userRoleId;
private String username;
private String email;
private String phoneNumber;
private String firstName;
private String lastName;
private Long dateOfJoin;
private Boolean activeStatus;
private Long createdAt;
private Long updatedAt;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "user")
private Client client;
}
Client Entity
public class Client {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long CID;
#Column(unique = true)
private Long userId;
private Long createdAt;
private Long updatedAt;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "client")
private ClientCompany clientCompany;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "userId" ,referencedColumnName = "UID", insertable = false, updatable = false)
private User user;
}
Client Company Entity
public class ClientCompany {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long CCID;
private Long clientId;
private String email;
private String phoneNumber;
public String streetAddress1;
public String streetAddress2;
public String zipCode;
public String city;
public String state;
public String country;
private Long createdAt;
private Long updatedAt;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "clientId", referencedColumnName = "CID", insertable = false, updatable = false)
private Client client;
}
The advantage of using Hibernate/JPA is that you do not need to code JDBC calls.
You just use objects.
In your scenario,
load a Client instance from the database;
create a ClientCompany object;
assign the Client instance to it (no need to check the client existence since you loaded it from the database);
save to database.
Hibernate will take care of everything without you writing any SQL statements.
Step 1) can also be replaced with creating a new Client that will be saved to the database, but again Hibernate will handle saving correctly (if you configured it correctly).

Can't delete child entity without deleting parent entity, regardless of CascadeTypes?

I'm trying to connect an entity (User) to entities they create which will be Surveys.
I have two repositories, one UserRepository and one SurveyRepository. I can load Surveys according to which User has them and currently they are all mapped by the User_ID, which is a field on the Survey entity.
However, when I try to remove a Survey, this removes my User whenever I define CascadeType.ALL.
But when I don't use that, I get another error "Caused by: java.sql.SQLIntegrityConstraintViolationException:"
I'm gussing this is all related to the password encryption I'm using, but I am not even trying to delete the User entity, I'm just deleting the Survey, which holds a reference, or an ID to the Survey..
I've tried CascadeType.All on both sides, and I've tried not having any CascadeType at all as well.. If I have it on both sides, this deletes the user whenever I tell my surveyRepository.delete(currentSurvey);
And whenever I don't have it on both sides, I get the exception above..
User Entity:
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private Long id;
#NotEmpty
#Email
#Column(unique = true)
private String email;
private String password;
#NotBlank
private String username;
#NotBlank
private String firstName;
#NotBlank
private String lastName;
#NotBlank private String role;
#OneToMany(fetch = FetchType.EAGER)
private Set<Survey> surveys = new HashSet<>();
Survey Entity:
#Entity
#Table(name = "survey")
public class Survey {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "survey_id")
private Long id;
private String title, creator, description;
private LocalDate date = LocalDate.now();
#OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "survey_id")
#OrderBy("position ASC")
private Set<Question> questions = new HashSet<>();
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id")
private User user;
I'm just not sure how I can tell JPA/Hibernate not to touch the User whenever we delete the Survey.
It doesn't matter if I save the User with Survvey or not does it?
Basically I've tried a lot of options and I figure I'm not quite grasping the issue, and I suspect it's about the annotations on the User side, but I still feel as if I should be able to delete the child entity with no problem at all since I am not touching the parent entity?
This is because of EAGER fetch type in User class for surveys.
You delete survey but because it is existed on surveys set in user yet, it wouldn't be deleted actually.
You need to do like this:
// User class
#OneToMany(cascade=CascadeType.ALL, orphanRemoval=true, mappedBy="user")
private Set<Survey> surveys = new HashSet<>();
//Survey class
#ManyToOne
#JoinColumn(name = "user_id")
private User user;

Links to Embedded entities in Spring Data Rest

I have the following entities defined in my project:
Country
#Entity
#Data
public class Country {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
#Column(nullable = false)
String name;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
List<City> cities = new ArrayList<City>();
}
City
#Entity
#Data
public class City {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
#Column(nullable = false)
String name;
#ManyToOne
Country country;
}
Person
#Entity
#Data
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
#Column
String name;
#Embedded
Address address = new Address();
}
Address
#Data
public class Address {
#Column
String line;
#ManyToOne
Country country;
#ManyToOne
City city;
}
I have also repositories defined for Person, Country and City.
When I make a GET request to /persons/1 I get the following result:
{
"name":null,
"address":{
"line":"Address1"
},
"_links":{
"self":{
"href":"http://localhost:8080/persons/1"
},
"city":{
"href":"http://localhost:8080/persons/1/city"
},
"country":{
"href":"http://localhost:8080/persons/1/country"
}
}
}
I suspect that since address is an embedded object, the generated links to country and city are wrong. They don't return anything although city and country values are present. What should the correct links be?
Are embedded objects not supported by Spring Data Rest?
Possible solutions:
move associations to the parent entity
promote the embeddable into a separate entity resource
add ResourceProcessor to remove those links
add a custom controller to handle those links
UPDATE: This seems to be already fixed in Spring-DATA-REST v2.1. See DATAREST-262.

Resources