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

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-

Related

unrelated data from the Many-to-Many With a New Entity concept

Problem summary
I am getting unrelated data from the Many-to-Many With a New Entity concept for the first two cases.
Fetching a particular company and associated users of the company.
Fetching particular user and associated company.
Not able to feth company and user whose company status is not equal to 3 (CompanyStatus=1 "Pending" ,CompanyStatus=2 "Other",CompanyStatus=1 "Active")
To work on this Many-to-Many With a New Entity concept I have used Company, User, and CompanyUserMapping as an entity.
The blog that I have referred https://www.baeldung.com/jpa-many-to-many section 4.2
As mentioned above I have created Company, User, and CompanyUserMapping as below
Company
#Getter
#Setter
#Entity
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long companyId;
private String companyName;
private String companyDescription;
private String companyWebsite;
private String companyEmailDomain;
private Integer companyTypeID;
private Integer numberOfEmployeesID;
private String companyLogo;
private Integer companyStatus;
private Boolean active;
#OneToMany(mappedBy = "company",fetch = FetchType.EAGER)
Set<CompanyUserMapping> companyUserMapping;
}
User
#Getter
#Setter
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;
private String firstName;
private String lastName;
private String email;
private String encryptedEmail;
private String username;
private String password;
private String userStatus;
private String guid;
private Boolean isNotlocked;
private Date lastLogin;
private String profilePic;
#OneToMany(mappedBy = "user",fetch = FetchType.EAGER)
Set<CompanyUserMapping> companyUserMapping;
}
CompanyUserMapping
#Getter
#Setter
#Entity
public class CompanyUserMapping {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long companyUserMappingId;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "company_id")
private Company company;
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "user_id")
private User user;
private String role;
private String [] authorities;
private boolean isExternal;
private boolean isActive;
private Long customerId;
}
Then I inserted two companies and a user in each of them. Then I have also inserted a common user for both of the companies.
{
"companyId": 1,
"companyName": "ABC company",
"companyUsers": [
{
"companyUserMappingId": 1,
"company": 1,
"user": {
"userId": 1,
"email": "sachintendulkar#gmail.com",
"companyUserMapping": [
1
]
},
"active": true,
"external": false
},
{
"companyUserMappingId": 3,
"company": 1,
"user": {
"userId": 3,
"email": "shanewarne#gmail.com",
"companyUserMapping": [
3,
{
"companyUserMappingId": 4,
"company": {
"companyId": 2,
"companyName": "XYZ company",
"companyUsers": [
{
"companyUserMappingId": 2,
"company": 2,
"user": {
"userId": 2,
"email": "sehwag#gmail.com",
"companyUserMapping": [
2
]
},
"active": true,
"external": false
},
4
]
},
"user": 3,
"active": true,
"external": false
}
]
},
"active": true,
"external": false
}
]
}
In the above JSON, ABC and XYZ are two companies and a common user for both of the companies is shanewarne#gmail.com
As mentioned in the summary If I try to pull a particular company(ABC company) and associated users, it also brings another company and users which is unrelated.
To resolve this problem I have Implemented like below
public CompanyDto getConsolidatedCompanyData(Long companyId) {
Company existingCompany = companyRepository.findById(companyId).orElseThrow(() -> new ResourceNotFoundException("Company not found for the companyId :: "+companyId));
CompanyDto companyDto=new CompanyDto();
companyDto.setCompanyId(existingCompany.getCompanyId());
companyDto.setCompanyName(existingCompany.getCompanyName());
Set<CompanyUserMappingDto> companyUserMappingDtoList=new LinkedHashSet<>();
Set<CompanyUserMapping> companyUserMappingList=existingCompany.getCompanyUserMapping();
for (CompanyUserMapping cum : companyUserMappingList) {
CompanyUserMappingDto companyUserMappingDto=new CompanyUserMappingDto();
companyUserMappingDto.setCompanyUserMappingId(cum.getCompanyUserMappingId());
CompanyUserDto cdto=new CompanyUserDto();
cdto.setUserId(cum.getUser().getUserId());
cdto.setEmail(cum.getUser().getEmail());
companyUserMappingDto.setUser(cdto);
companyUserMappingDto.setActive(cum.isActive());
companyUserMappingDto.setExternal(cum.isExternal());
companyUserMappingDtoList.add(companyUserMappingDto);
}
companyDto.setAssociatedUsers(companyUserMappingDtoList);
return companyDto;
}
In the same way, if I try to fetch a particular user and associated company I am getting related data so I have implemented code
private UserDto getConslidatedUserData(Long userId) {
User existingUser = userRepository.findById(userId).orElseThrow(() -> new ResourceNotFoundException("User not found for the userId :: "+userId));
Set<UserCompanyMappingDto> associatedCompanies=new HashSet<>();
UserDto userDto=new UserDto();
userDto.setUserId(existingUser.getUserId());
userDto.setEmail(existingUser.getEmail());
Set<CompanyUserMapping> companyUserMapping=existingUser.getCompanyUserMapping();
for (CompanyUserMapping companyUserMapping2 : companyUserMapping) {
UserCompanyMappingDto userCompanyMappingDto=new UserCompanyMappingDto();
userCompanyMappingDto.setCompanyUserMappingId(companyUserMapping2.getCompanyUserMappingId());
UserCompanyDto userCompanyDto=new UserCompanyDto();
userCompanyDto.setCompanyId(companyUserMapping2.getCompany().getCompanyId());
userCompanyDto.setCompanyName(companyUserMapping2.getCompany().getCompanyName());
userCompanyMappingDto.setCompany(userCompanyDto);
associatedCompanies.add(userCompanyMappingDto);
}
userDto.setAssociatedCompanies(associatedCompanies);
return userDto;
}
Do I need to improvise Implementation for the first two cases?

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) ?

