Spring JPA one-to-many relationship return null on update - spring

I have a one-to-many relationship between AcademicYear and subject (One AcademicYear has many Subjects).
Here is the model for AcademicYear:
package com.sms.entity;
import javax.persistence.*;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import org.hibernate.annotations.UpdateTimestamp;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.Date;
import java.util.Set;
#Entity
#Setter
#Getter
#Table(name = "academic_years")
#NoArgsConstructor
#AllArgsConstructor
public class AcademicYear {
public AcademicYear(long id, String name, Date updatedAt) {
this.id = id;
this.name = name;
this.updatedAt = updatedAt;
}
#Schema(description = "Unique identifier of the academic year.", example = "1")
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Schema(description = "Name of the Academic Year.", example = "First Year Primary", required = true)
#Column(name = "name")
private String name;
#JsonManagedReference
#OneToMany(mappedBy="academicYear", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Set<Subject> subjects;
#UpdateTimestamp
#Column(name = "updated_at", columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
private Date updatedAt;
public Set<Subject> getSubjects() {
return subjects;
}
public void setSubjects(Set<Subject> subjects) {
this.subjects = subjects;
}
}
And the model for Subject:
package com.sms.entity;
import javax.persistence.*;
import com.fasterxml.jackson.annotation.JsonBackReference;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.util.Date;
#Table(name = "subjects")
#Entity
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class Subject {
public Subject(long id, String name, Date updatedAt) {
this.id = id;
this.name = name;
this.updatedAt = updatedAt;
}
#Schema(description = "Unique identifier of the subject.", example = "1")
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Schema(description = "name of the subject.", example = "Mathematics-2")
#Column(name = "name")
private String name;
#ManyToOne(cascade = {CascadeType.ALL})
#JsonBackReference
#JoinColumn(name="academic_year_id", nullable=false)
private AcademicYear academicYear;
#UpdateTimestamp
#Column(name = "updated_at", columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
private Date updatedAt;
}
When I try to update name attribute for AcademicYear, I send a PUT request with the following body:
{
"id": 2,
"name": "Second Year"
}
I got the following response:
{
"id": 2,
"name": "Second Year",
"subjects": null,
"updatedAt": "2020-03-27T18:01:16.163+0000"
}
I have subjects as null. This AcademicYear entity already have records, when I send GET request with 2 as pathvariable to get the entity I get the following response:
{
"id": 2,
"name": "Second Year",
"subjects": [
{
"id": 3,
"name": "english",
"updatedAt": "2020-03-27T17:39:09.000+0000"
},
{
"id": 4,
"name": "physics",
"updatedAt": "2020-03-26T21:45:09.000+0000"
},
{
"id": 5,
"name": "chemistry",
"updatedAt": "2020-03-26T21:45:09.000+0000"
},
{
"id": 2,
"name": "math",
"updatedAt": "2020-03-27T17:39:09.000+0000"
}
],
"updatedAt": "2020-03-27T18:01:16.000+0000"
}
I have fetch type as EAGER, don't know why I get subjects as null when I update the entity name. Any help?

You need to use PATCH instead of PUT for partial updates.
Here is why
Based on RFC 7231, PUT should be used only for complete replacement of representation, in an idempotent operation. PATCH should be used for partial updates.
Based on your input, request set null to subjects.
If you still want to use the PUT then you need to provide the whole request object which you want to update/replace
you can find more details here
Why isn't HTTP PUT allowed to do partial updates in a REST API?

Related

how can i avoid duplicate field in jackson in serialisation

I'm using Jackson to serialise My Rest Api
POJO :
#Data
#Entity
#NoArgsConstructor
#AllArgsConstructor
#JsonTypeInfo(include= JsonTypeInfo.As.WRAPPER_OBJECT,use= JsonTypeInfo.Id.NAME)
public class Project implements Serializable {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String projectName;
private String resource;
#ManyToMany
private List<Collaborator> collaborators;
#JsonIgnore
#OneToMany(mappedBy = "project",cascade = CascadeType.ALL)
private List<Task> tasks;
public Project(String projectName, String resource) {
this.projectName = projectName;
this.resource = resource;
}
}
output:
{
"id": 1,
"dateDebut": "2022-05-31T13:14:39.091+00:00",
"dateFin": "2022-05-31T13:14:39.091+00:00",
"project": {
"Project": {
"id": 2,
"projectName": "project Suivi Activite 2",
"resource": "resource 2",
"collaborators": []
}
},
"collaborator": null,
"days": []
}
how can i avoid field "Project" inside "project" ?
#JsonTypeInfo(include= JsonTypeInfo.As.WRAPPER_OBJECT,use= JsonTypeInfo.Id.NAME) ?

Ignore property in nested object of the same type with Spring Boot

