Ignore property in nested object of the same type with Spring Boot - 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

Related

Post Request with Enum and composite key

I'm working on an exercise where i have to create CRUD operations.
I have a User table, a Role table and a UserRole table where i have the primary keys of those two entities.
I also have a RoleEnum with roles that have to be assigned to the User.
The problem that i'm gettin is that every time I insert a new user is a 200ok response but the role returns null and so it doesn't add it to the UserRole table as well.
I need help in solving the problem.
User Entity
#Data
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "utente")
public class Utente implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long matricola;
#Column(nullable = false)
private String nome;
#Column(nullable = false)
private String cognome;
#Column(nullable = false)
private String email;
#Column(name = "ruoloUtente", nullable = false)
#OneToMany(mappedBy = "matricolaUtente")
#JsonIgnore
private List<UtenteRuolo> ruoloUtente;
}
Role Entity
#Data
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "ruolo")
public class Ruolo implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "tipo_ruolo")
private String nome;
#Column(name = "utente_ruolo")
#OneToMany(mappedBy = "nomeRuolo")
private Set<UtenteRuolo> utenteRuolo;
}
UserRole class
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "utente_ruolo")
public class UtenteRuolo implements Serializable {
#EmbeddedId
private UtenteRuoloId utenteRuoloId;
#ManyToOne
#MapsId("matricola")
#JoinColumn(name = "matricola_utente", nullable = false)
private Utente matricolaUtente;
#ManyToOne
#MapsId("id")
#JoinColumn(name = "nome_ruolo", nullable = false)
private Ruolo nomeRuolo;
}
UserRoleId class
#Embeddable
#Data
#EqualsAndHashCode
#AllArgsConstructor
#NoArgsConstructor
public class UtenteRuoloId implements Serializable {
#Column(name = "matricola")
private Long matricola;
#Column(name = "id")
private Long id;
}
RoleEnum class
public enum RuoliEnum {
#JsonProperty
REFERENTE("REFERENTE"),
CONSULTATORE("CONSULTATORE"),
APPROVATORE("APPROVATORE");
#JsonProperty
private String value;
RuoliEnum(String value) { this.value = value; }
#JsonCreator
public static RuoliEnum fromValue(String text) {
for (RuoliEnum ruoli: RuoliEnum.values()) {
if (String.valueOf(ruoli.value).equalsIgnoreCase(text.trim())) {
return ruoli;
}
}
return null;
}
}
I also have all DTO's and the UserDTO has the RoleEnum instead of the List of class UserRole.
UserService
#Service
public class UtenteService {
#Autowired
private UtenteRepository utenteRepository;
#Autowired
private UtenteMap utenteMap;
public UtenteDto addUtente(UtenteDto utente) {
Utente u = utenteMap.fromDtoToModel(utente);
if(u != null) {
return utenteMap.fromModelToDto(utenteRepository.save(u));
}
return null;
}
UserController
#RestController
#RequestMapping("utente")
public class UtenteController {
#Autowired
private UtenteService utenteService;
#PostMapping("/addUtente")
public ResponseEntity addUtente(#Nullable #RequestBody UtenteDto utente) {
if(utente != null) {
return ResponseEntity.ok(utenteService.addUtente(utente));
} else {
return ResponseEntity.badRequest().body("utente non inserito correttamente");
}
}
this is a postman insert example:
{
"matricola" : 11,
"nome" : "aaa",
"cognome" : "bb",
"email" : "eee#mail.com",
"ruolo" : "APPROVATORE"
}
and this is the postman response with 200ok status:
{
"matricola": 11,
"nome": "aaa",
"cognome": "bb",
"email": "eee#mail.com",
"ruolo": null
}
the code doesn't tell me about any kind of error and i need to keep the UserRole table because it's required for this kind of exercise.
I tried everything I could but since I never worked like this with enums and this kind of table relations I don't know what's missing to complete it.

Spring Boot JPA Using Many-to-Many relationship with additional attributes in the join table

I have two simple classes Student and Course. I am trying to set up many to many relationship between these classes. I want to use additional table whose PRIMARY KEY is the combination of the primary keys of student and course tables (student_id and course_id).
The student class:
#Entity
#Table(name = "student")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "surname")
private String surname;
#OneToMany(mappedBy = "student")
private Set<CourseStudent> courses;
}
The course class:
#Entity
#Table(name = "course")
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String courseName;
#OneToMany(mappedBy = "course")
Set<CourseStudent> students;
}
The entity that stores the relationship between course and the student:
#Entity
#NoArgsConstructor
#Data
public class CourseStudent {
#EmbeddedId
CourseStudentKey id;
#ManyToOne
#MapsId("studentId")
#JoinColumn(name = "student_id")
Student student;
#ManyToOne
#MapsId("courseId")
#JoinColumn(name = "course_id")
Course course;
public CourseStudent(Student student, Course course) {
this.student = student;
this.course = course;
this.rating = 0;
}
int rating;
}
Attention: Since I want to have additional features in this entity (for example, storing the rating of the students for courses), I don't want to use #JoinTable idea that we implement in the Student class.
Since I have multiple attributes in the primary key of CourseStudent entity, I used the following class
#Embeddable
#Data
public class CourseStudentKey implements Serializable {
#Column(name = "student_id")
Long studentId;
#Column(name = "course_id")
Long courseId;
}
I have the following POST request to insert the student into a course:
#PostMapping("/insert/students/{studentId}/courses/{courseId}")
public CourseStudent insertStudentIntoCourse(#PathVariable(value = "studentId") Long studentId,
#PathVariable(value = "courseId") Long courseId) {
if (!studentRepository.existsById(studentId)) {
throw new ResourceNotFoundException("Student id " + studentId + " not found");
}
if (!courseRepository.existsById(courseId)) {
throw new ResourceNotFoundException("Course id " + courseId + " not found");
}
CourseStudent courseStudent = new CourseStudent(
studentRepository.findById(studentId).get(),
courseRepository.findById(courseId).get()
);
return courseStudentRepository.save(courseStudent);
}
I have manually added Student and the Course into my local database and send this request by using Postman.
http://localhost:8080/insert/students/1/courses/1
However, I get the following error:
{
"timestamp": "2022-08-04T12:33:18.547+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/insert/students/1/courses/1"
}
In the console, I get NullPointerException. What is the thing I am doing wrong here?