Spring Boot getting empty _embedded array for related entity

Using Spring Boot I'm Having the following abbreviated structure of entities:
#Entity
#Table(name = "item")
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class Item implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", updatable = false)
protected Long id;
...
}
#Entity
#Table(name = "book")
public class Book extends Item implements Serializable {
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "item_author", joinColumns = #JoinColumn(name = "item_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "author_id", referencedColumnName = "id"))
private Set<Author> authors;
}
#Entity
#Table(name = "author")
public class Author implements Serializable {
#Id
#GeneratedValue
private Long id;
#ManyToMany(mappedBy="authors")
private List<Book> books = new ArrayList<Book>();
private String name;
}
My DAOs are just plain simple RestResource interfaces for all entities, like:
#RestResource(path="items", rel="items")
public interface ItemDao extends CrudRepository<Item, Long> {
}
When I query an entity by id, it is all good
GET > http://localhost:8080/shelfventory/authors/1
{
"name" : "Jhonny Cash",
"used" : true,
"_links" : {
"self" : {
"href" : "http://localhost:8080/shelfventory/authors/1"
},
"author" : {
"href" : "http://localhost:8080/shelfventory/authors/1"
},
"books" : {
"href" : "http://localhost:8080/shelfventory/authors/1/books"
}
}
}
But when I try to follow the links for a related object I just get an empty embedded:
GET > http://localhost:8080/shelfventory/authors/1/books
{
"_embedded" : {
"books" : [ ]
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/shelfventory/authors/1/books"
}
}
}
What am I doing wrong, how to solve it?
Consider adding these two properties to your application.properties to keep your #Entity and schema in sync:
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=true

JPA #OneToMany get latest record by date from Join