I have one class which is entity and use the same class as a property:
#Entity
public class Employee {
private String name;
#OneToOne
#JoinColumn(name = "supervisor_id", referencedColumnName = "id")
private Employee supervisor;
//getters and setters
}
I want to get the supervisor of an employee, but not the supervisor of the supervisor. Can I manage this somehow?
{
"name": "PersonName",
"supervisor": {
"name": "Supervisor name",
"supervisor": null // i don't want this one
}
}
In the end I just used nested classes for both - dto and entity with fields needed.
True that I duplicated properties of the classes, but at least it's clean and simple.
Just ignore the nulls in the Json. The following works for me:
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import javax.persistence.*;
#Getter
#Setter
#ToString
#Entity
#JsonSerialize(include = JsonSerialize.Inclusion.NON_EMPTY)
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "supervisor_id", referencedColumnName = "id")
private Employee supervisor;
}
Here's the test
#Test
public void test2() throws Exception {
Employee employee = makeEmployee("employee 1");
Employee supervisor1 = makeEmployee("supervisor 1");
employee.setSupervisor(supervisor1);
Employee save = employeeRepository.save(employee);
System.out.println(new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(save));
}
Here's my test output:
{
"id" : 1,
"name" : "employee 1",
"supervisor" : {
"id" : 2,
"name" : "supervisor 1"
}
}
Make sure you use the correct JsonSerialize org.codehaus.jackson.map.annotate.JsonSerialize not com.fasterxml.jackson.databind.annotation.JsonSerialize

How to fix update process in Spring Boot (One-to-Many , One-to-One) via Postman?

