0

I've been developing a SpringBoot application, and some entities of the project has OneToMany relationship, which has cascade all, for example:

    @OneToMany(mappedBy = "candidate", cascade = CascadeType.ALL, orphanRemoval = true)
    @LazyCollection(LazyCollectionOption.FALSE)
    private Set<CandidateAcademicEducation> academicEducations;

    @OneToMany(mappedBy = "candidate", cascade = CascadeType.ALL, orphanRemoval = true)
    @LazyCollection(LazyCollectionOption.FALSE)
    private Set<CandidateProfessionalExperience> professionalExperiences;

In all entities, this cascade was working just fine, i was sending the parent object with all related entities (Using JSON), it was being saved, updated and deleted correctly, but specifically on this CandidateAcademicEducation entity, when i update the Candidate entity (Sending the same data present on database to the application, including related entities), for some reason (When the candidate already has academicEducations on database), always one of the AcademicEducations loses candidate's id (If i send 1 academicEducation, it just loses candidate's id. If i send 3, 2 of them stay with parent's id and the other loses).

I've been reading and re-reading the code a lot, because CandidateProfessionalExperience is working just fine, and i can't find the difference between them.

I've verified preUpdate and postUpdate methods on Candidate entity, and it always has the correct amount of AcademicEducation, with the data and reference to Candidate entity correctly setted.

EntityDTO:


public abstract class EntityDTO<Entity> implements Comparable<EntityDTO<Entity>> {
    @JsonProperty(access = Access.READ_ONLY)
    protected Long id;

    public EntityDTO() {
        super();
    }

    public EntityDTO(Long id) {
        this.id = id;
    }

    public static<T, K extends EntityDTO<T>> T getParsedEntity(
        K entityDTO
    ) {
        if(entityDTO == null) return null;
        return entityDTO.toEntity();
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public int compareTo(EntityDTO<Entity> entityDTO) {
        if(entityDTO == null) return -1;

        boolean idIsNull = id == null;
        boolean receivedIdIsNull = entityDTO.getId() == null;

        if(idIsNull && receivedIdIsNull) return 0;
        if(idIsNull || receivedIdIsNull) return -1;
        
        return id.compareTo(entityDTO.getId());
    }

    public boolean equals(EntityDTO<Entity> entityDTO) {
        return this.compareTo(entityDTO) == 0;
    }

    public abstract Entity toEntity();
}

Candidate entity:

    @Entity
    @Table(name = "candidates")
    public class Candidate {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        private String photo;
        private String profession;
        private String phoneNumber;
        private String linkedin;
        private String birthDate;
        private boolean hasProfessionalExperience;
    
        @ManyToOne
        private State state;
    
        @ManyToOne
        private City city;
    
        @OneToOne
        private User user;
    
        @OneToMany(mappedBy = "candidate", cascade = CascadeType.ALL, orphanRemoval = true)
        @LazyCollection(LazyCollectionOption.FALSE)
        private Set<CandidateAcademicEducation> academicEducations;
    
        @OneToMany(mappedBy = "candidate", cascade = CascadeType.ALL, orphanRemoval = true)
        @LazyCollection(LazyCollectionOption.FALSE)
        private Set<CandidateProfessionalExperience> professionalExperiences;
    
        @CreationTimestamp
        @Column(updatable = false)
        private Date createdAt;
     
        @UpdateTimestamp
        private Date updatedAt;
        
        public Candidate() {
            super();
        }
    
        public Candidate(
            Long id, String photo, String profession,
            String phoneNumber, String linkedin, String birthDate,
            boolean hasProfessionalExperience, State state,
            City city, User user, Set<CandidateAcademicEducation> academicEducations,
            Set<CandidateProfessionalExperience> professionalExperiences,
            Date createdAt, Date updatedAt
        ) {
            this.id = id;
            this.photo = photo;
            this.profession = profession;
            this.phoneNumber = phoneNumber;
            this.linkedin = linkedin;
            this.birthDate = birthDate;
            this.hasProfessionalExperience = hasProfessionalExperience;
            this.state = state;
            this.city = city;
            this.user = user;
            this.academicEducations = academicEducations;
            this.professionalExperiences = professionalExperiences;
            this.createdAt = createdAt;
            this.updatedAt = updatedAt;
        }
    
        @PrePersist
        @PreUpdate
        private void prePersistAndUpdate() {
            academicEducations.forEach(academicEducation -> academicEducation.setCandidate(this));
            professionalExperiences.forEach(professionalExperience -> professionalExperience.setCandidate(this));
        }
    }

CandidateDTO:

