How to send POST request with Many-to-many relationship in Spring? - spring

I'm trying to add a order with equipment list, here's my entities:
the order entity
#Entity #Table(name = "orders") public class Order extends Ticket{
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private Set<OrderEquipment> orderEquipments = new HashSet<>();}
the equipment entity
#Entity #Table(name = "equipments") public class Equipment extends DateAudit {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Size(max = 30)
private String name;
#NotNull
private Long nbr_piece ;
#OneToMany(mappedBy = "equipment", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<OrderEquipment> orderEquipments = new HashSet<>();}
and the order_equipment entity
#Entity #Table(name = "order_equipment") public class OrderEquipment extends DateAudit { #Id
#ManyToOne
#JoinColumn(name = "order_id")
private Order order;
#Id
#ManyToOne
#JoinColumn(name = "equipment_id")
private Equipment equipment;
#NotBlank
#Column(name = "quantity")
private Long quantity;}
here is the add function in the orderController
#PostMapping("/orders")
public Order createOrder(#Valid #RequestBody Order Order){
Order.setObservateurEmail(Order.getObservateurEmail());
Order.setObject(Order.getObject());
Order.setDescription(Order.getDescription());
return orderRepository.save(Order);
}

I have seen a mistakes there, lemme try to help you. Since you issue is not clear, please lemme know if it does/does not work:
You have two bidirectional mappings there:
Order (with ALL cascade) <-> OrderEquipment
Equipment (with ALL cascade) <-> OrderEquipment
You are using #JoinColumn for both of them, even though they are bidirectional. Please take a look at this. You should always use the mappedBy attribute when defining bidirectional relationships.
Now, you are receiving an Order object from a POST request, making changes to 3 attributes and then saving it. Since the mapping between the Order and OrderEquipment have the CascadeType.ALL attribute, any save on the Order object will save all OrderEquipment children associated. If the Order object you are receiving already have OrderEquipment children, your method will also save/update them.
Your POST mapping looks good to me, just take care with your table relationship definitions.
Take a look at this answer to check how a lits of entities should be formatted on a JSON POST.

Related

Hibernate mapping user relation to entities

Let's se we have Hibernate entity User with basic fields such as username, password, roles etc..
Now we have an entity such as Car.
User has a OneToOne relationship with Car, cause he can own a car. But he also has besides this a OneToMany relationship to Car, because he also owns the cars of his children. But in the frontend I want to know which cars he owns for himself and which cars he owns for his children. The same applies to the relationship between User and motorbike (his own, his childrens, etc...)
How would the User entity class look like? Is it good to have the relationships mapped in an "Helper" entity such as UserData:
#Entity
#Data
#Table( name = "users",
uniqueConstraints = {
#UniqueConstraint(columnNames = "username")
})
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Size(max = 150)
private String username;
#NotBlank
#Size(max = 120)
private String password;
#OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinColumn(name = "USER_DATA_ID")
private UserData userData;
UserData:
#Entity
#Data
#Table( name = "user_data")
public class UserData {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinColumn(name = "OWN_CAR_ID")
private Car ownCar;
#OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinColumn(name = "PARTNER_CAR_ID")
private Car partnerCar;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable( name = "user_children_cars",
joinColumns = #JoinColumn(name = "user_data_id"),
inverseJoinColumns = #JoinColumn(name = "car_id"))
private Set<Car> childrenCars = new HashSet<>();
public boolean addToChildrenCarSet(Car c) {
return childrenCars.add(c);
}
public UserData() {
}
}
As you ask for an opinion, I would say it gets unnecessary complicated if you use the intermediate entity user_data. :-) There is no real drawback to add more fields and keys into the user class - performance is probably also better then using the EAGER fetching. If performance is an issue, better optimize querys later on then splitting the table now.
Also the #ManyToMany I would avoid - better create the intermediate table and relations yourself. You can check out https://bootify.io and create your database schema there. There is no EAGER fetching and also no CascadeType.ALL (both only good ideas in special cases), you would probably add more problems with that then actual helping in any way.
So the addToChildrenCarSet method would end up in a #Service class, in a method with #Transactional, in my proposal.

spring data - how to make unique constraint with custom logic?

