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
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"}
]
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
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;
}
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.
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