  public class CandidateWithAllRelatedDataDTO extends CandidateDTO {
    private SortedSet<CandidateAcademicEducationDTO> academicEducations;
    private SortedSet<CandidateProfessionalExperienceDTO> professionalExperiences;
    
    public CandidateWithAllRelatedDataDTO() {
        super();
    }

    public CandidateWithAllRelatedDataDTO(
        Long id, String photo, String profession,
        String phoneNumber, String linkedin,
        String birthDate, Boolean hasProfessionalExperience,
        StateDTO state, CityDTO city,
        SortedSet<CandidateAcademicEducationDTO> academicEducations,
        SortedSet<CandidateProfessionalExperienceDTO> professionalExperiences,
        Long userId, Date createdAt, Date updatedAt
    ) {
        super(
            id, photo, profession, phoneNumber,
            linkedin, birthDate, hasProfessionalExperience,
            state, city, userId, createdAt, updatedAt
        );
        this.academicEducations = academicEducations;
        this.professionalExperiences = professionalExperiences;
    }

    public static CandidateWithAllRelatedDataDTO FromEntity(Candidate candidateEntity) {
        if(candidateEntity == null) return null;
        
        CandidateWithAllRelatedDataDTO candidateWithAllRelatedDataDTO = new CandidateWithAllRelatedDataDTO();
        candidateWithAllRelatedDataDTO.setId(candidateEntity.getId());
        candidateWithAllRelatedDataDTO.setPhoto(candidateEntity.getPhoto());
        candidateWithAllRelatedDataDTO.setProfession(candidateEntity.getProfession());
        candidateWithAllRelatedDataDTO.setPhoneNumber(candidateEntity.getPhoneNumber());
        candidateWithAllRelatedDataDTO.setLinkedin(candidateEntity.getLinkedin());
        candidateWithAllRelatedDataDTO.setBirthDate(candidateEntity.getBirthDate());
        candidateWithAllRelatedDataDTO.setHasProfessionalExperience(candidateEntity.hasProfessionalExperience());
        candidateWithAllRelatedDataDTO.setState(StateDTO.FromEntity(candidateEntity.getState()));
        candidateWithAllRelatedDataDTO.setCity(CityDTO.FromEntity(candidateEntity.getCity()));
        candidateWithAllRelatedDataDTO.setUserId(candidateEntity.getUser().getId());
        candidateWithAllRelatedDataDTO.setCreatedAt(candidateEntity.getCreatedAt());
        candidateWithAllRelatedDataDTO.setUpdatedAt(candidateEntity.getUpdatedAt());

        candidateWithAllRelatedDataDTO.setAcademicEducations(
            candidateEntity.getAcademicEducations().stream().map(
                academicEducation -> CandidateAcademicEducationDTO.FromEntity(academicEducation)
            ).collect(Collectors.toCollection(() -> new TreeSet<>()))
        );

        candidateWithAllRelatedDataDTO.setProfessionalExperiences(
            candidateEntity.getProfessionalExperiences().stream().map(
                professionalExperience -> 
                    CandidateProfessionalExperienceDTO.FromEntity(professionalExperience)
            ).collect(Collectors.toCollection(() -> new TreeSet<>()))
        );

        return candidateWithAllRelatedDataDTO;
    }

    public void update(CandidateWithAllRelatedDataDTO updatedCandidate) {
        String photo = updatedCandidate.getPhoto();
        if(photo != null) this.photo = photo;
        
        String profession = updatedCandidate.getProfession();
        if(profession != null) this.profession = profession;
        
        String phoneNumber = updatedCandidate.getPhoneNumber();
        if(phoneNumber != null) this.phoneNumber = phoneNumber;
        
        String linkedin = updatedCandidate.getLinkedin();
        if(linkedin != null) this.linkedin = linkedin;

        String birthDate = updatedCandidate.getBirthDate();
        if(birthDate != null) this.birthDate = birthDate;

        Boolean hasProfessionalExperience = updatedCandidate.hasProfessionalExperience();
        if(hasProfessionalExperience != null) this.hasProfessionalExperience = hasProfessionalExperience;

        StateDTO state = updatedCandidate.getState();
        if(state != null) this.state = state;

        CityDTO city = updatedCandidate.getCity();
        if(city != null) this.city = city;

        SortedSet<CandidateAcademicEducationDTO> academicEducations = updatedCandidate.getAcademicEducations();
        if(academicEducations != null) EntitySetUpdater.updateEntity(this.academicEducations, academicEducations);
        
        SortedSet<CandidateProfessionalExperienceDTO> professionalExperiences = updatedCandidate.getProfessionalExperiences();
        if(professionalExperiences != null) EntitySetUpdater.updateEntity(this.professionalExperiences, professionalExperiences);
    }