using spring data, I created User 1:N UserDog N:1 Dog relation. Both 1:N relations are unidirectional #ManyToOne with UserDog being the custom relation table.
User entity:
#Entity
public class User {
#Id
#GeneratedValue
private long id;
#Column(nullable = false)
private String name;
}
Dog entity:
#Entity
public class Dog {
#Id
#GeneratedValue
private long id;
#Column(nullable = false)
private String name;
}
User dog relation table:
#Entity
public class UserDog {
#Id
#GeneratedValue
private long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn
#OnDelete(action = OnDeleteAction.CASCADE)
private User user;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn
#OnDelete(action = OnDeleteAction.CASCADE)
private Dog dog;
#Column(nullable = false)
private Instant createdOn = Instant.now();
#Column
private Instant disabledOn;
}
Use case
Use case is to store history of User-Dog bindings, where the concrete Dog can be bound only to one User at the time. That's why I added createdOn and disabledOn columns to UserDog. disabledOn being null indicates that the relation is active and the Dog can't be assigned another User. If disabledOn is not null, then the record is stored only for evidence purposes and the Dog can be assigned to the same or another User again.
Question
How to ensure that the combination of Dog's id and disabledOn being null is unique in UserDog table?
In pseudo code I want something like this:
#Entity
#UniqueConstraint({#UniqueConstraint(this.dog.id), #NullConstraint(this.disabledOn)})
public class UserDog {...}
You can simply create a unique constraint for dogId and disabledOn.
It does add the limitation that no two relationships may end at the same time but this seems to fit your use case.

JPQL query / JPA / Spring boot best practice of updating many to many table

I have a user table and a city table and I have a connecting table users_cities (columns: id, user_id, city_id). A user can follow multiple cities.
From the client side i send an array of cityIds. Some might be new some might still be selected and some might have been deselected.
What is a best practice to update the users_cities table with the data? Should I just delete everything for that particular userId and insert the new array or ... ?~
Also, how does one delete and repsectively insert the data in bulk, for a many to many reference?!
#Getter
#Setter
#NoArgsConstructor
#ToString
#Accessors(chain = true)
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true)
private String email;
private String password;
private Boolean isGuest;
#ManyToMany(fetch = FetchType.EAGER)
private Set<Role> roles;
#ManyToOne()
#JoinColumn(name = "country_following_id")
private Country countryFollowing;
#ManyToMany()
private Set<City> citiesFollowing;
#ManyToMany()
private Set<Offer> offersFollowing;
}
and
#Getter
#Setter
#NoArgsConstructor
#ToString
#Accessors(chain = true)
#Entity
#Table(name = "cities")
public class City {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#NonNull
private Long id;
private String countryCode;
private String name;
#Latitude
private Double latitude;
#Longitude
private Double longitude;
}
Should I just delete everything for that particular userId and insert the new array
Since the relation connecting users and cities does not have an identity of its own, that sounds reasonable
Also, how does one delete and repsectively insert the data in bulk, for a many to many reference?
Just clear the User.citiesFollowing collection and populate it anew (hint: since you have cityIds at the ready, you can use EntityManager.getReference() to load the cities)

Register data into Many-to-Many Relation Table

I have 'Course' and 'Student' entities. They have many-to-many relation. So, i have COURSE_STUDENT(contains 'student_id' and 'course_id' columns) table. I want to register students to courses with a button.(For example; a student lists courses and click Register button to register a specific course).
When i want to create new courses, i use courseRepository and courseMapper which comes from JHipster by default.
But i don't have repository and mapper files for COURSE_STUDENT. Because it is not actually a main entity. It is created for many-to-many relation.
How can i register students to courses?
Git repo:https://github.com/canberkizgi/monolithic-mucs
My course entity:
#Entity
#Table(name = "course")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Course implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Column(name = "title", nullable = false)
private String title;
#Column(name = "description")
private String description;
#ManyToOne
private Instructor instructor;
#ManyToMany(fetch = FetchType.EAGER)
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JoinTable(name = "course_student",
joinColumns = #JoinColumn(name="courses_id", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="students_id", referencedColumnName="id"))
private Set<Student> students = new HashSet<>();
Student entity:
#Entity
#Table(name = "student")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne
#JoinColumn(unique = true)
private User user;
#ManyToMany(fetch = FetchType.EAGER,mappedBy = "students")
#JsonIgnore
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<Course> courses = new HashSet<>();
For example; Createcourse function with Mapper and Repository
#PostMapping("/courses")
#Timed
public ResponseEntity<CourseDTO> createCourse(#Valid #RequestBody CourseDTO courseDTO) throws URISyntaxException {
log.debug("REST request to save Course : {}", courseDTO);
if (courseDTO.getId() != null) {
return ResponseEntity.badRequest().headers(HeaderUtil.createFailureAlert(ENTITY_NAME, "idexists", "A new course cannot already have an ID")).body(null);
}
Course course = courseMapper.toEntity(courseDTO);
course = courseRepository.save(course);
CourseDTO result = courseMapper.toDto(course);
return ResponseEntity.created(new URI("/api/courses/" + result.getId()))
.headers(HeaderUtil.createEntityCreationAlert(ENTITY_NAME, result.getId().toString()))
.body(result);
}
The relationship is owned by the course entity. Thats because on the student side the #ManyToMany annotation has a mappedBy attribute. This means, that the database will reflect the set in the course. You need to add students to that set to save the relationship. That change needs to be done within a transaction.
That being said it would probably be best to follow DDD here. I would create a registerTo method in the student class that would take the course as a parameter. I would then call this.courses.add(course) and course.getStudents().add(this) in that method.

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

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

Resources