2

I am using Spring JPA to save entities into a database. Below is code structure

Customer.java

@Entity
@Table(name="customer")
@Data
public class Customer {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String name;

    @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL)
    private Set<Order> orders = new HashSet<>();

    public void add(Order order) {
        if(order != null) {
            if(orders == null) {
                this.orders = new HashSet<>();
            }
            this.orders.add(order);
            order.setCustomer(this);
        }
    }
}

Order.java

@Entity
@Table(name="order")
@Data
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String orderTrackingNumber;
    private String status;

    @ManyToOne
    @JoinColumn(name = "customer_id")
    private Customer customer;
}

Now I am trying to save customer along with its orders in database like below

OrderService.java

@Transactional
public PurchaseResponse placeOrder(Purchase purchase) {

    Order order = purchase.getOrder();

    String orderTrackingNumber = UUID.randomUUID().toString();
    order.setOrderTrackingNumber(orderTrackingNumber);

    Customer customer = purchase.getCustomer();
    customer.add(order);

    this.customerRepository.save(customer);

    return new PurchaseResponse(orderTrackingNumber);
}

When this.customerRepository.save(customer); statement executes then I get following exception

java.lang.StackOverflowError: null
at com.ecomhunt.entities.Order.hashCode(Order.java:15) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.ecomhunt.entities.Customer.hashCode(Customer.java:11) ~[classes/:na]
at com.ecomhunt.entities.Order.hashCode(Order.java:15) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.ecomhunt.entities.Customer.hashCode(Customer.java:11) ~[classes/:na]
at com.ecomhunt.entities.Order.hashCode(Order.java:15) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.ecomhunt.entities.Customer.hashCode(Customer.java:11) ~[classes/:na]
......

If I comment order.setCustomer(this); line in Customer#add(order), then error disappears but in database the customer_id is not save in order table.

I am not sure what I am doing wrong.

Junaid
  • 2,572
  • 6
  • 41
  • 77

2 Answers2

8

@Data annotation generates equals and hashCode methods relying on entity fields. When you placing order into Customer#orders, HashSet tries to compute hashCode of Order, Order refers to Customer which in turn refers to Customer#orders - that is why you are getting SO, basically you just need to correctly define equals and hashCode methods and do not rely on lombok magic.

Using Primiry Key (id) while overriding equals() and hashCode() methods

Andrey B. Panfilov
  • 4,324
  • 2
  • 12
  • 18
  • Right. I changed `@Data` to `@Getter` and `@Setter`. Now it works fine. Thanks for pointing out the `lombok` limitation for JPA entities. – Junaid Jul 22 '22 at 10:24
  • 1
    That is not a limitation of `lombok`, [it's configuration capabilities are extremely flexible](https://projectlombok.org/features/EqualsAndHashCode), take a look on `@EqualsAndHashCode` parameters: `of`, `callSuper`, `onlyExplicitlyIncluded`, the main problem here is to define those methods correctly for JPA entities, and there is no best answer how to do that properly. – Andrey B. Panfilov Jul 22 '22 at 10:34
  • As [this article](https://thorben-janssen.com/lombok-hibernate-how-to-avoid-common-pitfalls/) from Thorben Janssen states that, it is generally good idea to avoid using `Lombok` annotation on `Entity class`. Rather, we can use IDE to generate getter/setter and define certain method by ourselves as we wish. – Enfield Li Jul 22 '22 at 14:23
2

I found that the implicit toString feature of the @Data annotation was causing the StackOverFlow Error.

Remove your @Data Annotation and explicitly define the methods you want to use instead, e.g. getter and setter methods

Junaid
  • 2,572
  • 6
  • 41
  • 77
R_Shaad
  • 21
  • 3