    @Override
    public Candidate toEntity() {
        Candidate candidate = super.toEntity();

        Set<CandidateAcademicEducation> parsedAcademicEducations = new HashSet<>();
        if(academicEducations != null) {
            parsedAcademicEducations.addAll(
                academicEducations.stream().map(
                    academicEducation -> getParsedEntity(academicEducation)
                ).collect(Collectors.toSet())
            );
        }
        candidate.setAcademicEducations(parsedAcademicEducations);

        Set<CandidateProfessionalExperience> parsedProfessionalExperiences = new HashSet<>();
        if(professionalExperiences != null) {
            parsedProfessionalExperiences.addAll(
                professionalExperiences.stream().map(
                    professionalExperience -> getParsedEntity(professionalExperience)
                ).collect(Collectors.toSet())
            );
        }
        candidate.setProfessionalExperiences(parsedProfessionalExperiences);

        return candidate;
    }
}

Candidate professional experience:

    @Entity
    @Table(name = "candidate_professional_experiences")
    public class CandidateProfessionalExperience {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        private String roleName;
        private String companyName;
        private String startDate;
        private String endDate;
        private boolean actualJob;
    
        @Column(length = 1000)
        private String assignmentsDescription;
    
        @ManyToOne
        private State state;
    
        @ManyToOne
        private City city;
    
        @ManyToOne
        private Candidate candidate;
    
        @CreationTimestamp
        @Column(updatable = false)
        private Date createdAt;
     
        @UpdateTimestamp
        private Date updatedAt;
        
        public CandidateProfessionalExperience() {
            super();
        }
    
        public CandidateProfessionalExperience(
            Long id, String roleName, String companyName,
            String startDate, String endDate, boolean actualJob,
            String assignmentsDescription, State state, City city,
            Candidate candidate, Date createdAt, Date updatedAt
        ) {
            this.id = id;
            this.roleName = roleName;
            this.companyName = companyName;
            this.startDate = startDate;
            this.endDate = endDate;
            this.actualJob = actualJob;
            this.assignmentsDescription = assignmentsDescription;
            this.state = state;
            this.city = city;
            this.candidate = candidate;
            this.createdAt = createdAt;
            this.updatedAt = updatedAt;
        }
    }

Candidate Professional Experience DTO:

       public class CandidateProfessionalExperienceDTO extends EntityDTO<CandidateProfessionalExperience> {
        private String roleName;
        private String companyName;
        private String startDate;
        private String endDate;
        private String assignmentsDescription;
        private boolean actualJob;
    
        private StateDTO state;
        private CityDTO city;
    
        @JsonProperty(access = Access.READ_ONLY)
        private Date createdAt;
    
        @JsonProperty(access = Access.READ_ONLY)
        private Date updatedAt;
        
        public CandidateProfessionalExperienceDTO() {
            super();
        }
    
        public CandidateProfessionalExperienceDTO(
            Long id, String roleName, String companyName, String startDate,
            String endDate, String assignmentsDescription, boolean actualJob,
            StateDTO state, CityDTO city, Date createdAt, Date updatedAt
        ) {
            super(id);
            this.roleName = roleName;
            this.companyName = companyName;
            this.startDate = startDate;
            this.endDate = endDate;
            this.assignmentsDescription = assignmentsDescription;
            this.actualJob = actualJob;
            this.state = state;
            this.city = city;
            this.createdAt = createdAt;
            this.updatedAt = updatedAt;
        }
    
