1

I'm writing a OneToMany relation using spring boot, one property can have many propertySale.

This is my property class:

@Data
@Getter
@Entity
@Table(name = "Property")
public class Property {
    @Id
    @GeneratedValue
    private Long id;

    @OneToMany(mappedBy="property", cascade = CascadeType.ALL, targetEntity = PropertySale.class)
    @JsonManagedReference
    private Set<PropertySale> propertySales;
...

This is my propertySale class:

@Data
@Getter
@Entity
@Table(name = "PropertySale")
public class PropertySale {
    @Id
    @GeneratedValue
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "property_id", referencedColumnName = "id")
    @JsonBackReference
    private Property property;
...

I save propertySale like this:

@Override
    public ResponseEntity<PropertySale> savePropertySale(PropertySale propertySale) {
        Optional<Property> existPropertyOpt = this.propertyRepository.findById(propertySale.getProperty().getId());

        if(existPropertyOpt.isPresent()){
            Example<PropertySale> propertySaleExample =  Example.of(propertySale);
            Optional<PropertySale> existPropSale = this.propertySaleRepository.findOne(propertySaleExample);
            if(existPropSale.isPresent()){
                throw new PropertySaleAlreadyExistException();

            }else{
                Property existProperty = existPropertyOpt.get();
                propertySale.setProperty(existProperty);
                existProperty.addPropertySale(propertySale);
                this.propertyRepository.save(existProperty);
                return new ResponseEntity<>(propertySale, HttpStatus.CREATED);
            }

        }else{
            throw new PropertyNotFoundException(propertySale.getProperty().getId());
        }
    }

I get

Caused by: java.lang.StackOverflowError
    at java.util.AbstractSet.hashCode(AbstractSet.java:122)
    at org.hibernate.collection.internal.PersistentSet.hashCode(PersistentSet.java:459)
    at com.mikason.PropView.dataaccess.estateEntity.Property.hashCode(Property.java:12)
    at com.mikason.PropView.dataaccess.commercialEntity.PropertySale.hashCode(PropertySale.java:10)
    at java.util.AbstractSet.hashCode(AbstractSet.java:126)
    at org.hibernate.collection.internal.PersistentSet.hashCode(PersistentSet.java:459)
    at com.mikason.PropView.dataaccess.estateEntity.Property.hashCode(Property.java:12)
    at com.mikason.PropView.dataaccess.commercialEntity.PropertySale.hashCode(PropertySale.java:10)
    at java.util.AbstractSet.hashCode(AbstractSet.java:126)
...

when I try to save a propertySale, could someone please tell me where I did wrong? Thank you very much.

2 Answers2

6

Short answer

Add @EqualsAndHashCode.Exclude annotation to the field property of PropertySale.

Long answer

That happens because:

  1. the default implementation of Set used by Hibernate is HashSet, which is based on the hash code of its elements to store them, and...

  2. Since you are using Lombok's @Data annotation, the hash code (and also equals and toString) implementations take all of the class fields into consideration. This means that Property.hashCode() calls PropertySale.hashCode() and vice-versa, causing the stack overflow error you are getting whenever any one of them is called (this is also going to happen if you call .equals() or .toString() with any of these two classes).

In order to fix this, you have some options available:

  • Replace @Data with @Getter and @Setter on class Property. Since it is not used as an element inside a Set, it probably doesn't need to override hashCode/equals, unlike PropertySale.
  • Add @EqualsAndHashCode.Exclude (and @ToString.Exclude) on field PropertySale.property, so PropertySale.hashCode won't call Property.hashCode.
  • Write your own hashCode/equals implementations for PropertySale (in this case Lombok won't generate them) without calling Property.hashCode (you could still use Property.id though, for example).

Bonus

As I mentioned the same problem may arise from toString, but the correction is almost the same as for equals/hashCode: ToString.Exclude / avoiding @Data / custom implementation ...

You can also write unit tests to be sure that neither of these methods will throw a StackOverflowError when you run your application.

Gustavo Passini
  • 2,348
  • 19
  • 25
0

Quick Fix:

Change your hashCode to exclude propertySale.

I got the same problem with OneToMany; then realize that the HashCode is making an infinite loop.

You only need to change the hashCode method to exclude it, then it will solve your problem.

zmerr
  • 534
  • 3
  • 18