Getting stuck trying to fetch the latest record from a Join
I have the following classes
Author
#Entity
#Table(name = "author")
public class Author {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "name")
private String name;
#OneToMany
#JoinColumn(name = "author_id", referencedColumnName = "id")
#OrderBy("id Desc")
private List<Book> books;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Book> getBooks() {
return books;
}
public void setBooks(List<Book> books) {
this.books = books;
}
}
Book
#Entity
#Table(name = "book")
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "author_id")
private Integer authorId;
#Column(name = "date_published")
private Date datePublished;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Integer getAuthorId() {
return authorId;
}
public void setAuthorId(Integer authorId) {
this.authorId = authorId;
}
public Date getDatePublished() {
return datePublished;
}
public void setDatePublished(Date datePublished) {
this.datePublished = datePublished;
}
}
Repository
#Repository
public interface AuthorRepository extends
JpaRepository<Author, Long> {
public Page<Author> findALL(int id, Pageable pageable);
}
Current results
{
"id": 1,
"name": "James",
"books":[
{
"id": 1,
"name": "book1",
"datePublished": '12/12/2012'
},
{
"id": 1,
"name": "book2",
"datePublished": '01/02/2013'
}]
},
{
"id": 2,
"name": "Tim",
"books":[
{
"id": 5,
"name": "book5",
"datePublished": '12/12/2014'
},{
"id": 6,
"name": "book6",
"datePublished": '01/02/2015'
}]
}
Expected Result
{
"id": 1,
"name": "James",
"books":[
{
"id": 1,
"name": "book2",
"datePublished": '01/02/2013'
}]
},
{
"id": 2,
"name": "Tim",
"books":[
{
"id": 6,
"name": "book6",
"datePublished": '01/02/2015'
}]
}
From this a list of Authors are being returned with all their respective books.
Question is how can JPA assist me to pick only the latest book from the collection based on date published.
If you are using hibernate you can achieve this using #JoinFormula to map the latest record by date. Something like:
#ManyToOne(fetch = FetchType.LAZY)
#JoinFormula("(" +
"SELECT b.id " +
"FROM book b " +
"WHERE b.author_id = id " +
"ORDER BY b.date_published DESC " +
"LIMIT 1" +
")")
private Book latestBook;
I had similar problem. The other solution is with #Where annotation:
#OneToMany
#JoinColumn(name = "author_id", referencedColumnName = "id")
#Where(clause = "date_published = (SELECT MAX(book.date_published) " +
"FROM book" +
"where author_id = book.author_id)")
#OrderBy("datePublished Desc")
private List<Book> books;
My post on stack: Get applications with their latest status only
if you want to get last book for each author , you can add transient field to get it :
#Entity
#Table(name = "author")
public class Author {
.......
#Transient
private Book lastBook;
#PostLoad
private void setLastBook() {
if(books==null || books,isEmpty())
this.lastBook=null;
}else{
this.lastBook=books.get(0);
}
}
or make it one to one and save it in db by same method annotated with #PostPersist and #PostUpdate. it will save in db automatically last book
the answer by #Seldo97 is correct, but he missed the space between "book" and "where" in the Select query, which will throw an error.
so basically it should be
#OneToMany
#JoinColumn(name = "author_id", referencedColumnName = "id")
#Where(clause = "date_published = (SELECT MAX(book.date_published) " +
"FROM book" +
" where author_id = book.author_id)")
#OrderBy("datePublished Desc")
private List<Book> books;
author_id -> basically refers to foreign key column name in the child.
entity
date_published -> this refers to the column by which we want to
sort(in this case the date column name).
so, the above query will take the record with the latest date and put it in the list object:
List<book> book;

Exposing field of child object in Spring hateoas

I have two entities defined:
#Entity
public class VideoPost {
private #Id
#GeneratedValue(strategy= GenerationType.IDENTITY) Long id;
private String videoTitle;
private #ManyToOne #JoinColumn(name = "VideoPost_Id") User uploader;
private boolean isPublished = false;
//....
}
#Entity
public class User {
private #Id #GeneratedValue(strategy=GenerationType.IDENTITY) Long id;
private String userName;
private Date registrationDate;
#OneToMany(mappedBy = "uploader", cascade = CascadeType.ALL) private List<VideoPost> videoPosts;
//...
}
I have following JSON response to the call to /api/videoposts:
"_embedded" : {
"videoPosts" : [ {
"videoTitle" : "test video 1",
"uploadDate" : "2017-06-03T11:44:02.012+0000",
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/videoPosts/1"
},
"videoPost" : {
"href" : "http://localhost:8080/api/videoPosts/1"
},
"uploader" : {
"href" : "http://localhost:8080/api/videoPosts/1/uploader"
}
}
} ]
I would like to expose the uploader name inside this response directly. I.e.
"uploader" : {
"userName": theName
"href" : "http://localhost:8080/api/videoPosts/1/uploader"
}
How could I achieve this?
You can check Projections where you can customize you objects the way you want whether you want to show the whole nested attributes or some of them or even hide these nested objects

Resources