        public static CandidateProfessionalExperienceDTO FromEntity(
            CandidateProfessionalExperience candidateProfessionalExperienceEntity
        ) {
            if(candidateProfessionalExperienceEntity == null) return null;
            
            CandidateProfessionalExperienceDTO candidateProfessionalExperienceDTO = new CandidateProfessionalExperienceDTO();
            candidateProfessionalExperienceDTO.setId(candidateProfessionalExperienceEntity.getId());
            candidateProfessionalExperienceDTO.setRoleName(candidateProfessionalExperienceEntity.getRoleName());
            candidateProfessionalExperienceDTO.setCompanyName(candidateProfessionalExperienceEntity.getCompanyName());
            candidateProfessionalExperienceDTO.setStartDate(candidateProfessionalExperienceEntity.getStartDate());
            candidateProfessionalExperienceDTO.setEndDate(candidateProfessionalExperienceEntity.getEndDate());
            candidateProfessionalExperienceDTO.setAssignmentsDescription(
                candidateProfessionalExperienceEntity.getAssignmentsDescription()
            );
            candidateProfessionalExperienceDTO.setActualJob(candidateProfessionalExperienceEntity.isActualJob());
            candidateProfessionalExperienceDTO.setState(StateDTO.FromEntity(candidateProfessionalExperienceEntity.getState()));
            candidateProfessionalExperienceDTO.setCity(CityDTO.FromEntity(candidateProfessionalExperienceEntity.getCity()));
            candidateProfessionalExperienceDTO.setCreatedAt(candidateProfessionalExperienceEntity.getCreatedAt());
            candidateProfessionalExperienceDTO.setUpdatedAt(candidateProfessionalExperienceEntity.getUpdatedAt());
    
            return candidateProfessionalExperienceDTO;
        }
    
        @Override
        public CandidateProfessionalExperience toEntity() {
            State parsedState = getParsedEntity(state);
            City parsedCity = getParsedEntity(city);
    
            return new CandidateProfessionalExperience(
                id, roleName, companyName, startDate, endDate,
                actualJob, assignmentsDescription, parsedState, parsedCity,
                null, null, null
            );
        }
    
        @Override
        public int compareTo(EntityDTO<CandidateProfessionalExperience> candidateProfessionalExperienceDTO) {
            if(candidateProfessionalExperienceDTO == null) return -1;
    
            CandidateProfessionalExperienceDTO parsedDTO = (CandidateProfessionalExperienceDTO) candidateProfessionalExperienceDTO;
            int roleNameResult = StringComparator.compareStrings(roleName, parsedDTO.getRoleName());
            if(roleNameResult != 0) return roleNameResult;
    
            return StringComparator.compareStrings(companyName, parsedDTO.getCompanyName());
        }
    }

Candidate Academic Education:

    @Entity
    @Table(name = "candidate_academic_education")
    public class CandidateAcademicEducation {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        private String title;
        private String institutionName;
        private Integer startYear;
        private Integer endYear;
        private CandidateLevelOfSchooling levelOfSchooling;
    
        @ManyToOne
        private Candidate candidate;
    
        @CreationTimestamp
        @Column(updatable = false)
        private Date createdAt;
     
        @UpdateTimestamp
        private Date updatedAt;
    
        public CandidateAcademicEducation() {
            super();
        }
    
        public CandidateAcademicEducation(
            Long id, String title, String institutionName,
            Integer startYear, Integer endYear,
            CandidateLevelOfSchooling levelOfSchooling,
            Candidate candidate, Date createdAt, Date updatedAt
        ) {
            this.id = id;
            this.title = title;
            this.institutionName = institutionName;
            this.startYear = startYear;
            this.endYear = endYear;
            this.levelOfSchooling = levelOfSchooling;
            this.candidate = candidate;
            this.createdAt = createdAt;
            this.updatedAt = updatedAt;
        }
    }

Candidate Academic Education DTO:

    public class CandidateAcademicEducationDTO extends EntityDTO<CandidateAcademicEducation> {
        private String title;
        private String institutionName;
        private Integer startYear;
        private Integer endYear;
        private CandidateLevelOfSchooling levelOfSchooling;
    
        @JsonProperty(access = Access.READ_ONLY)
        private Date createdAt;
    
        @JsonProperty(access = Access.READ_ONLY)
        private Date updatedAt;
        
        public CandidateAcademicEducationDTO() {
            super();
        }
    
        public CandidateAcademicEducationDTO(
            Long id, String title, String institutionName,
            Integer startYear, Integer endYear,
            CandidateLevelOfSchooling levelOfSchooling, Date createdAt,
            Date updatedAt
        ) {
            super(id);
            this.title = title;
            this.institutionName = institutionName;
            this.startYear = startYear;
            this.endYear = endYear;
            this.levelOfSchooling = levelOfSchooling;
            this.createdAt = createdAt;
            this.updatedAt = updatedAt;
        }
    
