4

I'm still grasping JPA concepts and can't seem to find the answer to my question anywhere!

Assume

Both classes are annotated with @GeneratedValue(strategy = GenerationType.IDENTITY), There are all getters and setters.

Parent{
    ....
    @OneToMany(mappedBy = "parent")
    Collection<Child> children;
    ....
}

Child{
    ...
    @JoinColumn(name = "parent", referencedColumnName = "id", nullable = true)
    @ManyToOne(optional = false)
    Parent parent;
    ...
}

I then implemented the standard JpaRepository and setup my controller

Here is the problem
When I query all child records, only the first child record that was mapped to a particular parent will have the parent entity object in it. the rest will have an id referencing the parent enity.

Here is an example: Getting all children from POSTMAN returns:

[
    {
        "id": 1,
        "name": "child1",
        "parent": {
            "id": 1,
            "firstName": "..."
            ...
            }
    },
    {
        "id": 2,
        "name": "child2",
        "parent": 1
    }
    {
        "id": 3,
        "name": "child3",
        "parent": {
            "id": 2,
            "firstName": "..."
            ...
            }
    },
    {
        "id": 4,
        "name": "child4",
        "parent": 2
    }
]

As you can see child2 only has "parent": 1 since child1 mapped to that parent first! Similarly child4 only has "parent": 2 since child3 mapped to that parent first!

Can anyone explain this behaviour please? I tried fetch = FetchType.EAGER on parent but it did not help! I expect all children to have a comprehensive parent object to prevent another DB trip.

Thanks in advance!

UPDATING THE QUESTION WITH THE ACTUAL CLASSES:


PARENT

package backend.application.payroll.models;

import com.fasterxml.jackson.annotation.*;
import lombok.Data;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.*;

@Data
@Entity
@Table(name = "employees")
@JsonIdentityInfo(
        generator = ObjectIdGenerators.PropertyGenerator.class,
        property = "id")
public class Employee implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    @Column(name = "emp_code", nullable = false)
    private String empCode;
    @Column(name = "first_name", nullable = false)
    private String firstName;
    @Column(name = "middle_name", nullable = true)
    private String middleName;
    @Column(name = "last_name", nullable = false)
    private String lastName;
    @Column(name = "dob", nullable = false)
    @Temporal(TemporalType.DATE)
    private Date dob;
    @Column(name = "id_number", nullable = true)
    private String idNumber;
    @Column(name = "passport_number", nullable = true)
    private String passportNumber;
    @Column(name = "email_address", nullable = true)
    private String emailAddress;
    @OneToOne(cascade = CascadeType.DETACH)
    @JoinColumn(name = "pay_grade", referencedColumnName = "id", nullable = true)
    private Salary payGrade;
    @Column(name = "basic_pay", nullable = true)
    private BigDecimal basicPay;
    @OneToOne(cascade = CascadeType.DETACH)
    @JoinColumn(name = "department", referencedColumnName = "id", nullable = true)
    private Department department;
    @OneToOne(cascade = CascadeType.DETACH)
    @JoinColumn(name = "position", referencedColumnName = "id", nullable = true)
    private Position position;
    @Column(name = "tax_number", nullable = true)
    private String taxNumber;
    @Column(name = "hire_date", nullable = true)
    @Temporal(TemporalType.DATE)
    private Date hireDate;
    @Column(name = "address1", nullable = true)
    private String address1;
    @Column(name = "address2", nullable = true)
    private String address2;
    @Column(name = "postal_code", nullable = true)
    private String postalCode;
    //country
    @Column(name = "phone_number", nullable = true)
    private String phoneNumber;
    //banking details

    //HERE IT WORKS FINE SINCE IT'S ONETOONE - YOU CAN IGNORE
    @OneToOne(mappedBy = "employee")
    //@JsonManagedReference//used in conjunction with @JsonBackReference on the other end - works like @JsonIdentityInfo class annotation.
    private User user;

    //THIS IS WHAT CAUSING THE PROBLEM
    @OneToMany(mappedBy = "owner", fetch = FetchType.LAZY)
    //@JsonBackReference
    @JsonIgnore
    private Set<Costcentre> costcentres = new HashSet<>();

    public Employee() {

    }
}

CHILD

package backend.application.payroll.models;

import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import lombok.Data;

import javax.persistence.*;
import java.io.Serializable;


@Entity
@Table(name = "costcentres")
@Data
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Costcentre implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    @Column(name = "name", nullable = false)
    private String name;
    @Column(name = "description", nullable = true)
    private String description;
    @ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.EAGER)
    @JoinColumn(name = "owner", referencedColumnName = "id", nullable = true)
    //@JsonManagedReference
    private Employee owner; //CULPRIT

    public Costcentre() {

    }
    public Costcentre(long id, String name, String description) {
        super();
        this.id = id;
        this.name = name;
        this.description = description;
    }
}
Olivier M.
  • 51
  • 6

1 Answers1

0

Add JsonIdentityInfo to the parent and child and you can add fetch = FetchType.EAGER on the parent and JsonIgnore to ignore get cyclic child and parents

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")

, Like this:

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
Parent{
    ....
    @OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
    @JsonIgnore
    Collection<Child> children;
    ....
}

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
Child{
    ...
    @JoinColumn(name = "parent", referencedColumnName = "id", nullable = true)
    @ManyToOne(optional = false, fetch = FetchType.EAGER)
    Parent parent;
    ...
}
0xh3xa
  • 4,801
  • 2
  • 14
  • 28
  • Thanks for your reply @sc0der I already had these annotations except for `fetch = FetchType.EAGER` but even that did not solve the problem. the issue persists. I'm even thinking of dropping my entire DB since I only added this relationship later but I doubt that will help (I assume JPA is smart enough). The only I think I did was dropping the child table to ensure proper recreation of the FK but even with that I did not get any luck! – Olivier M. May 22 '20 at 20:29
  • I tried implementing `Serializable` earlier, it did not change a thing and I removed it. I've just put it back and same result. Something is clearly preventing multiple retrievals of a parent and I wonder what it is! In fact I don't even think it's got to do with multiple retrievals, it's just a matter of embedding the result of a previously retrieved entity! JSON issue? so confusing! – Olivier M. May 22 '20 at 20:54
  • Check this one: https://www.tutorialspoint.com/jackson_annotations/jackson_annotations_jsonidentityinfo.htm – 0xh3xa May 22 '20 at 20:55
  • And this one: https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion – 0xh3xa May 22 '20 at 20:56
  • I actually came across these articles and tried back and managed reference with no luck! Is it okay if Update my question with the actual 2 classes? (hope it fits) – Olivier M. May 22 '20 at 21:05
  • Done, I've added my classes. maybe your eyes will spot something I can't! – Olivier M. May 22 '20 at 21:15
  • Hmmm, Strange behavior!!!! – 0xh3xa May 22 '20 at 21:29
  • I guess this `JsonIdentityInfo` will solve the issue – 0xh3xa May 22 '20 at 21:29
  • I have upvoted the question, hope get the solution soon – 0xh3xa May 22 '20 at 21:33