Home > Software engineering >  The same value gets appended in rest post method
The same value gets appended in rest post method

Time:09-21

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 of Invoice named invoice.
  • Invoice Entity has a property of CustomerOrder named customerOrder.

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.

  • Related