        public static CandidateAcademicEducationDTO FromEntity(
            CandidateAcademicEducation candidateAcademicEducationEntity
        ) {
            if(candidateAcademicEducationEntity == null) return null;
            
            CandidateAcademicEducationDTO candidateAcademicEducationDTO = new CandidateAcademicEducationDTO();
            candidateAcademicEducationDTO.setId(candidateAcademicEducationEntity.getId());
            candidateAcademicEducationDTO.setTitle(candidateAcademicEducationEntity.getTitle());
            candidateAcademicEducationDTO.setInstitutionName(candidateAcademicEducationEntity.getInstitutionName());
            candidateAcademicEducationDTO.setStartYear(candidateAcademicEducationEntity.getStartYear());
            candidateAcademicEducationDTO.setEndYear(candidateAcademicEducationEntity.getEndYear());
            candidateAcademicEducationDTO.setLevelOfSchooling(candidateAcademicEducationEntity.getLevelOfSchooling());
            candidateAcademicEducationDTO.setCreatedAt(candidateAcademicEducationEntity.getCreatedAt());
            candidateAcademicEducationDTO.setUpdatedAt(candidateAcademicEducationEntity.getUpdatedAt());
    
            return candidateAcademicEducationDTO;
        }
    
        @Override
        public CandidateAcademicEducation toEntity() {
            return new CandidateAcademicEducation(
                id, title, institutionName, startYear, endYear,
                levelOfSchooling, null, null, null
            );
        }
    
