ManyToOne saves null reference - spring-boot

I am learning Springboot/Hibernate and am having some trouble with ManyToOne notation.
I have three models: Order, OrderDetail, and Product. Each Order has several OrderDetails (OneToMany, this works fine) and each OrderDetail holds an id of one Product (products can be repeated, therefore it should be ManyToOne).
What happens is that everytime I try to save an order the details get created and saved but the products (which already exist in my database) within each detail are being set null. I have tried everything I found on the internet for the past 4 hours but haven't found what's causing my issue yet.
Minimal code, let me know if you need more details:
#RestController
public class OrderController {
#Autowired
OrderRepository orderRepository;
#PostMapping("/orders")
public Order addOrder(#RequestBody Order order)
{
return orderRepository.save(order);
}
}
#Entity
#Table(name="orders")
public class Order {
#Id
#GeneratedValue
#Column(name = "order_id")
private Long id;
//...
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "order_id")
#Getter
#Setter
private List<OrderDetail> orderDetailList;
//...
^This works just fine
#Entity
#Table(name="order_details")
public class OrderDetail {
#Id
#GeneratedValue
#Column(name = "order_detail_id")
private Long id;
//...
#ManyToOne(/*optional = false, */cascade = CascadeType.ALL, targetEntity=Product.class)
#JoinColumn(name = "product_id")
#Getter
#Setter
private Product product;
//...
^I have commented optional=false otherwise I get a 500 error because of the product being null
#Entity
#Table(name="products")
public class Product {
#Id
#GeneratedValue
#Column(name="product_id")
private Long id;
//...
// #OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true)
// private List<OrderDetail> orderDetailList;
//...
}
^ I have tried adding or not adding this side of the relationship, none works; I think I should not need it anyways because I interact with my detail's product and not with my product's details.
And here is my Postman POST Request to /orders:
{
"address": "Dorton Road 80",
"email": "tsayb#opera.com",
"tel": "(0351) 48158101",
"schedule": "21:00",
"orderDetailList": [
{ "product_id": 1,
"quantity": 1 },
{ "product_id": 2,
"quantity": 1 }]
}
Which returns
{
"address": "Dorton Road 80",
"email": "tsayb#opera.com",
"tel": "(0351) 48158101",
"schedule": "21:00",
"orderDetailList": [
{
"quantity": 1,
"product": null
},
{
"quantity": 1,
"product": null
}
]
}
Note that products are null, but my products id 1 and 2 do exist in the products crud. Why are them not being saved(or returned) ?
Thank you very much for your time.

each OrderDetail holds an id of one Product (products can be repeated, therefore it should be ManyToOne).
I think you are right. Many orderDetail to One product.
Could you check in the database to see if the product_id is inserted into the order_details table? Note that "saved, not fetched" and "not saved, not fetched" are two problems.
Could you add fetch = FetchType.EAGER in the #ManyToOne and #OneToMany annotations to make sure that they are fetched eagerly?
Besides, it is not relevant to your question, but plural forms are conventionally forbidden as table names such as products.

Related

FetchType.LAZY is working as FetchType.EAGER

I have a problem with hibernate, namely with lazy data fetching. I have two entities and one of them clinic is having relation #OneToMany. I setup FetchType to LAZY, but still, when I am making request to get all clinic, I still have an array in the clinic entity. The questions is - why I am still getting full #OneToMany array? Shouldn't it be null?
Here I provided my entity classes
#Getter
#Setter
#Entity(name = "clinic")
public class Clinic {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToMany(fetch = FetchType.LAZY, mappedBy="clinic")
private List<Doctor> doctors;
}
And doctor entity
#Getter
#Setter
#Entity(name = "doctor")
public class Doctor {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstname;
private String lastname;
#ManyToOne(fetch = FetchType.LAZY)
#JsonIgnore
private Clinic clinic;
}
And my controller, which is JpaRepository interface
#RestController
#RequestMapping("clinics")
public class ClinicController {
private final ClinicRepository clinicRepository;
public ClinicController(ClinicRepository clinicRepository) {this.clinicRepository = clinicRepository;}
#GetMapping()
public List<Clinic> getAllClinics() {
return clinicRepository.findAll();
}
}
I also logged SQL provided by Hibernate and it looks like this
Hibernate: select clinic0_.id as id1_0_, clinic0_.name as name2_0_ from clinic clinic0_
Hibernate: select doctors0_.clinic_id as clinic_i4_1_0_, doctors0_.id as id1_1_0_, doctors0_.id as id1_1_1_, doctors0_.clinic_id as clinic_i4_1_1_, doctors0_.firstname as firstnam2_1_1_, doctors0_.lastname as lastname3_1_1_ from doctor doctors0_ where doctors0_.clinic_id=?
Hibernate: select doctors0_.clinic_id as clinic_i4_1_0_, doctors0_.id as id1_1_0_, doctors0_.id as id1_1_1_, doctors0_.clinic_id as clinic_i4_1_1_, doctors0_.firstname as firstnam2_1_1_, doctors0_.lastname as lastname3_1_1_ from doctor doctors0_ where doctors0_.clinic_id=?
As I understand, it supposed to send only one query (first select).
And of course, I also will provide an answer that I am getting right now
[
{
"id": 1,
"name": "Clinic 1",
"doctors": [
{
"id": 1,
"firstname": "Bob",
"lastname": "Fisher"
},
{
"id": 2,
"firstname": "John",
"lastname": "Cena"
}
]
},
{
"id": 2,
"name": "Clinic 2",
"doctors": [
{
"id": 3,
"firstname": "Some",
"lastname": "guy"
}
]
}
]
The association is lazily loaded because Jackson (JSON serialization library) simply serializes this proxy collection. Lazy loading is possible because Spring enables the open-session-in-view by default. If you turn that config off, you will see a LazyInitializationException.
What you want though, is to tell Jackson not to serialize the collection, by annotating it with #JsonIgnore:
#JsonIgnore
#OneToMany(fetch = FetchType.LAZY, mappedBy="clinic")
private List<Doctor> doctors;

How can we aggregate results in a list for one to many relationship in spring jpa

I have 2 tables with structures like this :
Table 1: user
id, // Primary key
name,
email
Table 2: user_social
user_id, // Foreign key to user table
social_channel,
social_link
The data is as follows:
user:
1, "Ram", "ram#gmail.com"
user_social:
1, "FB", "fb.com/ram"
1, "INSTA", "insta.com/ram"
And the respective JPA entities classes:
User.java
#Entity
#Table(name = "user")
public class User {
#Id
UUID id;
String name;
String email;
#OneToMany(mappedBy = "id.userObj")
Set<UserSocial> userSocial = new HashSet<>();
}
UserSocial.java
#Entity
#Table(name = "user_social")
public class UserSocial {
// there is no primary key to the table and the whole row is unique
#EmbeddedId
UserSocialId id;
#Embeddable
public static class UserSocialId {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
User userObj;
String social_link;
String social_channel
}
}
Now when I try to fetch all the user details, I get duplicate results like this:
[
{
"id": 1,
"name": "Ram",
"email": "ram#gmail.com",
"userSocial": [
{ "social_link": "fb.com/ram", "social_channel": "FB" },
{ "social_link": "insta.com/ram", "social_channel": "INSTA" }
]
},
{
"id": 1,
"name": "Ram",
"email": "ram#gmail.com",
"userSocial": [
{ "social_link": "fb.com/ram", "social_channel": "FB" },
{ "social_link": "insta.com/ram", "social_channel": "INSTA" }
]
}
]
I believe this is because JPA makes a join query and since the user_social table has 2 rows in it, it duplicates the result for it. Is there a way where we could aggregate the user_social results in a list and thus have a single JSON element in response?
EDIT:
I am using an interface interface UserRepository extends JpaRepository<User, UUID> 's method findAll method to fetch results.

Spring boot JPA - What is the best solution for updating the intermediate table of ManyToMany relationship with extra columns in the Controller?

I am using Spring Boot for backend in my project. In the database (MySQL) I have a many-to-many relationship which has the next entities: User, interest and relUserInterest. The RelUserInterest is a intermediate table between User and Interest and has extra columns.
User Entity
#Entity
public class User {
#Id #GeneratedValue private Long id;
#NotNull
#Column (unique = true) private String email;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
#NotNull
Set<RelUserInterest> priority = new HashSet<>();
// more attributes, constructor, get and set
}
Interest entity
#Entity
public class Interest {
#Id
#GeneratedValue
private long id;
#NotEmpty
#Column(unique = true)
private String nameInterest;
#OneToMany(mappedBy = "interest", cascade = CascadeType.ALL)
#NotNull
Set<RelUserInterest> priority = new HashSet<>();
// more attributes, constructor, get and set
}
RelUserInterest entity
#Entity
#Table(name = "rel_user_interest")
#IdClass(UserInterestId.class)
public class RelUserInterest implements Serializable {
#Id
#ManyToOne
#JoinColumn(name = "user_id", referencedColumnName = "id")
User user;
#Id
#ManyToOne
#JoinColumn(name = "interest_id", referencedColumnName = "id")
Interest interest;
int priority;
// constructor, get and set
}
So far so good. I want to update the user and their interests. My controller is this. I want to do is when I update a existing user, update the intermediate table (RelUserInterest).
#PutMapping("/update/{id}")
public ResponseEntity<?> updateUser(#Validated #RequestBody UserDTOUpdate userDTOUpdate, BindingResult result, #PathVariable Long id) {
// More code
User user = usersService.getUserById(id);
// Code updating other attributes
// Here this the problem --> I don't know how to update the attribute priority of RelUserInterest
usersService.updateUser(user);
return new ResponseEntity<>(new Mensaje("Usuario actualizado"), HttpStatus.CREATED);
}
I have found several links but I'm not sure which is the best solution and I don't know how do it with my code.
How to update ManyToMany with extra columns
Spring boot JPA many to many with extra column insert and update
issue
Spring-Data-JPA ManyToMany relationship with extra column
In the Postman i want to send the next JSON although the interest array can be different if it is necessary for the solution:
{
"age": 22,
"genre": "Male",
"userName": "Miguel",
"roles": [
"ROLE_ADMIN",
"ROLE_USER"
],
"interest": [
{
"interestID": 1,
"nameInterest": "Museum",
"priority": 9
}
]
}
Question
So, the question is: How can I update the attribute priority of the RelUserEntity table? I suppose that making an intermediate table repository is a mistake. I'm a little lost. I hope you can help me. Thank you.

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

Bidirectional mapping does not pick up parent

I have a bidirectional relationship between two entities. Here are the two classes, getters/setters omitted:
#Entity
#Table(name = "strategy")
public class Strategy {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
#OneToMany(mappedBy = "strategy", cascade = { CascadeType.ALL })
private List<StrategyDetail> details;
}
And
public class StrategyDetail {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String content;
#ManyToOne()
private Strategy strategy;
}
I then have a repository and controller over this, but nothing worth mentionning (my repository only extends the JpaRepository and the controller has a get/post mapping).
When I try to create a strategy, I send this JSON:
{
"name": "my strat",
"details": [{
"content": "my content"
}]
}
and the response is this:
{
"id": 1,
"name": "my strat",
"details": [
{
"id": 2,
"content": "my content",
"strategy": null
}
]
}
I don't get why the strategy is null here, the mapping is fairly obvious. When I then try to get the strategy, the answer is this:
[
{
"id": 1,
"name": "my strat",
"details": []
}
]
Which I guess is working as intended (foreign key failure so the detail has not been saved?).
What should I change to make it work (meaning having access to the strategy inside the strategyDetail)? Even better I think would be having the strategy id rather than the whole strategy.
I've tried removing the bidirectionality by removing the strategy property in StrategyDetail and then it does properly save the details, but then I obviously don't have access to the strategy field inside the detail...
I've been looking for a solution on this for the past hour, looking at tutorials and what not, but I have not found a working solution.

Resources