Search code examples
javapostgresqlspring-bootjpaone-to-many

The One-To-Many relationship is not working in my Spring Boot application with Postgres database


I'm trying to use Joins in my Spring Boot application with Postgres database. Some of the entities have a One-to-One relationship and some have multiple One-to-Many relationships. The code for the Entity classes and the runner function have been shown below for reference. I've not shown the Entity classes for DevicesRequired and Benefit. They follow the same structure as DevicesReceived with the same "personal_details_id_no" field in the @JoinColumn, which is also present in each of the tables in my DB. My issue is that when I execute my "saveData" runner function, the "personal_details_id_no" in my "owner" tables with the @ManyToOne annotations are always null. However, the @OneToOne field (i.e. address_id_no) in PersonalDetail table is filled properly and doesn't give any issues.
I've tried going through the documentation, and I'm following the steps properly but the issue remains. Any help would be greatly appreciated.

Note: I'm using liquibase for table creation. I've already created the "personal_details_id_no" in each of the owner tables.

@Entity
@Table(name="personal_details")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder(toBuilder = true)
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class PersonalDetail extends Auditable<String> implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    private String idNo;

    private String gender;

    private String name;

    // For Address
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "address_id_no", referencedColumnName = "idNo")
    private Address address;

    // For Devices Received
    @JsonManagedReference
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "personalDetail", cascade = CascadeType.ALL)
    private List<DevicesReceived> devicesReceivedList;

    // For Devices Required
    @JsonManagedReference
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "personalDetail", cascade = CascadeType.ALL)
    private List<DevicesRequired> devicesRequiredList;

    // For Benefits
    @JsonManagedReference
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "personalDetail", cascade = CascadeType.ALL)
    private List<Benefit> benefitList;
}

@Entity
@Table(name = "address")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder(toBuilder = true)
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class Address extends Auditable<String> implements Serializable {
    private static final long serialVersionUID = 1L;
    
    @Id
    private String idNo;

    private String address;
}
@Entity
@Table(name="devices_received")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder(toBuilder = true)
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class DevicesReceived extends Auditable<String> implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Integer id;

    private String idNo;

    private String device;

    @JsonBackReference
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "personal_details_id_no", nullable = false)
    private PersonalDetail personalDetail;
}
// Runner Function
public void saveData(DetailsResponseDTO detailsResponseDTO) throws Exception {
    PersonalDetail personalDetail = new PersonalDetail().toBuilder()
            .idNo(detailsResponseDTO.getData().idNo())
            .name(detailsResponseDTO.getData().getName())
            .address(detailsResponseDTO.getData().getAddress())
            .devicesReceivedList(detailsResponseDTO.getData().getDevicesReceived())
            .devicesRequiredList(detailsResponseDTO.getData().getDevicesRequired())
            .benefitList(detailsResponseDTO.getData().getBenefits())
            .build();
    try {
        personalDetailRepository.save(personalDetail);
    } catch (Exception e) {
        System.out.println("Error while saving personalDetail Details: " + personalDetail + ", with Exception: " + e.toString());
        throw e;
    }
}

Solution

  • I figured out the answer. It seems that a setter function has to be explicitly defined in the PersonalDetail class which establishes the relationship between the parent and child entity. Something like the below function, which needs to be explicitly called instead of using the Builder.

    public void setDevicesReceivedList(List<DevicesReceived> devicesReceivedList) {
        for(DevicesReceived devicesReceived : devicesReceivedList) {
            devicesReceived.setPersonalDetail(this);
            this.devicesReceivedList.add(devicesReceived);
        }
    }
    

    And this setter has to be explicitly called in the runner function i.e.

    personalDetail.setDevicesReceivedList(detailsResponseDTO.getData().getDevicesReceived());