Spring Data CrudRepository's save throws InvocationTargetException

I have spent the whole weekend trying to debug this piece of code. I have a Spring RestController :
import com.tsakirogf.schedu.model.ContactMean;
import com.tsakirogf.schedu.model.DefaultContactMean;
import com.tsakirogf.schedu.model.human.Business;
import com.tsakirogf.schedu.services.BusinessService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import java.util.Optional;
import java.util.Set;
#RestController
#RequestMapping("api/v1/business/")
public class BusinessController
{
#Autowired
BusinessService businessService;
#GetMapping(value = "businesss")
Iterable<Business> list()
{
Iterable<Business> retVal = businessService.findAll();
return retVal;
}
#RequestMapping(value = "business", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
Business create(#RequestBody Business business)
{
CollectionOfContactMethods collectionOfContact = business.getContact();
collectionOfContact.setBusiness(business);
Set<ContactMean> contactMeanSet = collectionOfContact.getContactMeans();
DefaultContactMean defaultContactMeanSet = collectionOfContact.getDefaultContactMean();
defaultContactMeanSet.getCollectionOfContactMethodsDefault().setId(collectionOfContact.getId());
for (ContactMean element : contactMeanSet)
{
element.setCollectionOfContactMethods(collectionOfContact);
}
collectionOfContact.setDefaultContactMean(defaultContactMeanSet);
business.setContact(collectionOfContact);
Business retval = businessService.save(business);
return retval;
}
#RequestMapping(value = "business/{id}", method = RequestMethod.GET )
Optional<Business> get(#PathVariable Long id)
{
return businessService.findById(id);
}
}
And the service :
public interface BusinessService extends CrudRepository<Business, Long>
{
}
This is the model :
#Table(name = "business")
public class Business
{
#Id
#Column(name = "business_id", nullable = false)
private Long id;
#JsonProperty("name")
private String name;
#Embedded
#JsonProperty("address")
private Address address;
#OneToMany(mappedBy = "business",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY)
#JsonProperty("operatives")
#JsonIgnore
Set<Professional> operatives;
#OneToOne(mappedBy = "business",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
optional = false)
#JsonBackReference
#JsonProperty("contact_numbers")
private CollectionOfContactMethods contact;
public Business()
{
}
// Getters and Setters
}
When I send a POST request like this :
Where I got the following
{
"timestamp": "2021-11-01T08:59:06.343+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/api/v1/business/business"
}
I debug and I am getting InvocationTargetException as seen below
This is the controller, right before save() which seems to throw :
And here is the catch :
I found this article posted in a similar event in StackOverflow but I don't think that's what is happening in this case since I have only H2 database for now.
This is application.properties file :
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
spring.jpa.hibernate.hbm2ddl.auto=create
I would appreciate any ideas. Thanks for your time.
If you look at your last screenshot you see a message indicating that there is an id field that has no value.
In your entity you have the following declaration of your id field:
#Id
#Column(name = "business_id", nullable = false)
private Long id;
Which indicates to hibernate that it shouldn't generate a key or that there is no database assigned one. Which means you will manually need to set the value for id. If you don't you will run into this exception.
Now I assume that this was a mistake and that you actually wanted to have a sequence or auto-incremented id field. For this add the #GeneratedValue annotation to add this behavior.
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE))
#Column(name = "business_id", nullable = false)
private Long id;
This will instruct hibernate to use a sequence to generate the id upon inserting the entity. If your database supports identity columns you might want to use GenerationType.IDENTITY instead of GenerationType.SEQUENCE.

