Sping boot many-to-many GET request - java

I have many-to-many relationship in my Spring boot app. But when i try to do get response i always get an empty array;
Here are my classes(I have pasted code without constructor, getters and setters but i have them in my code):
#Entity
#Table(name="orders")
public class Order {
private #Id
#GeneratedValue
Long id;
#OneToOne(cascade = CascadeType.ALL)
private Customer customer;
#OneToMany(mappedBy = "product",fetch = FetchType.LAZY,cascade = {CascadeType.PERSIST,CascadeType.MERGE,CascadeType.DETACH},orphanRemoval = true)
private Set<ProductOrderDetails> productOrderDetails;
#DateTimeFormat
private Date shippmentDate;
private double totalOrderPrice;
private OrderStatus status;
private String note1;
private String note2;
#Entity
#Table
public class Product {
private #Id
#GeneratedValue
Long id;
private String name;
private String model;
private String color;
private String material;
private double price;
#Transient
private int productQuantity;
#OneToMany(mappedBy = "order",fetch = FetchType.LAZY)
private List<ProductOrderDetails> productOrderDetailsSet;
#Entity
#IdClass(ProductOrderDetails.class)
public class ProductOrderDetails implements Serializable {
#Id
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="order_id")
Order order;
#Id
#ManyToOne(cascade = {CascadeType.PERSIST,CascadeType.MERGE,CascadeType.DETACH})
#JoinColumn(name="product_id")
Product product;
private int quantity;
Here is my OrderController code:
#GetMapping("/{id}")
public Order One(#PathVariable Long id) {
Order order=repository.findById(id).orElseThrow(()->new ObjectNotFoundException(id));
return order;
}
And this is the response that I get:
{
"id": 2,
"customer": {
"id": 1,
"name": "Company",
"address": "Main Street 1",
"city": "Bern",
"state": "Switzerland",
"zip": 58529,
"contactPersonName": "John Smith",
"contactPersonEmail": "test#gmail.com"
},
"productOrderDetails": [],
"shippmentDate": "2020-12-09T23:00:00.000+00:00",
"totalOrderPrice": 3434.0,
"status": "WAITING",
"note1": "note 1",
"note2": "note 2"
}
How do i get an array of productOrderDetails (array of products that are ordered)?
Id prefer if i could use JPA

You have a couple of possibilities to do this:
Using FetchType.EAGER strategy
#Entity
#Table(name="orders")
public class Order {
private #Id
#GeneratedValue
Long id;
#OneToOne(cascade = CascadeType.ALL)
private Customer customer;
#OneToMany(mappedBy = "product",fetch = FetchType.EAGER,cascade = {CascadeType.PERSIST,CascadeType.MERGE,CascadeType.DETACH},orphanRemoval = true)
private Set<ProductOrderDetails> productOrderDetails;
#DateTimeFormat
private Date shippmentDate;
private double totalOrderPrice;
private OrderStatus status;
private String note1;
private String note2;
}
Using Join fetching in your custom Repository method:
#Query(value = "SELECT o FROM Order o JOIN FETCH o.productOrderDetails")
List<Order> findAllOrders();

So to anyone who had the same problem as i did,
i had a typo error, in the Order class i should've used:
#OneToMany(mappedBy = "order",fetch = FetchType.LAZY,cascade = {CascadeType.PERSIST,CascadeType.MERGE,CascadeType.DETACH},orphanRemoval = true)
private Set<ProductOrderDetails> productOrderDetails;
instead of mappedBy="product".
And if you dont want to have a recursive call when you do a GET request you should add in ProductOrderDetails class #JsonIgnore:
#Id
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="order_id")
#JsonIgnore
Order order;
The rest of the code stays unchanged.

Related

Problem creating endpoint with springboot

My task is to create endpoint, the logic is:
User provides input --> nip (one of the variables in Contractor.class)
on the basis of that nip, I must return JSON, which will contain information about the product that is assigned to the contractor with the provided nip.
Example JSON should look like: {"name": "product_name", "quantity": "0", "address": "storage_address"}
I spent lots of time on this problem, but still don't know what logic to implement.
It's over my newbie head;
Product.class:
public class Product extends AbstractEntity {
#Id
#GeneratedValue
private Long id;
private String name;
private long quantity;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "product", fetch = FetchType.EAGER)
private List<Assignment> assignments;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "product")
private List<Place> places;
}
Contractor.class:
public class Contractor extends AbstractEntity {
#Id
#GeneratedValue
private Long id;
private String contractorName;
private int nip;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "contractor")
private List<Assignment> assignments;
}
Assignment.class:
public class Assignment extends AbstractEntity {
#Id
#GeneratedValue
private Long id;
#JsonIgnore
#ManyToOne
#JoinColumn(name = "id_product", referencedColumnName = "id", nullable = false)
private Product product;
#JsonIgnore
#ManyToOne
#JoinColumn(name = "id_contractor", referencedColumnName = "id", nullable = false)
private Contractor contractor;
}
Storage.class:
public class Storage extends AbstractEntity {
#Id
#GeneratedValue
private Long id;
private String address;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "storage")
private List<Place> places;
}
Place.class:
public class Place extends AbstractEntity {
#Id
#GeneratedValue
private Long id;
private Long shelfNumber;
#JsonIgnore
#ManyToOne
#JoinColumn(name = "id_product", referencedColumnName = "id")
private Product product;
#JsonIgnore
#ManyToOne
#JoinColumn(name = "id_storage", referencedColumnName = "id", nullable = false)
private Storage storage;
}
image of ERD Diagram
I do not know what logic do you need precisely. Also I am not sure if that code will fit to rest of your app. But maybe it will help.
The interface which will be returned by repository and by the rest controller:
public interface GetProductResponse {
public String getName();
public int getQuantity();
public String getAddress();
}
The repository where you can write your query:
public interface ProductRepository extends CrudRepository<Product, Long> {
#Query(nativeQuery = true,
value = (
"SELECT product.name AS name, product.quantity AS quantity, storage.address " +
//be sure to name the result columns the variables in GetProductResponse (without the 'get')
"FROM contractor INNER JOIN assignment ON contractor.id = assignment.id_contractor " +
" INNER JOIN product ON product.id = assignment.id " +
" INNER JOIN place ON product.id = place.id_product " +
" INNER JOIN storage ON storage.id = place.id_storage " +
"WHERE contractor.nip = :nip_ "
)
public List<GetProductResponse> getProducts(#Param("nip_")String nip)
}
The rest controller:
#RestController
public class Controller {
#RequestMapping(value = "/getProductsByNip", method = { RequestMethod.POST})
public List<GetProductResponsee> getProductsByNip(#RequestBody String nip) {
return productRepository.getProducts(nip);
}
}
The output will look like:
[
{"name": "product_name1", "quantity": "0", "address": "storage_address1"},
{"name": "product_name2", "quantity": "2", "address": "storage_address2"}
]

ModelMapper issues converting DTO Objects to Entity

I have a simple spring-boot app where Product needs to be stored and conversion between DTO and Entity needs to happen. I am using the ModelMapper dependency. User can attach a ProductCategory to the Product or leave it empty. Similarly Product can have multiple ReplaceNumber or empty. If I dont attach category it gives error. If I attach category it saves the product with the attached category. If I leave the replaceNumbers array empty it saves. If I fill it it gives errors. Errors are described below.
ProductCategory
#Entity
#Table(name = "product_categories")
public class ProductCategory
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Column(name = "name", nullable = false)
#Size(max = 20)
private String name;
public ProductCategory()
{
}
public ProductCategory(String name)
{
this.name = name;
}
}
ReplaceNumber
#Entity
#Table(name = "replace_numbers")
public class ReplaceNumber
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Size(max = 20)
private String partNumber;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "product_id", nullable = false)
private Product product;
public ReplaceNumber()
{
}
public ReplaceNumber(String partNumber)
{
this.partNumber = partNumber;
}
}
Product
#Entity
#Table(name = "products", indexes = {#Index(name= "part_number_index", columnList = "part_number", unique = true)})
public class Product
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Column(name = "part_number", nullable = false)
#Size(max = 20)
private String partNumber;
#NotBlank
#Size(max = 255)
private String description;
#OneToMany(
mappedBy = "product",
cascade = CascadeType.ALL,
fetch = FetchType.EAGER,
orphanRemoval = true
)
#Fetch(FetchMode.SELECT)
private List<ReplaceNumber> replaceNumbers = new ArrayList<>();
#ManyToOne
#JoinColumn(name = "product_category_id", referencedColumnName = "id")
private ProductCategory category;
}
Following are the DTO Classes that need to be converted.
ReplaceNumberRequest
public class ReplaceNumberRequest
{
#NotBlank
#Size(max = 20)
private String partNumber;
public String getPartNumber()
{
return partNumber;
}
public void setPartNumber(String partNumber)
{
this.partNumber = partNumber;
}
}
ProductCategoryResponse
public class ProductCategoryResponse
{
private Long id;
private String name;
public ProductCategoryResponse()
{
}
public ProductCategoryResponse(String name)
{
this.name = name;
}
}
ProductRequest
public class ProductRequest
{
#NotBlank
#Size(max = 20)
private String partNumber;
#NotBlank
#Size(max = 255)
private String description;
private List<ReplaceNumberRequest> replaceNumbers = new ArrayList<>();
private ProductCategoryResponse category;
}
ProductService
#Service
public class ProductService
{
#Autowired
ProductRepository productRepository;
public Product create(ProductRequest productRequest)
{
Product product = new Product();
org.modelmapper.ModelMapper modelMapper = new org.modelmapper.ModelMapper();
modelMapper.map(productRequest, product);
return productRepository.save(product);
}
}
If I post the following JSON from Postman
{
"partNumber": "443455783",
"description": "443434",
"replaceNumbers": [],
"category": ""
}
It goes for saving the empty category and produces the following error.
org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : org.walana.GP.model.Product.category -> org.walana.GP.model.ProductCategory; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : org.walana.GP.model.Product.category -> org.walana.GP.model.ProductCategory
If I post the following JSON from Postman
{
"partNumber": "443455783",
"description": "443434",
"replaceNumbers": [
{
"partNumber": "123455"
},
{
"partNumber": "343435"
}
],
"category": {
"id": 1,
"name": "Mounting"
}
}
It gives following error.
could not execute statement; SQL [n/a]; constraint [part_number_index]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement

List<object> null when deserializing json in Spring Boot controller

I have a problem with a controller in a Spring Boot application. When I make a call on the controller (URI/call), the object list is always null.
{
"code": "TEST",
"name": "TEST NAME",
"groupe": "A1",
"list": [
{"type":"web", "link":"https://google.com/"},
{"type":"web", "link":"https://google2.com/"}
]
}
#PostMapping(value="/call")
public ResponseEntity<Void> ajouterEnvironnement(#RequestBody First first) {
first.getCode() // value : "TEST"
first.getList() // value : null
}
public class First {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String code;
private String name;
private String groupe;
#OneToMany(mappedBy = "first", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Second> list;
}
public class Second {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String type;
private String link;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "first_id", nullable = false)
private First first;
}
By writing this question, I was able to find a solution, so I share it:
Use the #JsonManagedReference and #JsonBackReference annotations.
More information here.
public class First {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String code;
private String name;
private String groupe;
#OneToMany(mappedBy = "first", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JsonManagedReference
private List<Second> listLiens;
}
public class Second {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String type;
private String link;
#ManyToOne(fetch = FetchType.EAGER)
#JsonBackReference
#JoinColumn(name = "first_id", nullable = false)
private First first;
}

Hibernate unable to map many students to a class

I hae 2 simple entities: Student and Class. I want to POST a student, where I specify the class it belongs to, but I've got stuck in hibernate mapping.
ClassModel.class
#Entity
#Table(name = "class" )
public class ClassModel implements Serializable {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#NotEmpty
#Size(max = 20)
#Column(name = "name")
private String name;
#Column(name = "tables")
private int tables;
#Column(name = "chairs")
private int chairs;
#Column(name = "teacher")
private String teacher;
(getters + setters)
StudentModel
#Entity
#Table(name = "student")
public class StudentModel implements Serializable {
#Id
#Column(name = "student_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int student_id;
#NotEmpty
#Column(name = "name")
#Size(max = 50)
private String name;
#Column(name = "age")
private int age;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id")
private ClassModel classModel;
(getters + setters)
}
StudentController.java
...
#Autowired
private StudentService studentService;
#Autowired
private ClassService classService;
#PostMapping(value = "/save")
public StudentModel save(#RequestBody StudentModel studentModel){
ClassModel classModel = classService.findById(studentModel.getClassId()).get();
studentModel.setClassModel(classModel);
return studentService.save(studentModel);
}
...
But when I make a request from Postman with the following body:
{
"name": "some name",
"age": 12,
"class_id": 1
}
I get the following error from hibernate:
Column 'class_id' cannot be null
Where is the mistake in my hibernate mapping?
It's how I have made working join in hibernate. Have a look:
TrainingEntity.java
#Id
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "animal_id", nullable = false, insertable = false, updatable = false)
private AnimalEntity animalEntity;
#Column(name = "animal_id")
private Integer animalId;
AnimalEntity.java
#Id
private Integer id;
#OneToMany(mappedBy = "animalEntity", fetch = FetchType.LAZY)
private List<TrainingEntity> trainingEntityList = new ArrayList<>();
So here is the join between AnimalEntity and TrainingEntity.
AnimalEntity have a list of TrainingEntities.
The mistake is in this line:
"class_id": 1
You're using column name instead of field name. You would have to replace class_id with classModel, where classModel would be an object. Other solution would be to find ClassModel by id from json and set it as parent to StudentModel.

Hibernate doesn't change column for new parameters

I have a simple object which I want save in DB. But for time it was necessary to change someone parameters. I drop my old table but when I turn on my program - Hibernate still create DB with old columns.
For example:
My old class looked like this :
#Entity
#Table(name = "Tests")
public class Test {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
private String description;
private boolean isFree;
private int status;
private String author;
private String section;
#OneToMany(mappedBy = "qTest", fetch = FetchType.LAZY, cascade = { CascadeType.ALL })
private List<Question> questions;
private String commentToAdmin;
...
and geters-seters
}
In table I have column "status" like INT.
But then I change this class parameter to String. Now my class looks like this:
#Entity
#Table(name = "Tests")
public class Test {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
private String description;
private boolean isFree;
#Column(name = "statusT")
private String status;
private String author;
private String section;
#OneToMany(mappedBy = "qTest", fetch = FetchType.LAZY, cascade = { CascadeType.ALL })
private List<Question> questions;
private String commentToAdmin;
And geters-setters
}
But after drop old table and restart my application column "status" still "INT" and his name "status" (not "statust").
Please help me understand why it's happening

Categories