        @Override
        public int compareTo(EntityDTO<CandidateAcademicEducation> candidateAcademicEducationDTO) {
            if(candidateAcademicEducationDTO == null) return -1;
    
            CandidateAcademicEducationDTO parsedDTO = (CandidateAcademicEducationDTO) candidateAcademicEducationDTO;
            int titleResult = StringComparator.compareStrings(title, parsedDTO.getTitle());
            if(titleResult != 0) return titleResult;
    
            return StringComparator.compareStrings(institutionName, parsedDTO.getInstitutionName());
        }
    }

Candidate Services (Update method with auxiliar methods):

private void validateCandidateLinkedin(String linkedin) {
    boolean existsCandidateByLinkedin = candidateRepository.existsByLinkedin(
        linkedin
    );
    if(existsCandidateByLinkedin) {
        throw new ServerException(
            "A candidate with this linkedin already exists",
            HttpStatus.BAD_REQUEST
        );
    }
}

private void validateCandidatePhoneNumber(String phoneNumber) {
    boolean existsCandidateByPhoneNumber = candidateRepository.existsByPhoneNumber(
        phoneNumber
    );
    if(existsCandidateByPhoneNumber) {
        throw new ServerException(
            "A candidate with this phone number already exists",
            HttpStatus.BAD_REQUEST
        );
    }
}

private void validateNewCandidateData(
    Candidate databaseCandidate, CandidateWithAllRelatedDataDTO updatedCandidate
) {
    boolean hasDifferentLinkedin = StringComparator.compareStrings(
        databaseCandidate.getLinkedin(), updatedCandidate.getLinkedin()
    ) != 0;
    if(hasDifferentLinkedin) validateCandidateLinkedin(updatedCandidate.getLinkedin());

    boolean hasDifferentPhoneNumber = !databaseCandidate.getPhoneNumber().equals(
        updatedCandidate.getPhoneNumber()
    );
    if(hasDifferentPhoneNumber) validateCandidatePhoneNumber(updatedCandidate.getPhoneNumber());
}

public CandidateWithAllRelatedDataDTO update(
    Long userId, CandidateWithAllRelatedDataDTO updatedCandidate
) {
    Candidate findedCandidate = findOneWithAllDataByUserId(
        userId
    );
    if(findedCandidate == null) {
        throw new ServerException(
            "Requested user does not have a candidate account",
            HttpStatus.BAD_REQUEST
        );
    }

    CandidateWithAllRelatedDataDTO parsedUpdatedCandidate = 
        CandidateWithAllRelatedDataDTO.FromEntity(findedCandidate);
    parsedUpdatedCandidate.update(updatedCandidate);

    validateNewCandidateData(findedCandidate, parsedUpdatedCandidate);
    parsedUpdatedCandidate.setUpdatedAt(new Date());

    candidateRepository.save(EntityDTO.getParsedEntity(parsedUpdatedCandidate));
    return findOneById(findedCandidate.getId());
}

The JSON that i've been using both in create and update methods:

{
    "photo": "photo.png",
    "profession": "Programador",
    "phoneNumber": "(00)9.0000-0000",
    "linkedin": "https://linkedin.com.br",
    "birthDate": "10/2002",
    "hasProfessionalExperience": true,
    "state": {
        "id": 1
    },
    "city": {
        "id": 1
    },
    "academicEducations": [
        {
            "title": "Ciência da Computação",
            "institutionName": "UFERSA",
            "startYear": 2020,
            "endYear": 2024,
            "levelOfSchooling": "INCOMPLETE_HIGHER_EDUCATION"
        },
        {
            "title": "Direito",
            "institutionName": "UERN",
            "startYear": 2016,
            "endYear": 2020,
            "levelOfSchooling": "COMPLETE_HIGHER_EDUCATION"
        }
    ],
    "professionalExperiences": [
        {
            "roleName": "Administrador de negócios",
            "companyName": "Unijuris",
            "startDate": "05/2019",
            "endDate": "08/2022",
            "assignmentsDescription": "Gestão de pessoas, execução de movimentações financeiras.",
            "actualJob": true,
            "state": {
                "id": 1
            },
            "city": {
                "id": 1
            }
        },
        {
            "roleName": "Veterinário",
            "companyName": "Nova startup",
            "startDate": "05/2012",
            "endDate": "06/2019",
            "assignmentsDescription": "Tratamento de animais de várias espécies diferentes.",
            "actualJob": false,
            "state": {
                "id": 1
            },
            "city": {
                "id": 1
            }
        }
    ]
}
  • You have an overriden `equals()` (interesting implementation, but I can't see anything wrong with it) and no `hashCode()`? That might produce weird effects when dealing with collections of `EntityDTO`, as it's violating hashcode contract. – Deltharis Aug 10 '22 at 14:38
  • What is the problem - is the CandidateAcademicEducation.candidate reference null, is the foreign key being updated to be null, or is it trying to insert a new Candidate because the instance is valid just missing an ID? You mention sending data as JSON - What is the state of the entities you build from that before you pass it to JPA exactly? Pre/Post update on Candidate might not help if another instance of CandidateAcademicEducation is getting into the persistence unit that isn't tied to that Candidate. Maybe check the events on CandidateAcademicEducation and where they are created – Chris Aug 10 '22 at 15:06
  • @Deltharis I'm not so familiar with Java, i made this overriden thinking on SortedSet Ordination, and to verify and prevent objects with basic the same properties to be handled differently. I didn't know about this hashCode contract, can you talk more about it? (I'm thinking it's to generate the hash and use it on the comparsion of compareTo instead of inserting the logic directly on compareTo method, but i'm just guessing) – Ríad Oliveira de Morais Aug 11 '22 at 10:21
  • @Chris The problem is the foreign key being updated to null, but strangely it only happens on one of the academicEducations passed via JSON (It's like one of them is being ignored, and i'm think it has something to do with the conversion/deconversion to DTO on the update method of CandidateServices, because when i just fetch the candidate data from database, without making any operation, and save it, this don't happen) – Ríad Oliveira de Morais Aug 11 '22 at 10:30
  • @Chris i updated the post and added the JSON that i've been using on create and update methods. About the Pre/Post update on Candidate, i thought that if i tied the CandidateAcademicEducation instances to Candidate on PreUpdate it would guarantee that all instances sent to persistence have the correct reference to the Candidate, does it not work like that? Can you please explain how exactly i verify the events of an entity? I'm not really sure what you mean by events – Ríad Oliveira de Morais Aug 11 '22 at 11:06
  • You are using "parsedUpdatedCandidate.update(updatedCandidate);" seemingly to merge the incomplete JSON (there are no IDs in there, nor are there CandidateAcademicEducation.candidate references, so I suspect they would all be null), and this maybe where the problem or difference is. You are also using HashSets/HashMaps which rely on hashcode and equals on the objects, and since your objects don't have IDs set, might cause you issues on lookups; not having hashcode impl that matches equals in this case might be helping you, though your sets won't work the way you want them to. – Chris Aug 11 '22 at 15:34

1 Answers1

0

Based on what @Chris said on the comments, it's not possible to guarantee which prePersist/preUpdate method will be called first(From the parent entity or the child), because of that some of the related entities was working perfectly while CandidateAcademicEducation wasn't. So the solution is to set the parent (Candidate reference on CandidateAcademicEducation in this case) references before call save method from the repository.

I've also found this question talking about this uncertainty of prePersist/preUpdate/postPersist/postUpdate: PreUpdate called but child collection entity not stored

@Chris and @Deltharis Thank you very much for the help.