I have a problem about updating the movie.
I wrote a function that is named for "update" in MovieService.
Here is that function which is shown below.
public void update(Long id,Movie movie) {
boolean isUpdatingEmployee = (movie.getId() == id);
if (isUpdatingEmployee) {
Movie existingMovie = movieRepository.findById(movie.getId()).get();
existingMovie.setId(id);
existingMovie.setName(movie.getName());
existingMovie.setRating(movie.getRating());
existingMovie.setDirector(movie.getDirector());
existingMovie.setGenres(movie.getGenres());
existingMovie.setCreatedAt(movie.getCreatedAt());
movieRepository.save(existingMovie);
}
}
When ı try to update a movie after saving it, I got this kind of JSON result and that's why the update process cannot be done.
http://localhost:8082/api/v1/movie/update/1
Body Request
{
"name": "MovieC",
"genres": [
{
"name" : "Adventure"
},
{
"name" : "Action"
}
],
"createdAt": "2021-04-28",
"rating" : 9,
"director" : {
"name" : "Director 2"
}
}
The result of JSON after updating the process.
{
"id": null,
"name": "MovieC",
"genres": [
{
"id": null,
"name": "Action"
},
{
"id": null,
"name": "Adventure"
}
],
"rating": 9.0,
"createdAt": "2021-04-28",
"director": {
"id": null,
"name": "Director 2"
}
}
Here is my Movie entity which is shown below.
#Entity
#Getter
#Setter
#ToString
#AllArgsConstructor
#NoArgsConstructor
public class Movie implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#JsonManagedReference
#OneToMany(mappedBy="movie",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private Set<Genre> genres;
private Double rating;
private LocalDate createdAt;
#ManyToOne(cascade=CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn
private Director director;
}
Here is my Director entity which is shown below.
#Entity
#Getter
#Setter
#ToString
#RequiredArgsConstructor
#NoArgsConstructor
#JsonIgnoreProperties({"movies"})
public class Director implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NonNull
private String name;
#OneToMany(mappedBy="director",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private Set<Movie> movies;
}
Here is my Genre entity which is shown below.
#Entity
#Getter
#Setter
#ToString
#RequiredArgsConstructor
#NoArgsConstructor
#JsonIgnoreProperties({"movie"})
public class Genre implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NonNull
private String name;
#JsonBackReference
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn
private Movie movie;
}
Here is my sample project link : Project Link
How can I fix it?
As per your code, this is your request:
http://localhost:8082/api/v1/movie/update/1
{
"name": "MovieC",
"genres": [
{
"name" : "Adventure"
},
{
"name" : "Action"
}
],
"createdAt": "2021-04-28",
"rating" : 9,
"director" : {
"name" : "Director 2"
}
}
Now consider this snippet from your code:
public void update(Long id,Movie movie) {
boolean isUpdatingEmployee = (movie.getId() == id);
if (isUpdatingEmployee) {
...
Your id will be 1 as you've set this in your path variable.
However, movie.getId() will be null since I don't see it in your RequestBody.
And so:
isUpdatingEmployee = (movie.getId() == id)`
isUpdatingEmployee = ( null == 1)
isUpdatingEmployee = false
this will always give you false so I don't think this will enter in your update logic.
I think the problem because you are returning the same object movie you passed in the body of the post method in the controller - https://github.com/Rapter1990/springboothazelcast/blob/3157f354a628d418cccb99cfdbd188f594c24e9c/src/main/java/com/springboot/hazelcast/controller/MovieController.java#L64
You should rewrite it to something like this:
#PostMapping("/save")
public Movie saveMovie(#RequestBody Movie movie) throws ParseException {
LOG.info("MovieController | Saving Movie.");
return movieService.save(movie);
}
Here is the link to CRUDRepository javadocs:
https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/CrudRepository.html#save-S-

One to Many bidirectional

I have problems with Many to One relationship because I don't show correctly the entity.
Could anyone helps to me ?
I attached my code.
Invoice
#Entity
#Table(name = "invoices")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,property = "id")
public class Invoice {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String clave;
#OneToMany(mappedBy = "invoice", cascade = CascadeType.REMOVE, fetch = FetchType.LAZY, orphanRemoval = true)
private List<InvoiceLine> lines;
InvoiceLines
#Entity
#Table(name = "invoice_lines")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,property = "id")
public class InvoiceLine {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "product", nullable = false)
private String product;
#ManyToOne
#JoinColumn(name = "invoice_id", referencedColumnName = "id", nullable = false)
private Invoice invoice;
Controller
#RestController
public class InvoiceController{
#Autowired
private InvoiceRepository invoiceRepository;
#Autowired
private InvoiceLineRepository invoiceLineRepository;
#GetMapping("/")
public Iterable<Invoice> findAllnvoices(){
return invoiceRepository.findAll();
}
#GetMapping("/invoiceLine")
public Iterable<InvoiceLine> findAllInvoiceLine(){
return invoiceLineRepository.findAll();
}
#GetMapping("/{id}")
public Optional<Invoice> findTagByInvoice(#PathVariable("id") Long id){
return invoiceRepository.findById(id);
}
}
The response when I call a endpoint invoiceLine :
[
{
"id": 1,
"product": "Tag1-ES",
"invoice": {
"id": 1,
"clave": "Tag1",
"lines": [
1,
{
"id": 2,
"product": "Tag1-FR",
"invoice": 1
},
{
"id": 3,
"product": "Tag1-IT",
"invoice": 1
}
]
}
},
2,
3
]
My question :Why is not showing correctly the response the ManyToOne entity if I have all correct ?
If I understood your problem correctly after the comments, you are bothered by the "numbers" that are displayed. Those numbers are used to avoid infinite recursion, and they refer to entities that were already displayed.
So the number "2" would be this actually:
{
"id": 2,
"product": "Tag1-FR",
"invoice": 1
}
If a representation like that is not used, then the whole invoice and it's items would be repeated infinitely.
There are several different ways to avoid this behavior, such as using #JsonIgnore or #JsonBackReference and #JsonManagedReference. Take a look at this explanation about their differences
Difference between #JsonIgnore and #JsonBackReference, #JsonManagedReference

spring mongodb send only the not null fields

I have an Article document class like.
package com.document.feed.model;
import java.util.List;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.lang.NonNullFields;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
#Data
#AllArgsConstructor
#NoArgsConstructor
#ToString
class Source {
private String id;
private String name;
}
#Data
#AllArgsConstructor
#NoArgsConstructor
#ToString
#Document
#Getter
#Setter
#JsonIgnoreProperties(ignoreUnknown = true)
public class Article {
#Id
private String id;
private Source source;
private String author;
private String title;
private String description;
private String url;
private String urlToImage;
private String publishedAt;
private String content;
private String country;
private String category;
// Vector of Tf-Idf weights.
private List<Double> v;
// Extra fields computed during ordering.
private double dot;
// public Article(String id, String author, Double dot) {
// this.id = id;
// this.author = author;
// this.dot = dot;
// }
}
I am using aggregation pipeline to select only author and dot of the documents as:
{
author: 1,
dot: 1
}
Aggregation is done like:
Aggregation aggregation = newAggregation(Article.class,
aggregate("$project",
projection),
sort(Sort.Direction.DESC, "dot")
);
return mongoTemplate.aggregate(aggregation, "article", Article.class);
But I am getting the API response as:
{
"id": "5e137c67771a9880d1639b5d",
"source": null,
"author": "Asian News International",
"title": null,
"description": null,
"url": null,
"urlToImage": null,
"publishedAt": null,
"content": null,
"country": null,
"category": null,
"v": null,
"dot": 3.2454110250954025
},
I want only the non null fields as output. I can do it by defining a new POJO class for the required fields only, but Is there a way to do it without defining new classes(It will be a nightmare if the projection is a parameter of the API only)?
Add jackson annotation #JsonInclude for removing null fields.
#JsonInclude(Include.NON_NULL)

Resources