Michiel Timmerman
Michiel Timmerman

Reputation: 353

How to deal with circular references in JPA and Hibernate with associative entity

I've got 3 entities: Realestate, Realtor and the associative entity RealestateRealtor. One Realestate can have multiple Realtors and vice versa. The many-to-many relation has an extra entity RealestateRealtor with a specific url field.

Question: How can I create a working reference using @JsonIgnore, @JsonManaged etc... so I can request a Realtor having all of it's Realestate childs and in another request a Realestate having all of it's Realtor childs without crippling one of them or being unable to save because of an entity not being set because of ignoring deserialization.

Realtor:

@Entity
@Table(name="realtor",uniqueConstraints={@UniqueConstraint(name = "uniquerealtor", columnNames = {"name", "streetname", "housenumber", "housenumberaddition", "cityname"})})
public class Realtor {

    @Id 
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer idrealtor;
    
    @NotEmpty
    @Column(length = 100)
    private String name;
    
    @NotEmpty
    private String streetname;
    
    @NotNull
    private Integer housenumber;
    
    @Column(length = 5)
    private String housenumberaddition;
    
    @NotEmpty
    private String cityname;

    @OneToMany(mappedBy = "realtor", cascade = CascadeType.ALL)
    @JsonBackReference(value = "realestateref")
    private Set<RealestateRealtor> realestaterealtor = new HashSet<RealestateRealtor>();
    
    public Integer getIdrealtor() {
        return idrealtor;
    }

    public void setIdrealtor(Integer idrealtor) {
        this.idrealtor = idrealtor;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getStreetname() {
        return streetname;
    }

    public void setStreetname(String streetname) {
        this.streetname = streetname;
    }

    public Integer getHousenumber() {
        return housenumber;
    }

    public void setHousenumber(Integer housenumber) {
        this.housenumber = housenumber;
    }

    public String getHousenumberaddition() {
        return housenumberaddition;
    }

    public void setHousenumberaddition(String housenumberaddition) {
        this.housenumberaddition = housenumberaddition;
    }

    public String getCityname() {
        return cityname;
    }

    public void setCityname(String cityname) {
        this.cityname = cityname;
    }

    public Set<RealestateRealtor> getRealestaterealtor() {
        return realestaterealtor;
    }

    public void setRealestaterealtor(Set<RealestateRealtor> realestaterealtor) {
        this.realestaterealtor = realestaterealtor;
    }
}

Realestate:

@Entity
@Table(name="realestate",uniqueConstraints={@UniqueConstraint(name = "uniquerealestate",columnNames = {"streetname" , "housenumber", "housenumberaddition", "cityname"})})
public class Realestate {

    @Id 
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer idrealestate;
    
    @NotEmpty()
    private String streetname;
    
    @NotNull
    private Integer housenumber;
    
    @Column(length = 5)
    @javax.validation.constraints.NotNull
    private String housenumberaddition;
    
    @NotEmpty()
    private String cityname;
    
    @OneToMany(mappedBy = "realestate", cascade = CascadeType.ALL)
    @JsonManagedReference(value = "realestateref")
    private Set<RealestateRealtor> realestaterealtor = new HashSet<RealestateRealtor>();
    
    public Integer getIdrealestate() {
        return idrealestate;
    }

    public void setIdrealestate(Integer idrealestate) {
        this.idrealestate = idrealestate;
    }

    public String getStreetname() {
        return streetname;
    }

    public void setStreetname(String streetname) {
        this.streetname = streetname;
    }

    public Integer getHousenumber() {
        return housenumber;
    }

    public void setHousenumber(Integer housenumber) {
        this.housenumber = housenumber;
    }

    public String getHousenumberaddition() {
        return housenumberaddition;
    }

    public void setHousenumberaddition(String housenumberaddition) {
        this.housenumberaddition = housenumberaddition;
    }

    public String getCityname() {
        return cityname;
    }

    public void setCityname(String cityname) {
        this.cityname = cityname;
    }

    public Set<RealestateRealtor> getRealestaterealtor() {
        return realestaterealtor;
    }

    public void setRealestaterealtor(Set<RealestateRealtor> realestaterealtor) {
        this.realestaterealtor = realestaterealtor;
    }
}

RealestateRealtor:

@Entity
@Table(name="realestaterealtor")
public class RealestateRealtor {
    
    @Id 
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer idrealestaterealtor;
    
    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "idrealestate")
    @JsonBackReference(value = "realestateref")
    private Realestate realestate;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "idrealtor")
    private Realtor realtor;

    @Column(name = "realestateurl")
    private String realestateurl;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "idrealestaterealtor", nullable = true)
    private Set<Media> media = new HashSet<Media>();
    
    public Integer getIdrealestaterealtor() {
        return idrealestaterealtor;
    }

    public void setIdrealestaterealtor(Integer idrealestaterealtor) {
        this.idrealestaterealtor = idrealestaterealtor;
    }

    public Realestate getRealestate() {
        return realestate;
    }
    
    public void setRealestate(Realestate realestate) {
        this.realestate = realestate;
    }

    public Realtor getRealtor() {
        return realtor;
    }
    
    public void setRealtor(Realtor realtor) {
        this.realtor = realtor;
    }

    public String getRealestateurl() {
        return realestateurl;
    }

    public void setRealestateurl(String realestateurl) {
        this.realestateurl = realestateurl;
    }

    public Set<Media> getMedia() {
        return media;
    }

    public void setMedia(Set<Media> media) {
        this.media = media;
    }
}

The posted code only works for the Realestate request, not the other way around. The Realtor request only shows itself not it's children.

What have I tried without success:

  1. @JSonIgnore only on the getters
  2. @JSonProperties ... allowGetters = false
  3. Multiple Managed and Back references with unique values

I've not tried JSonViews, @JsonIdentityInfo or a custom Serializer

@Entity
@Table(name="realestate",uniqueConstraints={@UniqueConstraint(name = "uniquerealestate",columnNames = {"streetname" , "housenumber", "housenumberaddition", "cityname"})})
    @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,property = "idrealestate")
    public class Realestate {
@Entity
@Table(name="realtor",uniqueConstraints={@UniqueConstraint(name = "uniquerealtor", columnNames = {"name", "streetname", "housenumber", "housenumberaddition", "cityname"})})
    @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,property = "idrealtor")
    public class Realtor {

This resulted in a result of Realestate with all Realtors, but also resulted in a result of Realtor with all Realestate and all Realtors belonging to the Realesatet, this last 'issue' was tackled bij nullifying it in the controller. The Realtor

Upvotes: 1

Views: 93

Answers (1)

You can do using JSonViews but if you have a service layer where the transaction ends and a rest controller layer, in the rest controller layer, you can break the bi-directional recursion in the rest controller code.

  • On the rest endpoint that return type Realestate, then in the rest controller, realState.getRealestaterealtor().forEach(item -> item.setRealState(null)
  • On the rest endpoint that return type RealestateRealtor, then in the rest controller, realestateRealtor.getRealestate().setRealestateRealtor(null)

Upvotes: 1

Related Questions