Spring boot REST API not returnning expect Json data

I just begin my first web application using Spring Boot, and I create the first project as well as the Database for this project. It used to fine to return the list of products with encapsulated datas that a product has. When I leave it for several Days and come back, the query api return instead of the products themselves but links.
so when I type "http://localhost:8080/api/products/1" in the browser, it return this
unexpected return JSON data.
Where it use to return something like
{
"sku" : "BOOK-TECH-1000",
"name" : "Crash Course in Python",
"description" : "Learn Python at your own pace. The author explains how the technology works in easy-to-understand language. This book includes working examples that you can apply to your own projects. Purchase the book and get started today!",
"unitPrice" : 14.99,
"imageUrl" : "assets/images/products/books/book-1000.png",
"active" : true,
"unitsInStock" : 100,
"dateCreated" : "2021-01-04T21:05:48.000+0000",
"lastUpdated" : null,
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/products/1"
},
"product" : {
"href" : "http://localhost:8080/api/products/1"
},
"category" : {
"href" : "http://localhost:8080/api/products/1/category"
}
}
}
I dont know why the data informations are not return anymore, any suggestion?
The return page of the GET query
The DAO class I Created for the JPA
The product class
database data, which is what should be return in the result page
Product.java:
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import javax.persistence.*;
import java.math.BigDecimal;
import java.util.Date;
#Entity
#Table(name="product")
#Data
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
//join with Entity "ProductCategory". vise verse, many products to one product Category.
#ManyToOne
#JoinColumn(name = "category_id", nullable = false)
private ProductCategory category;
#Column(name = "sku")
private String sku;
#Column(name = "name")
private String name;
#Column(name = "description")
private String description;
#Column(name = "unit_price")
private BigDecimal unitPrice;
#Column(name = "image_url")
private String imageUrl;
#Column(name = "active")
private boolean active;
#Column(name = "units_in_stock")
private int unitsInStock;
#Column(name = "date_created")
#CreationTimestamp
private Date dateCreated;
#Column(name = "last_updated")
#UpdateTimestamp
private Date lastUpdated;
}
ProductRepository.java:
import com..Entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.web.bind.annotation.CrossOrigin;
#CrossOrigin("http://localhost:4200")
public interface ProductRepository extends JpaRepository<Product, Long> {
}
I faced something similar due to lombok not working popery. Writing the getter and setter manually should work. In my case, adding lombok extension for vs code solved the problem.

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

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?

Resources