I have two models that are related in one-to-one relation. I am defying the save method of one of those models in-service class. But when I saving them through rest API it is appending the same object continously.
I have 2 models Invoice and CustomerOrder with one-to-one relation.
Invoice model
@Entity
public class CustomerOrder {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(nullable = true)
private LocalDate date;
@OneToOne(cascade = CascadeType.ALL, mappedBy = "customerOrder")
private Invoice invoice;
public Invoice getInvoice() {
return invoice;
}
public void setInvoice(Invoice invoice) {
this.invoice = invoice;
}
....
and Invoice
@Entity
public class Invoice {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(precision = 8, scale = 2)
private BigDecimal amount;
private LocalDate issued;
private LocalDate due;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "order_id")
private CustomerOrder customerOrder;
public CustomerOrder getCustomerOrder() {
return customerOrder;
}
public void setCustomerOrder(CustomerOrder customerOrder) {
this.customerOrder = customerOrder;
}
...
And my service save method
public void saveInvoice(Invoice invoice, Long order_id) {
CustomerOrder customerOrder = customerOrderRepository.getById(order_id);
invoice.setCustomerOrder(customerOrder);
invoiceRepository.save(invoice);
}
Controller method
@PostMapping(path = "{order_id}")
public void saveInvoice(@RequestBody Invoice invoice,@PathVariable(value = "order_id") Long order_id) {
invoiceService.saveInvoice(invoice, order_id);
}
But when saving the same object is appending continously.
"id": 3,
"amount": 10.20,
"issued": "2020-09-20",
"due": "2020-09-21",
"customerOrder": {
"id": 2,
"date": "2020-09-20",
"invoice": {
"id": 3,
"amount": 10.20,
"issued": "2020-09-20",
"due": "2020-09-21",
"customerOrder": {
"id": 2,
"date": "2020-09-20",
"invoice": {
"id": 3,
"amount": 10.20,
"issued": "2020-09-20",
"due": "2020-09-21",
"customerOrder": {
"id": 2,
"date": "2020-09-20",
"invoice": { ....
On and on.
CodePudding user response:
What is happening actually here:
CustomerOrder
Entity has a property ofInvoice
namedinvoice
.Invoice
Entity has a property ofCustomerOrder
namedcustomerOrder
.
While jackson serializer try to serialize this CustomerOrder
it sees it has a invoice
property which also has customerOrder
inside it. Although there is only two object of customerOrder
& invoice
referring each other, but while serializing it recursively goes down.
Solution:
Using @JsonIgnore
:
Put a @JsonIgnore
on cusotmerOrder
property of Invoice
entity which will prevent serializing customerOrder
inside the Invoice
entity.
Pros: Less overhead Cons: Cannot dynamically control when to serialize upto which level.
Using @JsonIgnore
or using a pair of @JsonBackReference
& @JsonManagedReference
has the similar cons.
Others:
For dynamically control the depth of your response in different responses (for different routes) you can go for @JsonView
or @JsonFilter
.
Usage Examples:
CodePudding user response:
@OneToOne Is not best option for the relation, f.e. it limits LAZY loading off entity. Also u have CascadeType.ALL from both sides of the relation which may cause some problems. Change the relation to @OneToMany one CustomerOrder and many Invoice and drop the cascade. Save CustomerOrder first and then Invoice. But if u want to use @OneToOne check out annotation @MapsId https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/
It is not good practice to use directly entity as a RequestBody in REST api. Try to use InvoiceRequest class as input and InvoiceDTO as output to separate view from business logic. That will also fix Jackson serialization/deserialization issues.